🎯 必要性分析

强烈推荐使用 TypeScript 的情况:

1. 企业级和大型应用

typescript

// 复杂的业务逻辑需要类型安全
interface Order {
  id: string
  status: 'pending' | 'processing' | 'completed' | 'cancelled'
  items: OrderItem[]
  customer: Customer
  // 明确的类型定义防止运行时错误
}
2. 组件库和设计系统开发

typescript

// 提供完整的类型提示给使用者
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger'
  size: 'sm' | 'md' | 'lg'
  disabled?: boolean
  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
  children: React.ReactNode
}
3. 团队协作项目
  • 接口契约明确,减少沟通成本

  • 新成员快速理解代码结构

  • 代码审查更高效

可能不需要 TypeScript 的情况:

  • 快速原型和概念验证

  • 简单的展示型页面

  • 团队对 TypeScript 不熟悉且项目紧急

⚡ 使用体验

显著优势:

1. 卓越的 Props 类型检查

typescript

interface UserProfileProps {
  user: {
    id: number
    name: string
    email: string
    avatar?: string
  }
  onUpdate: (user: Partial<User>) => Promise<void>
  permissions: ('read' | 'write' | 'admin')[]
}

const UserProfile: React.FC<UserProfileProps> = ({ 
  user, 
  onUpdate, 
  permissions 
}) => {
  // 完整的自动补全和类型安全
  return <div>{user.name}</div>
}
2. Hooks 的完美类型支持

typescript

// useState 自动类型推断
const [count, setCount] = useState(0) // number 类型自动推断

// 自定义 Hook 类型安全
function useApi<T>(url: string) {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)
  
  useEffect(() => {
    fetchData()
  }, [url])
  
  return { data, loading, error }
}

// 使用时的类型安全
const { data: user } = useApi<User>('/api/user/1')
3. 事件处理的类型安全

typescript

const FormComponent = () => {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    // 完整的表单事件类型支持
  }
  
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value) // 正确的类型推断
  }
  
  return <form onSubmit={handleSubmit}>...</form>
}

生态系统成熟度:

1. 第三方库支持完善

typescript

// Redux Toolkit 完美 TS 支持
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

interface CounterState {
  value: number
}

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 } as CounterState,
  reducers: {
    increment: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    }
  }
})
2. React Router 类型安全

typescript

import { useParams, useNavigate } from 'react-router-dom'

// 路由参数类型安全
interface RouteParams {
  userId: string
}

const UserPage = () => {
  const { userId } = useParams<RouteParams>()
  const navigate = useNavigate()
  
  // 类型安全的导航
  navigate(`/user/${userId}/profile`)
}

🛠️ 最佳实践

1. 组件 Props 定义

typescript

// 推荐:使用 interface 定义 Props
interface CardProps {
  title: string
  description?: string
  onClick?: (id: string) => void
  children?: React.ReactNode
}

// 函数组件写法
const Card: React.FC<CardProps> = ({ title, description, onClick, children }) => {
  return <div>{title}</div>
}

// 或者直接注解参数
const Card = ({ title, description }: CardProps) => {
  return <div>{title}</div>
}

2. Context API 类型安全

typescript

interface AppContextType {
  user: User | null
  theme: 'light' | 'dark'
  toggleTheme: () => void
  login: (user: User) => void
}

const AppContext = createContext<AppContextType | undefined>(undefined)

// 自定义 Hook 提供类型安全
export const useAppContext = () => {
  const context = useContext(AppContext)
  if (context === undefined) {
    throw new Error('useAppContext must be used within AppProvider')
  }
  return context
}

3. 表单处理

typescript

interface FormData {
  name: string
  email: string
  age: number
}

const UserForm = () => {
  const [formData, setFormData] = useState<FormData>({
    name: '',
    email: '',
    age: 0
  })
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setFormData(prev => ({
      ...prev,
      [name]: name === 'age' ? parseInt(value) || 0 : value
    }))
  }
}

📊 实际体验对比

开发效率:

  • 短期:编写速度稍慢,需要定义类型

  • 长期:调试时间大幅减少,重构信心增强

错误预防:

typescript

// TypeScript 在编译时捕获错误
const Component = ({ user }: { user: User }) => {
  // 编译时报错:user.nme 不存在
  return <div>{user.nme}</div> // ❌ Property 'nme' does not exist
}

团队协作提升:

  • 新功能开发时接口明确

  • 代码审查关注点更集中

  • API 变更影响立即显现

