Skip to content
刘恩义的技术博客
返回

React + TypeScript 最佳实践

Edit page

React 与 TypeScript 的结合已经成为现代前端开发的主流选择。TypeScript 为 React 应用带来了类型安全,减少了运行时错误,同时提升了代码的可维护性和可读性。本文将详细介绍 React 与 TypeScript 结合的最佳实践,帮助你写出更高质量的代码。

1. 类型定义最佳实践

1.1 组件 Props 类型

为组件 props 定义明确的类型是使用 TypeScript 的基础:

// 方式一:接口定义
interface ButtonProps {
  text: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
}

const Button: React.FC<ButtonProps> = ({ 
  text, 
  onClick, 
  variant = 'primary', 
  size = 'medium' 
}) => {
  return (
    <button 
      onClick={onClick}
      className={`btn btn-${variant} btn-${size}`}
    >
      {text}
    </button>
  );
};

// 方式二:类型别名
 type InputProps = {
  value: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  placeholder?: string;
  disabled?: boolean;
};

const Input: React.FC<InputProps> = ({ 
  value, 
  onChange, 
  placeholder, 
  disabled 
}) => {
  return (
    <input 
      type="text" 
      value={value}
      onChange={onChange}
      placeholder={placeholder}
      disabled={disabled}
    />
  );
};

1.2 事件类型

使用 React 提供的事件类型,确保类型安全:

// 常见事件类型
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};

const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  // 处理点击事件
};

const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  // 处理表单提交
};

1.3 状态类型

为状态定义明确的类型,避免使用 any

// 基本状态类型
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);

// 复杂状态类型
interface User {
  id: number;
  name: string;
  email: string;
}

const [user, setUser] = useState<User | null>(null);
const [users, setUsers] = useState<User[]>([]);

// 联合类型状态
 type ApiState<T> = {
  status: 'idle';
} | {
  status: 'loading';
} | {
  status: 'success';
  data: T;
} | {
  status: 'error';
  error: string;
};

const [apiState, setApiState] = useState<ApiState<User[]>>({
  status: 'idle'
});

1.4 泛型组件

使用泛型创建可复用的组件:

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// 使用
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

const todos: Todo[] = [
  { id: 1, text: 'Learn React', completed: true },
  { id: 2, text: 'Learn TypeScript', completed: false }
];

<List
  items={todos}
  keyExtractor={todo => todo.id}
  renderItem={todo => (
    <div>
      <input 
        type="checkbox" 
        checked={todo.completed} 
        onChange={() => {}}
      />
      <span>{todo.text}</span>
    </div>
  )}
/>

2. 组件设计最佳实践

2.1 函数组件 vs 类组件

优先使用函数组件和 Hooks,它们与 TypeScript 结合更自然:

// 推荐:函数组件 + Hooks
const Counter: React.FC = () => {
  const [count, setCount] = useState<number>(0);
  
  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

// 不推荐:类组件
class CounterClass extends React.Component<{}, { count: number }> {
  constructor(props: {}) {
    super(props);
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };
  
  decrement = () => {
    this.setState(prev => ({ count: prev.count - 1 }));
  };
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

2.2 自定义 Hooks

创建类型安全的自定义 Hooks:

// 自定义 Hooks 类型
interface UseCounterReturn {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

function useCounter(initialValue: number = 0): UseCounterReturn {
  const [count, setCount] = useState<number>(initialValue);
  
  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);
  const reset = () => setCount(initialValue);
  
  return { count, increment, decrement, reset };
}

// 使用自定义 Hooks
const Counter: React.FC = () => {
  const { count, increment, decrement, reset } = useCounter(10);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

2.3 组件组合

使用类型安全的组件组合模式:

interface CardProps {
  title: string;
  children: React.ReactNode;
  footer?: React.ReactNode;
}

const Card: React.FC<CardProps> = ({ title, children, footer }) => {
  return (
    <div className="card">
      <div className="card-header">{title}</div>
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
};

// 使用
const UserCard: React.FC<{ user: User }> = ({ user }) => {
  return (
    <Card 
      title={user.name}
      footer={
        <button onClick={() => handleEdit(user.id)}>Edit</button>
      }
    >
      <p>Email: {user.email}</p>
      <p>Role: {user.role}</p>
    </Card>
  );
};

3. 状态管理最佳实践

3.1 Context API

使用 TypeScript 增强 Context API 的类型安全性:

// 定义 Context 类型
interface UserContextType {
  user: User | null;
  setUser: React.Dispatch<React.SetStateAction<User | null>>;
  isLoading: boolean;
  error: string | null;
}

// 创建 Context
const UserContext = createContext<UserContextType | undefined>(undefined);

// 创建 Provider
interface UserProviderProps {
  children: React.ReactNode;
}

const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  
  // 登录函数
  const login = async (email: string, password: string) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await api.login(email, password);
      setUser(response.user);
    } catch (err) {
      setError('Login failed');
    } finally {
      setIsLoading(false);
    }
  };
  
  // 登出函数
  const logout = () => {
    setUser(null);
  };
  
  return (
    <UserContext.Provider value={{ user, setUser, isLoading, error }}>
      {children}
    </UserContext.Provider>
  );
};

// 创建自定义 Hook
const useUser = (): UserContextType => {
  const context = useContext(UserContext);
  if (context === undefined) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
};

// 使用
const Profile: React.FC = () => {
  const { user, isLoading, error } = useUser();
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return <div>Please login</div>;
  
  return (
    <div>
      <h2>Profile</h2>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
    </div>
  );
};

3.2 Redux Toolkit

使用 Redux Toolkit 结合 TypeScript,简化状态管理:

// 定义类型
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

type TodoState = {
  todos: Todo[];
  status: 'idle' | 'loading' | 'succeeded' | 'failed';
  error: string | null;
};

// 创建 slice
const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: [],
    status: 'idle',
    error: null
  } as TodoState,
  reducers: {
    todoAdded: (state, action: PayloadAction<string>) => {
      const newTodo: Todo = {
        id: nanoid(),
        text: action.payload,
        completed: false
      };
      state.todos.push(newTodo);
    },
    todoToggled: (state, action: PayloadAction<number>) => {
      const todo = state.todos.find(todo => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.todos = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message || 'Failed to fetch todos';
      });
  }
});

