在TypeScript开发中,我们经常会遇到两种文件扩展名:
.ts
和
.tsx
。虽然它们都是TypeScript文件,但用途和特性却有显著差异。
基本定义
.ts 文件
.ts
文件是标准的TypeScript文件,主要用于编写纯TypeScript代码,不包含JSX语法。
.tsx 文件
.tsx
文件是TypeScript JSX文件,专门用于编写包含JSX语法的TypeScript代码,主要用于React组件开发。
主要区别
1. JSX语法支持
最核心的区别在于JSX语法的支持:
.ts 文件示例:
// utils.ts
export interface User {
id: number;
name: string;
email: string;
}
export const formatUserName = (user: User): string => {
return `${user.name} (${user.email})`;
};
export class UserService {
private users: User[] = [];
addUser(user: User): void {
this.users.push(user);
}
getUser(id: number): User | undefined {
return this.users.find(user => user.id === id);
}
}
.tsx 文件示例:
// UserCard.tsx
import React from 'react';
interface User {
id: number;
name: string;
email: string;
}
interface UserCardProps {
user: User;
onEdit: (user: User) => void;
}
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onEdit(user)}>
编辑用户
</button>
</div>
);
};
export default UserCard;
2. 编译器行为
TypeScript编译器对这两种文件的处理方式不同:
- .ts 文件:编译器假设文件中不包含JSX,如果遇到JSX语法会报错
- .tsx 文件:编译器启用JSX解析,能够正确处理JSX语法
3. 泛型语法的差异
在.tsx
文件中,泛型语法可能与JSX标签产生冲突:
// 在 .ts 文件中,这样写没问题
const identity = <T>(arg: T): T => {
return arg;
};
// 在 .tsx 文件中,编译器可能将 <T> 误认为JSX标签
// 需要添加约束来消除歧义
const identity = <T extends {}>(arg: T): T => {
return arg;
};
// 或者使用这种方式
const identity = <T,>(arg: T): T => {
return arg;
};
4. 导入和导出的差异
虽然语法相同,但用途不同:
// math.ts - 纯逻辑模块
export const add = (a: number, b: number): number => a + b;
export const multiply = (a: number, b: number): number => a * b;
// Calculator.tsx - React组件
import React from 'react';
import { add, multiply } from './math';
const Calculator: React.FC = () => {
const [result, setResult] = React.useState<number>(0);
return (
<div>
<button onClick={() => setResult(add(5, 3))}>
5 + 3 = {result}
</button>
</div>
);
};
export default Calculator;
适用场景
何时使用 .ts 文件
工具函数和帮助类
// utils.ts export const formatDate = (date: Date): string => { return date.toLocaleDateString(); };
数据类型定义
// types.ts export interface ApiResponse<T> { data: T; success: boolean; message?: string; }
业务逻辑层
// userService.ts export class UserService { async fetchUsers(): Promise<User[]> { // API调用逻辑 } }
配置文件
// config.ts export const API_CONFIG = { baseURL: 'https://api.example.com', timeout: 5000 };
何时使用 .tsx 文件
React组件
// Button.tsx import React from 'react'; interface ButtonProps { onClick: () => void; children: React.ReactNode; } const Button: React.FC<ButtonProps> = ({ onClick, children }) => { return <button onClick={onClick}>{children}</button>; };
页面组件
// HomePage.tsx import React from 'react'; import UserCard from './UserCard'; const HomePage: React.FC = () => { return ( <div> <h1>欢迎来到主页</h1> <UserCard user={currentUser} /> </div> ); };
自定义Hooks(如果返回JSX)
// useModal.tsx import React from 'react'; export const useModal = () => { const [isOpen, setIsOpen] = React.useState(false); const Modal = ({ children }: { children: React.ReactNode }) => { if (!isOpen) return null; return <div className="modal">{children}</div>; }; return { isOpen, setIsOpen, Modal }; };
项目结构建议
src/
├── components/ # React组件 (.tsx)
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── UserCard.tsx
├── pages/ # 页面组件 (.tsx)
│ ├── HomePage.tsx
│ └── UserPage.tsx
├── hooks/ # 自定义hooks (.ts或.tsx)
│ ├── useApi.ts
│ └── useModal.tsx
├── utils/ # 工具函数 (.ts)
│ ├── formatters.ts
│ └── validators.ts
├── types/ # 类型定义 (.ts)
│ ├── api.ts
│ └── user.ts
├── services/ # 业务逻辑 (.ts)
│ ├── userService.ts
│ └── apiService.ts
└── config/ # 配置文件 (.ts)
└── constants.ts
配置注意事项
tsconfig.json 配置
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx" // 重要:JSX的处理方式
},
"include": [
"src/**/*.ts",
"src/**/*.tsx" // 包含两种文件类型
]
}
ESLint 配置
{
"extends": [
"@typescript-eslint/recommended"
],
"rules": {
// 针对 .tsx 文件的特殊规则
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
},
"overrides": [
{
"files": ["*.tsx"],
"rules": {
"react/prop-types": "off"
}
}
]
}
实践指南
- 明确职责分离:纯逻辑用
.ts
,包含JSX的用.tsx
- 一致的命名规范:组件文件使用PascalCase,工具文件使用camelCase
- 合理的文件组织:按功能模块组织,而不是按文件类型
- 类型定义集中管理:将共享的类型定义放在独立的
.ts
文件中 - 避免不必要的
.tsx
:不包含JSX的文件不要使用.tsx
扩展名