🔧 配置和工具链

1. 现代 React + TS 配置

json

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["DOM", "DOM.Iterable", "ES6"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  }
}

2. 开发工具支持

  • VSCode 提供完美的 IntelliSense

  • ESLint + TypeScript ESLint 代码质量保障

  • 热重载保持开发体验流畅

🎯 结论

React + TypeScript 是目前前端开发的最佳实践组合之一:

强烈推荐使用:

  • ✅ 所有新开始的 React 项目

  • ✅ 团队协作项目

  • ✅ 需要长期维护的应用

  • ✅ 复杂业务逻辑的应用

核心价值:

  1. 开发阶段:提供卓越的开发者体验和代码提示

  2. 维护阶段:大幅降低维护成本和错误率

  3. 协作阶段:明确接口契约,提升团队效率

行业趋势:

  • 大多数 React 职位要求 TypeScript 技能

  • 主流 UI 库和工具链优先支持 TypeScript

  • 大型项目几乎全部采用 TypeScript

总的来说,React 与 TypeScript 的结合已经非常成熟,是生产级 React 应用的首选方案。 除非是极其简单的场景,否则都推荐使用 TypeScript。

React + TypeScript 表格管理页面

下面创建一个完整的表格管理页面,包含增删改查功能。

🚀 完整实现

1. 首先安装必要的依赖

bash

npm install react react-dom typescript @types/react @types/react-dom

2. 类型定义文件

typescript

// types.ts
export interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'editor';
  status: 'active' | 'inactive';
  createdAt: Date;
}

export type UserFormData = Omit<User, 'id' | 'createdAt'>;

3. 主表格组件

typescript

// UserTable.tsx
import React, { useState, useEffect } from 'react';
import { User, UserFormData } from './types';
import { UserForm } from './UserForm';
import './UserTable.css';

// 模拟数据
const initialUsers: User[] = [
  {
    id: '1',
    name: '张三',
    email: 'zhangsan@example.com',
    role: 'admin',
    status: 'active',
    createdAt: new Date('2023-01-15')
  },
  {
    id: '2',
    name: '李四',
    email: 'lisi@example.com',
    role: 'user',
    status: 'active',
    createdAt: new Date('2023-02-20')
  },
  {
    id: '3',
    name: '王五',
    email: 'wangwu@example.com',
    role: 'editor',
    status: 'inactive',
    createdAt: new Date('2023-03-10')
  }
];