// 导出 actions
export const { todoAdded, todoToggled } = todosSlice.actions;

// 导出 reducer
export default todosSlice.reducer;

// 创建异步 thunk
export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await api.getTodos();
    return response.todos;
  }
);

// 定义 RootState
export type RootState = ReturnType<typeof store.getState>;

// 创建 useAppSelector 自定义 Hook
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// 使用
const TodoList: React.FC = () => {
  const todos = useAppSelector((state) => state.todos.todos);
  const status = useAppSelector((state) => state.todos.status);
  const error = useAppSelector((state) => state.todos.error);
  const dispatch = useDispatch();
  
  useEffect(() => {
    if (status === 'idle') {
      dispatch(fetchTodos());
    }
  }, [status, dispatch]);
  
  const handleAddTodo = (text: string) => {
    dispatch(todoAdded(text));
  };
  
  if (status === 'loading') return <div>Loading...</div>;
  if (status === 'failed') return <div>Error: {error}</div>;
  
  return (
    <div>
      <TodoForm onAddTodo={handleAddTodo} />
      <ul>
        {todos.map(todo => (
          <TodoItem 
            key={todo.id} 
            todo={todo} 
            onToggle={() => dispatch(todoToggled(todo.id))} 
          />
        ))}
      </ul>
    </div>
  );
};

4. 工具类型与高级技巧

4.1 常用工具类型

TypeScript 提供了许多实用的工具类型,可以简化类型定义:

// Partial<T>:将所有属性变为可选
const updateUser = (user: User, updates: Partial<User>) => {
  return { ...user, ...updates };
};

// Required<T>:将所有属性变为必需
const createUser = (user: Required<Pick<User, 'name' | 'email'>>) => {
  // 创建用户
};

// Pick<T, K>:从 T 中选取指定属性
const getUserInfo = (user: Pick<User, 'id' | 'name' | 'email'>) => {
  return {
    id: user.id,
    name: user.name,
    email: user.email
  };
};

// Omit<T, K>:从 T 中排除指定属性
const createUserForm = (data: Omit<User, 'id' | 'createdAt'>) => {
  // 创建用户
};

// Record<K, T>:创建键为 K,值为 T 的对象类型
const userRoles: Record<string, 'admin' | 'user' | 'guest'> = {
  '1': 'admin',
  '2': 'user',
  '3': 'guest'
};

4.2 类型断言

在必要时使用类型断言,但要谨慎:

// 类型断言
const user = await fetchUser();
const typedUser = user as User;

// 非空断言
const element = document.getElementById('root')!;
ReactDOM.createRoot(element).render(<App />);

// 可选链与空值合并
const userName = user?.name ?? 'Unknown';

4.3 模块增强

通过模块增强扩展现有类型:

// 扩展 Window 接口
declare global {
  interface Window {
    __APP__: {
      version: string;
      environment: 'development' | 'production';
    };
  }
}

// 使用
console.log(window.__APP__.version);

// 扩展第三方库类型
import 'axios';

declare module 'axios' {
  export interface AxiosInstance {
    customMethod: () => void;
  }
}

5. 项目配置与工具

5.1 tsconfig.json 配置

合理配置 tsconfig.json,提升开发体验:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

5.2 ESLint 与 Prettier 配置

结合 ESLint 和 Prettier,确保代码质量和一致性:

// .eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2020,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "rules": {
    "react/prop-types": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-explicit-any": "warn"
  }
}

// .prettierrc
{
  "singleQuote": true,
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "jsxSingleQuote": true
}

5.3 类型声明文件

为第三方库创建类型声明文件:

// types/custom.d.ts
declare module '*.svg' {
  import React = require('react');
  export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

declare module '*.png' {
  const value: string;
  export default value;
}

declare module '*.jpg' {
  const value: string;
  export default value;
}

6. 最佳实践总结

6.1 类型安全

6.2 代码组织

6.3 性能与可读性

6.4 工具与生态

总结

React 与 TypeScript 的结合可以显著提升代码质量和开发效率。通过本文介绍的最佳实践,你可以:

  1. 编写类型安全的组件:为 props、状态和事件定义明确的类型
  2. 创建可复用的自定义 Hooks:结合 TypeScript 增强类型安全性
  3. 实现类型安全的状态管理:使用 Context API 或 Redux Toolkit
  4. 利用高级类型特性:使用工具类型和模块增强
  5. 配置最佳开发环境:合理配置 TypeScript、ESLint 和 Prettier

随着 TypeScript 在前端开发中的普及,掌握 React 与 TypeScript 的最佳实践将成为前端开发者的核心竞争力。通过不断学习和实践,你可以写出更安全、更可维护的 React 应用。


Edit page

Previous Post
React Server Components 实战指南
Next Post
React 性能优化实战指南