React + TypeScript最佳实践

TypeScript与前端框架的结合已经成为现代前端开发的标配,特别是在React生态系统中。TypeScript为React应用带来了类型安全、更好的代码提示和更可维护的代码结构。本文将探讨React与TypeScript结合使用时的最佳实践。

1. 项目初始化

使用Create React App创建TypeScript项目是最简单的方式:

bash 复制代码
npx create-react-app my-app --template typescript

或者使用Vite:

bash 复制代码
npm create vite@latest my-app -- --template react-ts

2. 组件定义

函数组件

使用React.FC类型定义函数组件:

typescript 复制代码
interface Props {
  name: string;
  age?: number; // 可选属性
}

const MyComponent: React.FC<Props> = ({ name, age = 18 }) => {
  return (
    <div>
      Hello, {name}. You are {age} years old.
    </div>
  );
};

类组件

typescript 复制代码
interface State {
  count: number;
}

interface Props {
  initialCount?: number;
}

class Counter extends React.Component<Props, State> {
  state: State = {
    count: this.props.initialCount || 0,
  };

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

3. Hooks与TypeScript

useState

typescript 复制代码
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);

useEffect

typescript 复制代码
useEffect(() => {
  const fetchData = async () => {
    const response = await fetch('/api/user');
    const data: User = await response.json();
    setUser(data);
  };

  fetchData();
}, []);

useReducer

typescript 复制代码
type Action = 
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset'; payload: number };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    case 'reset':
      return action.payload;
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, 0);

4. 事件处理

typescript 复制代码
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.preventDefault();
  console.log('Clicked!');
};

return (
  <>
    <input type="text" onChange={handleChange} />
    <button onClick={handleClick}>Click me</button>
  </>
);

5. 高阶组件

typescript 复制代码
interface WithLoadingProps {
  loading: boolean;
}

function withLoading<P extends object>(
  Component: React.ComponentType<P>
): React.FC<P & WithLoadingProps> {
  return ({ loading, ...props }: WithLoadingProps & P) => {
    return loading ? <div>Loading...</div> : <Component {...props as P} />;
  };
}

6. Context API

typescript 复制代码
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = React.createContext<ThemeContextType | undefined>(undefined);

const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const useTheme = () => {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

7. 自定义Hook

typescript 复制代码
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

8. 第三方库集成

对于没有类型定义的第三方库,可以创建declarations.d.ts文件:

typescript 复制代码
declare module 'untyped-library' {
  export function doSomething(): void;
}

或者安装社区维护的类型定义:

bash 复制代码
npm install --save-dev @types/untyped-library

9. 测试

使用Jest和React Testing Library进行类型安全的测试:

typescript 复制代码
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('increments counter', () => {
  render(<Counter initialCount={0} />);
  const button = screen.getByText(/increment/i);
  fireEvent.click(button);
  expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});

10. 代码组织

推荐的项目结构:

复制代码
src/
  components/
    Button/
      Button.tsx
      Button.test.tsx
      Button.stories.tsx
      index.ts
  hooks/
    useLocalStorage.ts
  types/
    user.d.ts
  utils/
    api.ts
  App.tsx
  index.tsx

总结

React与TypeScript的结合为前端开发带来了显著的改进。通过遵循这些最佳实践,您可以构建更健壮、更易维护的应用程序。记住:

  1. 始终为组件props和state定义明确的类型
  2. 利用TypeScript的泛型特性处理通用逻辑
  3. 为自定义Hook和Context提供完整的类型定义
  4. 保持类型定义与业务逻辑同步更新
  5. 利用类型推断减少冗余的类型注解

随着TypeScript在React生态系统中的日益普及,掌握这些最佳实践将使您能够构建更高质量的应用程序。