export const UserTable: React.FC = () => {
  const [users, setUsers] = useState<User[]>(initialUsers);
  const [editingUser, setEditingUser] = useState<User | null>(null);
  const [isFormOpen, setIsFormOpen] = useState(false);
  const [searchTerm, setSearchTerm] = useState('');

  // 过滤用户
  const filteredUsers = users.filter(user =>
    user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
    user.email.toLowerCase().includes(searchTerm.toLowerCase())
  );

  // 添加用户
  const handleAddUser = (userData: UserFormData) => {
    const newUser: User = {
      ...userData,
      id: Date.now().toString(),
      createdAt: new Date()
    };
    setUsers(prev => [...prev, newUser]);
    setIsFormOpen(false);
  };

  // 编辑用户
  const handleEditUser = (userData: UserFormData) => {
    if (!editingUser) return;
    
    setUsers(prev => prev.map(user =>
      user.id === editingUser.id
        ? { ...user, ...userData }
        : user
    ));
    setEditingUser(null);
    setIsFormOpen(false);
  };

  // 删除用户
  const handleDeleteUser = (id: string) => {
    if (window.confirm('确定要删除这个用户吗?')) {
      setUsers(prev => prev.filter(user => user.id !== id));
    }
  };

  // 开始编辑
  const handleStartEdit = (user: User) => {
    setEditingUser(user);
    setIsFormOpen(true);
  };

  // 取消编辑/添加
  const handleCancel = () => {
    setEditingUser(null);
    setIsFormOpen(false);
  };

  // 获取状态标签类名
  const getStatusClass = (status: User['status']): string => {
    return status === 'active' ? 'status-active' : 'status-inactive';
  };

  // 获取角色标签类名
  const getRoleClass = (role: User['role']): string => {
    return `role-${role}`;
  };

  return (
    <div className="user-table-container">
      <div className="table-header">
        <h1>用户管理</h1>
        <div className="header-actions">
          <div className="search-box">
            <input
              type="text"
              placeholder="搜索用户名或邮箱..."
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              className="search-input"
            />
          </div>
          <button
            onClick={() => setIsFormOpen(true)}
            className="btn btn-primary"
          >
            添加用户
          </button>
        </div>
      </div>

      <div className="table-wrapper">
        <table className="user-table">
          <thead>
            <tr>
              <th>姓名</th>
              <th>邮箱</th>
              <th>角色</th>
              <th>状态</th>
              <th>创建时间</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody>
            {filteredUsers.length === 0 ? (
              <tr>
                <td colSpan={6} className="no-data">
                  没有找到用户
                </td>
              </tr>
            ) : (
              filteredUsers.map(user => (
                <tr key={user.id}>
                  <td>{user.name}</td>
                  <td>{user.email}</td>
                  <td>
                    <span className={`role-tag ${getRoleClass(user.role)}`}>
                      {user.role === 'admin' && '管理员'}
                      {user.role === 'editor' && '编辑'}
                      {user.role === 'user' && '用户'}
                    </span>
                  </td>
                  <td>
                    <span className={`status-tag ${getStatusClass(user.status)}`}>
                      {user.status === 'active' ? '激活' : '未激活'}
                    </span>
                  </td>
                  <td>{user.createdAt.toLocaleDateString('zh-CN')}</td>
                  <td>
                    <div className="action-buttons">
                      <button
                        onClick={() => handleStartEdit(user)}
                        className="btn btn-edit"
                      >
                        编辑
                      </button>
                      <button
                        onClick={() => handleDeleteUser(user.id)}
                        className="btn btn-delete"
                      >
                        删除
                      </button>
                    </div>
                  </td>
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>

      {/* 添加/编辑表单模态框 */}
      {isFormOpen && (
        <UserForm
          user={editingUser}
          onSubmit={editingUser ? handleEditUser : handleAddUser}
          onCancel={handleCancel}
        />
      )}
    </div>
  );
};

4. 表单组件

typescript

// UserForm.tsx
import React, { useState, useEffect } from 'react';
import { User, UserFormData } from './types';
import './UserForm.css';

interface UserFormProps {
  user?: User | null;
  onSubmit: (userData: UserFormData) => void;
  onCancel: () => void;
}

export const UserForm: React.FC<UserFormProps> = ({ 
  user, 
  onSubmit, 
  onCancel 
}) => {
  const [formData, setFormData] = useState<UserFormData>({
    name: '',
    email: '',
    role: 'user',
    status: 'active'
  });

  const [errors, setErrors] = useState<Partial<UserFormData>>({});

  // 编辑模式下填充数据
  useEffect(() => {
    if (user) {
      setFormData({
        name: user.name,
        email: user.email,
        role: user.role,
        status: user.status
      });
    }
  }, [user]);

  // 验证表单
  const validateForm = (): boolean => {
    const newErrors: Partial<UserFormData> = {};

    if (!formData.name.trim()) {
      newErrors.name = '姓名不能为空';
    }

    if (!formData.email.trim()) {
      newErrors.email = '邮箱不能为空';
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
      newErrors.email = '邮箱格式不正确';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  // 处理表单提交
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    
    if (validateForm()) {
      onSubmit(formData);
    }
  };

  // 处理输入变化
  const handleChange = (
    e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
  ) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
    
    // 清除对应字段的错误
    if (errors[name as keyof UserFormData]) {
      setErrors(prev => ({
        ...prev,
        [name]: undefined
      }));
    }
  };

  return (
    <div className="modal-overlay">
      <div className="modal">
        <h2>{user ? '编辑用户' : '添加用户'}</h2>
        
        <form onSubmit={handleSubmit} className="user-form">
          <div className="form-group">
            <label htmlFor="name">姓名 *</label>
            <input
              type="text"
              id="name"
              name="name"
              value={formData.name}
              onChange={handleChange}
              className={errors.name ? 'error' : ''}
            />
            {errors.name && <span className="error-text">{errors.name}</span>}
          </div>

          <div className="form-group">
            <label htmlFor="email">邮箱 *</label>
            <input
              type="email"
              id="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              className={errors.email ? 'error' : ''}
            />
            {errors.email && <span className="error-text">{errors.email}</span>}
          </div>

          <div className="form-group">
            <label htmlFor="role">角色</label>
            <select
              id="role"
              name="role"
              value={formData.role}
              onChange={handleChange}
            >
              <option value="user">用户</option>
              <option value="editor">编辑</option>
              <option value="admin">管理员</option>
            </select>
          </div>

          <div className="form-group">
            <label htmlFor="status">状态</label>
            <select
              id="status"
              name="status"
              value={formData.status}
              onChange={handleChange}
            >
              <option value="active">激活</option>
              <option value="inactive">未激活</option>
            </select>
          </div>

          <div className="form-actions">
            <button type="button" onClick={onCancel} className="btn btn-secondary">
              取消
            </button>
            <button type="submit" className="btn btn-primary">
              {user ? '更新' : '添加'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

5. CSS 样式文件

UserTable.css

css

.user-table-container {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid #e0e0e0;
}

.table-header h1 {
  margin: 0;
  color: #333;
}

.header-actions {
  display: flex;
  gap: 15px;
  align-items: center;
}

.search-box {
  position: relative;
}

.search-input {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  width: 250px;
  font-size: 14px;
}

.search-input:focus {
  outline: none;
  border-color: #007bff;
}

.table-wrapper {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.user-table {
  width: 100%;
  border-collapse: collapse;
}

.user-table th,
.user-table td {
  padding: 12px 16px;
  text-align: left;
  border-bottom: 1px solid #e0e0e0;
}

.user-table th {
  background-color: #f8f9fa;
  font-weight: 600;
  color: #555;
}

.user-table tbody tr:hover {
  background-color: #f5f5f5;
}

.no-data {
  text-align: center;
  color: #999;
  padding: 40px !important;
}

/* 状态标签 */
.status-tag {
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}

.status-active {
  background-color: #d4edda;
  color: #155724;
}

.status-inactive {
  background-color: #f8d7da;
  color: #721c24;
}

/* 角色标签 */
.role-tag {
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}

.role-admin {
  background-color: #d1ecf1;
  color: #0c5460;
}

.role-editor {
  background-color: #e2e3e5;
  color: #383d41;
}

.role-user {
  background-color: #fff3cd;
  color: #856404;
}

/* 按钮样式 */
.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}

.btn-primary {
  background-color: #007bff;
  color: white;
}

.btn-primary:hover {
  background-color: #0056b3;
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

.btn-secondary:hover {
  background-color: #545b62;
}

.btn-edit {
  background-color: #28a745;
  color: white;
  padding: 6px 12px;
  margin-right: 8px;
}

.btn-edit:hover {
  background-color: #1e7e34;
}

.btn-delete {
  background-color: #dc3545;
  color: white;
  padding: 6px 12px;
}

.btn-delete:hover {
  background-color: #c82333;
}

.action-buttons {
  display: flex;
  gap: 8px;
}
UserForm.css

css

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal {
  background: white;
  border-radius: 8px;
  padding: 24px;
  width: 500px;
  max-width: 90vw;
  max-height: 90vh;
  overflow-y: auto;
}

.modal h2 {
  margin: 0 0 20px 0;
  color: #333;
}

.user-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.form-group label {
  font-weight: 500;
  color: #555;
  margin-bottom: 4px;
}

.form-group input,
.form-group select {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
}

.form-group input:focus,
.form-group select:focus {
  outline: none;
  border-color: #007bff;
}

.form-group input.error {
  border-color: #dc3545;
}

.error-text {
  color: #dc3545;
  font-size: 12px;
  margin-top: 2px;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #e0e0e0;
}

6. 主应用文件

typescript

// App.tsx
import React from 'react';
import { UserTable } from './UserTable';
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <UserTable />
    </div>
  );
};

export default App;
App.css

css

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #f5f5f5;
}

.App {
  min-height: 100vh;
}

7. 使用示例

typescript

// index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

✨ 功能特点

  1. 完整的 CRUD 操作:添加、编辑、删除、查看

  2. 搜索功能:按姓名或邮箱搜索

  3. 表单验证:客户端验证确保数据完整性

  4. 类型安全:完整的 TypeScript 类型定义

  5. 响应式设计:适配不同屏幕尺寸

  6. 用户体验:加载状态、确认对话框、悬停效果

🎯 TypeScript 优势体现

  • 类型安全:编译时捕获类型错误

  • 智能提示:完整的代码补全和类型推断

  • 接口明确:清晰的 API 契约

  • 重构友好:类型系统帮助安全重构

这个实现展示了 React + TypeScript 在实际项目中的强大组合,提供了优秀的开发体验和代码质量保证。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