TypeScript文件扩展名:.tsx vs .ts 指南

发布于:2025-07-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

在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 文件

  1. 工具函数和帮助类

    // utils.ts
    export const formatDate = (date: Date): string => {
      return date.toLocaleDateString();
    };
    
  2. 数据类型定义

    // types.ts
    export interface ApiResponse<T> {
      data: T;
      success: boolean;
      message?: string;
    }
    
  3. 业务逻辑层

    // userService.ts
    export class UserService {
      async fetchUsers(): Promise<User[]> {
        // API调用逻辑
      }
    }
    
  4. 配置文件

    // config.ts
    export const API_CONFIG = {
      baseURL: 'https://api.example.com',
      timeout: 5000
    };
    

何时使用 .tsx 文件

  1. 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>;
    };
    
  2. 页面组件

    // HomePage.tsx
    import React from 'react';
    import UserCard from './UserCard';
    
    const HomePage: React.FC = () => {
      return (
        <div>
          <h1>欢迎来到主页</h1>
          <UserCard user={currentUser} />
        </div>
      );
    };
    
  3. 自定义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"
      }
    }
  ]
}

实践指南

  1. 明确职责分离:纯逻辑用.ts,包含JSX的用.tsx
  2. 一致的命名规范:组件文件使用PascalCase,工具文件使用camelCase
  3. 合理的文件组织:按功能模块组织,而不是按文件类型
  4. 类型定义集中管理:将共享的类型定义放在独立的.ts文件中
  5. 避免不必要的.tsx:不包含JSX的文件不要使用.tsx扩展名