目录
[1. 创建 TypeScript React 项目](#1. 创建 TypeScript React 项目)
[2. 关键tsconfig.json配置](#2. 关键tsconfig.json配置)
[1. 函数组件(React 18+)](#1. 函数组件(React 18+))
[三、Hooks 的深度类型集成](#三、Hooks 的深度类型集成)
[1. useState](#1. useState)
[2. useEffect](#2. useEffect)
[3. useRef](#3. useRef)
[4. 自定义 Hook](#4. 自定义 Hook)
[1. 表单事件](#1. 表单事件)
[2. 鼠标/键盘事件](#2. 鼠标/键盘事件)
[五、状态管理(Redux Toolkit)](#五、状态管理(Redux Toolkit))
[1.定义类型化的 Slice](#1.定义类型化的 Slice)
[2. 类型化的useSelector和useDispatch(组件中使用)](#2. 类型化的useSelector和useDispatch(组件中使用))
[六、路由(React Router v6)](#六、路由(React Router v6))
[1. 扩展全局类型](#1. 扩展全局类型)
[2. 组件默认 Props](#2. 组件默认 Props)
[3. 泛型组件](#3. 泛型组件)
[5.Context API类型安全](#5.Context API类型安全)
[1. Jest + Testing Library](#1. Jest + Testing Library)
[1. 如何处理第三方库类型?](#1. 如何处理第三方库类型?)
[2.处理动态导入(Lazy Loading )](#2.处理动态导入(Lazy Loading ))
[3. 类型断言的使用](#3. 类型断言的使用)
[3. 处理可选 Props](#3. 处理可选 Props)
[1.使用react.memo 优化渲染](#1.使用react.memo 优化渲染)
一、环境与项目配置
1. 创建 TypeScript React 项目
使用 Create React App(推荐)
npx create-react-app my-app --template typescript
或使用 Vite(更轻量)
npm create vite@latest my-react-app -- --template react-ts
2. 关键tsconfig.json配置
TypeScript
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"], // 浏览器环境支持
"jsx": "react-jsx", // React 17+ 的 JSX 转换
"strict": true, // 启用所有严格类型检查
"esModuleInterop": true, // 兼容 CommonJS/ES6 模块
"skipLibCheck": true, // 跳过第三方库类型检查(提升速度)
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"baseUrl": "./src", // 路径别名基础目录
"paths": {
"@components/*": ["components/*"] // 路径别名配置
}
},
"include": ["src/**/*"]
}
3.安装核心类型包
TypeScript
npm install @types/react @types/react-dom @types/node --save-dev
二、组件类型定义
1. 函数组件(React 18+)
TypeScript
// Button.tsx
import React from 'react';
// 定义 Props 类型
interface ButtonProps {
children: React.ReactNode;
onClick: () => void;
variant?: 'primary' | 'secondary';//可选属性
disabled?: boolean;//可选回调函数
}
//使用React.FC 泛型定义组件( React 18 后 FC 不再隐式包含 children)
const Button: React.FC<ButtonProps> = ({
children,
onClick,
variant = 'primary', //默认值
disabled = false
}) => {
return (
<button
className={`btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
};
export default Button;
2.类组件
TypeScript
// Counter.tsx
import React from 'react';
//State和Props类型定义
interface CounterProps {
initialCount?: number;
}
interface CounterState {
count: number;
}
class Counter extends React.Component<CounterProps, CounterState> {
// 默认 Props
static defaultProps: Partial<CounterProps> = {
initialCount: 0
};
// 初始化 State(需明确类型)
state: CounterState = {
count: this.props.initialCount!
};
// 箭头函数绑定 this(避免手动 bind)
increment = () => {
this.setState((prev) => ({ count: prev.count + 1 }));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
三、Hooks 的深度类型集成
1. useState
TypeScript
//react
//const [count, setCount] = React.useState<number>(0); // 显式指定类型
//const [user, setUser] = React.useState<User | null>(null); // 联合类型
// 基础类型推断
const [count, setCount] = useState<number>(0);
// 复杂对象类型(明确初始值)
interface User {
id: string;
name: string;
email?: string;
}
const [user, setUser] = useState<User>({
id: '1',
name: 'Alice'
});
2. useEffect
TypeScript
// 异步请求的类型安全处理
useEffect(() => {
let isMounted = true; // 防止组件卸载后更新状态
const fetchData = async () => {
try {
const response = await fetch('/api/users');
const data: User[] = await response.json();
if (isMounted) setUsers(data);
} catch (error) {
console.error('Fetch error:', error);
}
};
fetchData();
return () => {
isMounted = false;
};
}, []);
3. useRef
TypeScript
// DOM 引用
const inputRef = useRef<HTMLInputElement>(null);
// 可变值(非 DOM)
// 引用 DOM 元素
const inputRef = useRef<HTMLInputElement>(null);
// 存储可变值(非 DOM)
interface Timer {
id: number;
start: () => void;
}
const timerRef = useRef<Timer | null>(null);
4. 自定义 Hook
TypeScript
// useLocalStorage.ts
import { useState, useEffect } from 'react';
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) {
return initialValue;
}
});
const setValue = (value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('LocalStorage set error:', error);
}
};
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key) {
setStoredValue(JSON.parse(e.newValue!));
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [key]);
return [storedValue, setValue];
}
// 使用
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');
四、事件处理
1. 表单事件
TypeScript
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// 访问表单元素
const input = e.currentTarget.elements.namedItem('username') as HTMLInputElement;
console.log(input.value);
};
2. 鼠标/键盘事件
TypeScript
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.clientX, e.clientY);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
// 处理回车
}
};
五、状态管理(Redux Toolkit)
1.定义类型化的 Slice
TypeScript
// features/counter/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface Todo {
id:string;
text: string ;
value: number;
}
interface TodosState {
list: Todo[];
status: 'idle' | 'loading' | 'succeeded' | 'failef';
const initialState: TodosState = {
list: [];
status: 'idle'
};
const counterSlice = createSlice({
name: 'tods',
initialState,
reducers: {
addTodo: (state, action: PayloadAction<{ text: string }>) => {
const newTodo: Todo = {
id: Date.now().toString(),
text: action.payload.text,
completed: false
};
state.list.push(newTodo);
},
toggleTodo: (state, action: PayloadAction<string>) => {
const todo = state.list.find(t => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
}
},
extraReducers: (builder) => {
// 异步处理示例
builder
.addCase(fetchTodos.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchTodos.fulfilled, (state, action: PayloadAction<Todo[]>) => {
state.status = 'succeeded';
state.list = action.payload;
});
}
});
export const {addTodo, toggleTodo} = counterSlice.actions;
export default todoSlice.reducer;
2. 类型化的useSelector和useDispatch(组件中使用)
TypeScript
// Counter.tsx
import { useDispatch, useSelector } from 'react-redux';
import type { RootState } from '@/app/store';
import { increment } from '@/features/counter/counterSlice';
const Counter = () => {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+1</button>
</div>
);
};
六、路由(React Router v6)
TypeScript
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
interface RouteConfig {
path: string;
element: React.ReactNode;
}
const routes: RouteConfig[] = [
{ path: '/', element: <HomePage /> },
{ path: '/about', element: <AboutPage /> }
];
const App = () => (
<BrowserRouter>
<Routes>
{routes.map((route) => (
<Route key={route.path} {...route} />
))}
</Routes>
</BrowserRouter>
);
七、类型扩展与最佳实践
1. 扩展全局类型
TypeScript
// react-app-env.d.ts
declare namespace React {
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
// 扩展自定义属性
customAttr?: string;
}
}
2. 组件默认 Props
TypeScript
interface Props {
size?: 'small' | 'medium' | 'large';
}
const MyComponent = ({ size = 'medium' }: Props) => {
// ...
};
3. 泛型组件
TypeScript
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<div>
{items.map((item, index) => (
<div key={index}>{renderItem(item)}</div>
))}
</div>
);
}
// 使用
<List<{ id: string; name: string }>
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>
4.高阶组件(HOC)类型
TypeScript
type WithLoadingProps = {
isLoading: boolean;
};
// 高阶组件:为组件添加 loading 状态
function withLoading<T extends object>(
WrappedComponent: React.ComponentType<T>
) {
return (props: T & WithLoadingProps) => {
if (props.isLoading) return <div>Loading...</div>;
return <WrappedComponent {...props} />;
};
}
// 使用
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading isLoading={true} users={[]} />;
5.Context API类型安全
TypeScript
// ThemeContext.tsx
import React, { createContext, useContext } from 'react';
type Theme = 'light' | 'dark';
type ThemeContextType = {
theme: Theme;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<Theme>('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// 自定义 Hook 确保 Context 存在
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
};
八、测试与调试
1. Jest + Testing Library
TypeScript
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('handles click event', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
fireEvent.click(screen.getByText(/click me/i));
expect(handleClick).toHaveBeenCalledTimes(1);
});
// 测试异步操作
test('loads user data', async () => {
render(<UserProfile userId="1" />);
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});
2.类型安全的Mock数据
TypeScript
// mocks/user.ts
import { User } from '../types';
export const mockUser: User = {
id: '1',
name: 'Alice',
email: 'alice@example.com'
};
// 测试中使用
jest.mock('../api', () => ({
fetchUser: jest.fn().mockResolvedValue(mockUser)
}));
九、常见问题
1. 如何处理第三方库类型?
TypeScript
npm install @types/react-router-dom @types/lodash # 安装类型声明
# 安装社区维护的类型包
npm install @types/lodash @types/react-select --save-dev
# 临时忽略类型检查(不推荐)
// @ts-ignore
import untypedLib from 'untyped-lib';
2.处理动态导入(Lazy Loading )
TypeScript
// 明确组件类型
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// 使用 Suspense
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
3. 类型断言的使用
TypeScript
//例a
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value as string; // 明确类型
//例b
const element = document.getElementById('root') as HTMLElement; // 安全断言
const data = response as ApiResponse; // 明确知道类型时
3. 处理可选 Props
TypeScript
interface AvatarProps {
src: string;
alt?: string; // 可选属性
size?: number;
}
const Avatar = ({ src, alt = '', size = 40 }: AvatarProps) => (
<img src={src} alt={alt} width={size} />
);
4、性能优化
1.使用react.memo 优化渲染
TypeScript
interface MemoizedComponentProps {
data: string[];
}
const MemoizedComponent = React.memo<MemoizedComponentProps>(({ data }) => {
// 复杂计算
return <div>{data.join(', ')}</div>;
});
2.类型化的useCallback和useMemo
TypeScript
const memoizedCallback = useCallback(
(id: string) => {
console.log('Callback called with:', id);
},
[] // 依赖项数组
);
const memoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]); // 依赖项变化时重新计算
十一、学习资源
码字不易,各位大佬点点赞呗