React总结
React 完全指南:从入门到精通
1. React 简介
React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发并维护。它具有以下特点:
- 组件化:将 UI 拆分为独立可复用的组件
- 声明式:通过 JSX 描述 UI 状态
- 高效:虚拟 DOM 实现高性能渲染
- 单向数据流:数据自上而下流动
2. 环境搭建
2.1 使用 Create React App
npx create-react-app my-app
cd my-app
npm start
2.2 项目结构
my-app/
├── node_modules/
├── public/
│ └── index.html
├── src/
│ ├── App.css
│ ├── App.js
│ ├── index.css
│ └── index.js
├── package.json
└── README.md
3. 核心概念
3.1 JSX 语法
const element = <h1>Hello, world!</h1>;
3.2 组件
函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
3.3 Props 和 State
- Props:父组件传递给子组件的数据
- State:组件内部维护的状态
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
3.4 事件处理
function ActionButton() {
function handleClick() {
console.log('Button clicked');
}
return (
<button onClick={handleClick}>
Click Me
</button>
);
}
4. 高级特性
4.1 Hooks
useState
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
4.2 Context API
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button theme={theme}>I am styled by theme context!</button>;
}
4.3 React Router
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
5. 最佳实践
5.1 组件设计原则
- 单一职责原则
- 可复用性
- 可组合性
- 可测试性
5.2 性能优化
5.2.1 组件优化
-
React.memo
const MyComponent = React.memo(function MyComponent(props) { // 组件实现 });
-
PureComponent
class MyComponent extends React.PureComponent { render() { return <div>{this.props.value}</div>; } }
-
shouldComponentUpdate
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return nextProps.value !== this.props.value; } render() { return <div>{this.props.value}</div>; } }
5.2.2 Hooks优化
-
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
useCallback
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
-
useReducer
const [state, dispatch] = useReducer(reducer, initialState);
5.2.3 代码分割
-
React.lazy
const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <React.Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </React.Suspense> ); }
-
路由懒加载
const Home = React.lazy(() => import('./routes/Home')); const About = React.lazy(() => import('./routes/About')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> ); }
5.2.4 列表优化
-
虚拟列表
import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> );
-
窗口化渲染
import { VariableSizeList as List } from 'react-window'; const rowHeights = new Array(1000) .fill(true) .map(() => 25 + Math.round(Math.random() * 50)); const getItemSize = index => rowHeights[index]; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={getItemSize} width={300} > {Row} </List> );
5.2.5 渲染优化
-
批量更新
import { unstable_batchedUpdates } from 'react-dom'; function handleClick() { unstable_batchedUpdates(() => { setCount(c => c + 1); setFlag(f => !f); }); }
-
避免不必要的渲染
function ParentComponent() { const [count, setCount] = useState(0); return ( <> <button onClick={() => setCount(c => c + 1)}>Increment</button> <MemoizedChildComponent /> </> ); }
-
使用Fragment
function MyComponent() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> ); }
5.2.6 内存优化
-
清理副作用
useEffect(() => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source]);
-
避免内存泄漏
useEffect(() => { let isMounted = true; async function fetchData() { const result = await someAsyncOperation(); if (isMounted) { setData(result); } } fetchData(); return () => { isMounted = false; }; }, []);
-
使用WeakMap
const cache = new WeakMap(); function getCachedData(obj) { if (!cache.has(obj)) { const result = computeExpensiveValue(obj); cache.set(obj, result); } return cache.get(obj); }
5.2.7 网络优化
-
预加载资源
import { Prefetch } from 'react-static'; function MyComponent() { return ( <Prefetch path="/about"> {({ loading, loaded }) => ( <Link to="/about"> About {loading ? 'Loading...' : 'Loaded'} </Link> )} </Prefetch> ); }
-
数据预取
const { data } = useSWR('/api/data', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false });
-
资源优先级
<link rel="preload" href="critical.css" as="style"> <link rel="preload" href="main.js" as="script">
5.2.8 工具使用
-
React Profiler
import { Profiler } from 'react'; function onRenderCallback( id, phase, actualDuration, baseDuration, startTime, commitTime, interactions ) { console.log('Render time:', actualDuration); } function App() { return ( <Profiler id="App" onRender={onRenderCallback}> <MyComponent /> </Profiler> ); }
-
React DevTools
- 组件性能分析
- 状态调试
- 组件树检查
-
Lighthouse
- 性能评分
- 最佳实践检查
- 可访问性评估
5.3 测试策略
5.3.1 单元测试
-
组件测试
import { render, screen } from '@testing-library/react'; import Button from './Button'; test('renders button with correct text', () => { render(<Button>Click me</Button>); const buttonElement = screen.getByText(/click me/i); expect(buttonElement).toBeInTheDocument(); });
-
Hooks测试
import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from './useCounter'; test('should increment counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); });
-
快照测试
import renderer from 'react-test-renderer'; import Component from './Component'; test('matches snapshot', () => { const tree = renderer.create(<Component />).toJSON(); expect(tree).toMatchSnapshot(); });
5.3.2 集成测试
-
组件交互测试
import { render, screen, fireEvent } from '@testing-library/react'; import LoginForm from './LoginForm'; test('submits form with correct values', () => { const handleSubmit = jest.fn(); render(<LoginForm onSubmit={handleSubmit} />); fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password123' } }); fireEvent.click(screen.getByRole('button', { name: /submit/i })); expect(handleSubmit).toHaveBeenCalledWith({ username: 'testuser', password: 'password123' }); });
-
API集成测试
import { render, screen, waitFor } from '@testing-library/react'; import axios from 'axios'; import UserList from './UserList'; jest.mock('axios'); test('fetches and displays users', async () => { axios.get.mockResolvedValue({ data: [{ id: 1, name: 'John Doe' }] }); render(<UserList />); await waitFor(() => { expect(screen.getByText(/john doe/i)).toBeInTheDocument(); }); });
5.3.3 E2E测试
-
Cypress配置
// cypress.config.js module.exports = { e2e: { baseUrl: 'http://localhost:3000', supportFile: false } };
-
页面导航测试
describe('Navigation', () => { it('should navigate to about page', () => { cy.visit('/'); cy.get('a[href*="about"]').click(); cy.url().should('include', '/about'); cy.get('h1').contains('About Page'); }); });
-
表单提交测试
describe('Login Form', () => { it('should submit form', () => { cy.visit('/login'); cy.get('#username').type('testuser'); cy.get('#password').type('password123'); cy.get('form').submit(); cy.url().should('include', '/dashboard'); }); });
5.3.4 测试覆盖率
-
Jest配置
{ "collectCoverage": true, "coverageReporters": ["text", "lcov"], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } }
-
生成覆盖率报告
npm test -- --coverage
-
查看HTML报告
open coverage/lcov-report/index.html
5.3.5 测试最佳实践
-
测试金字塔
- 70% 单元测试
- 20% 集成测试
- 10% E2E测试
-
测试命名规范
- 描述性测试名称
- Given-When-Then模式
- 避免使用"should"
-
测试数据管理
- 使用工厂函数生成测试数据
- 避免硬编码
- 使用faker.js生成随机数据
-
测试隔离
- 每个测试独立运行
- 使用beforeEach/afterEach清理状态
- 避免测试间依赖
6. 生态系统
6.1 状态管理
Redux
-
核心概念
- Store:应用状态容器
- Action:状态变更描述
- Reducer:状态变更处理
- Middleware:扩展功能
-
示例代码
// store.js import { createStore } from 'redux'; function counterReducer(state = { value: 0 }, action) { switch (action.type) { case 'increment': return { value: state.value + 1 }; default: return state; } } const store = createStore(counterReducer); // Component.js import { useSelector, useDispatch } from 'react-redux'; function Counter() { const count = useSelector(state => state.value); const dispatch = useDispatch(); return ( <div> <span>{count}</span> <button onClick={() => dispatch({ type: 'increment' })}> Increment </button> </div> ); }
Recoil
-
核心概念
- Atom:状态单元
- Selector:派生状态
- Hooks:状态访问
-
示例代码
// state.js import { atom } from 'recoil'; export const countState = atom({ key: 'countState', default: 0, }); // Component.js import { useRecoilState } from 'recoil'; import { countState } from './state'; function Counter() { const [count, setCount] = useRecoilState(countState); return ( <div> <span>{count}</span> <button onClick={() => setCount(count + 1)}> Increment </button> </div> ); }
6.2 样式方案
Styled Components
-
基本用法
import styled from 'styled-components'; const Button = styled.button` background: ${props => props.primary ? 'palevioletred' : 'white'}; color: ${props => props.primary ? 'white' : 'palevioletred'}; font-size: 1em; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; `; function Example() { return ( <div> <Button>Normal</Button> <Button primary>Primary</Button> </div> ); }
-
主题支持
import { ThemeProvider } from 'styled-components'; const theme = { colors: { primary: 'palevioletred', secondary: 'white' } }; function App() { return ( <ThemeProvider theme={theme}> <Button>Styled</Button> </ThemeProvider> ); }
CSS Modules
-
基本用法
/* Button.module.css */ .button { background-color: white; color: palevioletred; font-size: 1em; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; }
import styles from './Button.module.css'; function Button() { return ( <button className={styles.button}> Click me </button> ); }
-
组合样式
.primary { background-color: palevioletred; color: white; }
function Button({ primary }) { return ( <button className={`${styles.button} ${primary && styles.primary}`}> Click me </button> ); }
6.3 表单处理
Formik
-
基本用法
import { Formik, Form, Field } from 'formik'; function LoginForm() { return ( <Formik initialValues={{ email: '', password: '' }} onSubmit={values => { console.log(values); }} > <Form> <Field name="email" type="email" /> <Field name="password" type="password" /> <button type="submit">Submit</button> </Form> </Formik> ); }
-
表单验证
function validate(values) { const errors = {}; if (!values.email) { errors.email = 'Required'; } else if ( !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email) ) { errors.email = 'Invalid email address'; } return errors; }
React Hook Form
-
基本用法
import { useForm } from 'react-hook-form'; function LoginForm() { const { register, handleSubmit } = useForm(); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email')} /> <input {...register('password')} type="password" /> <button type="submit">Submit</button> </form> ); }
-
表单验证
<input {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address' } })} />
6.4 数据获取
React Query
-
基本用法
import { useQuery } from 'react-query'; function UserList() { const { data, isLoading, error } = useQuery('users', () => fetch('/api/users').then(res => res.json()) ); if (isLoading) return 'Loading...'; if (error) return 'Error!'; return ( <ul> {data.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
-
数据缓存
const { data } = useQuery('users', fetchUsers, { staleTime: 1000 * 60 * 5, // 5 minutes cacheTime: 1000 * 60 * 10 // 10 minutes });
SWR
-
基本用法
import useSWR from 'swr'; function Profile() { const { data, error } = useSWR('/api/user', fetcher); if (error) return <div>failed to load</div>; if (!data) return <div>loading...</div>; return <div>hello {data.name}!</div>; }
-
自动重试
const { data } = useSWR('/api/user', fetcher, { onErrorRetry: (error, key, config, revalidate, { retryCount }) => { if (error.status === 404) return; if (retryCount >= 10) return; setTimeout(() => revalidate({ retryCount }), 5000); } });
6.5 UI 库
Material-UI
-
基本用法
import { Button, TextField } from '@mui/material'; function Form() { return ( <form> <TextField label="Email" variant="outlined" /> <TextField label="Password" type="password" variant="outlined" /> <Button variant="contained" color="primary"> Submit </Button> </form> ); }
-
主题定制
import { createTheme, ThemeProvider } from '@mui/material/styles'; const theme = createTheme({ palette: { primary: { main: '#1976d2', }, }, }); function App() { return ( <ThemeProvider theme={theme}> <Form /> </ThemeProvider> ); }
Ant Design
-
基本用法
import { Button, Input } from 'antd'; function Form() { return ( <form> <Input placeholder="Email" /> <Input.Password placeholder="Password" /> <Button type="primary">Submit</Button> </form> ); }
-
主题定制
import { ConfigProvider } from 'antd'; function App() { return ( <ConfigProvider theme={{ token: { colorPrimary: '#00b96b', }, }} > <Form /> </ConfigProvider> ); }
7. 学习资源
- 官方文档:https://reactjs.org/
- React 中文文档:https://zh-hans.reactjs.org/
- React 入门教程:https://react.iamkasong.com/
- React 进阶指南:https://react.iamkasong.com/advanced/
- React 源码解析:https://react.iamkasong.com/implementation/
8. React 18 新特性
8.1 并发渲染(Concurrent Rendering)
- 可中断渲染:允许React在渲染过程中暂停和恢复
- 自动批处理:自动合并多个状态更新
- 过渡更新:区分紧急和非紧急更新
import { startTransition } from 'react';
// 紧急更新
setInputValue(input);
// 非紧急更新
startTransition(() => {
setSearchQuery(input);
});
8.2 新的Hooks
- useId:生成唯一ID
- useSyncExternalStore:与外部存储同步
- useInsertionEffect:用于CSS-in-JS库
8.3 服务端渲染改进
- 流式SSR:支持渐进式HTML流
- 选择性水合:优先水合重要部分
8.4 其他改进
- 新的根API:createRoot
- 严格模式增强:检测不安全的副作用
- 改进的错误处理:自动恢复错误边界
9. Server Components
9.1 概念与优势
- 在服务端渲染组件
- 减少客户端包体积
- 直接访问后端服务
- 自动代码分割
9.2 使用场景
- 数据获取密集型组件
- 静态内容
- 大型依赖组件
- 安全性要求高的组件
9.3 示例代码
// Note.server.js
import db from 'db.server';
function Note({id}) {
const note = db.notes.get(id);
return <NoteView note={note} />;
}
// NoteView.client.js
'use client';
function NoteView({note}) {
return (
<div>
<h1>{note.title}</h1>
<p>{note.content}</p>
</div>
);
}
9.4 注意事项
- 客户端交互限制
- 状态管理方式
- 网络延迟影响
- 开发环境配置
10. 状态管理库对比
10.1 Redux
- 优点:
- 成熟的生态系统
- 强大的中间件支持
- 时间旅行调试
- 严格的单向数据流
- 缺点:
- 样板代码较多
- 学习曲线较陡
- 可能过度设计简单场景
10.2 Recoil
- 优点:
- 原生支持React
- 细粒度状态管理
- 异步数据支持
- 更简单的API
- 缺点:
- 相对较新
- 生态系统不够成熟
- 调试工具有限
10.3 Zustand
- 优点:
- 极简API
- 高性能
- 支持中间件
- 易于集成
- 缺点:
- 功能相对基础
- 社区支持较少
- 缺少内置异步处理
10.4 选择建议
场景 | 推荐方案 |
---|---|
大型复杂应用 | Redux |
中小型应用 | Recoil |
简单应用 | Zustand |
需要时间旅行调试 | Redux |
需要细粒度更新 | Recoil |
追求极简实现 | Zustand |
11. 常见问题与解决方案
11.1 性能问题
- 问题:组件频繁重新渲染
- 解决方案:使用React.memo、useMemo、useCallback优化
- 问题:长列表渲染卡顿
- 解决方案:使用虚拟列表库(如react-window)
11.2 状态管理
- 问题:组件间状态共享困难
- 解决方案:使用Context API或状态管理库
- 问题:状态更新导致意外渲染
- 解决方案:检查依赖数组,使用useEffect正确管理副作用
11.3 路由问题
- 问题:页面刷新后404
- 解决方案:配置服务器支持HTML5 History API
- 问题:路由切换时数据丢失
- 解决方案:使用状态管理或路由缓存
11.4 样式冲突
- 问题:全局样式污染
- 解决方案:使用CSS Modules或CSS-in-JS
- 问题:第三方组件样式覆盖
- 解决方案:使用CSS命名空间或样式优先级调整
11.5 开发环境
- 问题:热更新失效
- 解决方案:检查webpack配置,确保HMR启用
- 问题:TypeScript类型错误
- 解决方案:安装正确的类型定义文件(@types包)
12. 项目架构设计
12.1 分层架构
展示层(Presentation Layer)
-
职责
- UI展示
- 用户交互
- 数据展示
- 样式管理
-
技术栈
- React组件
- CSS-in-JS
- UI库(如Material-UI)
- 动画库(如Framer Motion)
-
示例代码
// components/Button.jsx import styled from 'styled-components'; const StyledButton = styled.button` background: ${props => props.primary ? 'blue' : 'white'}; color: ${props => props.primary ? 'white' : 'blue'}; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; `; function Button({ primary, children }) { return <StyledButton primary={primary}>{children}</StyledButton>; }
业务逻辑层(Business Logic Layer)
-
职责
- 业务规则处理
- 数据转换
- 状态管理
- 业务逻辑复用
-
技术栈
- 自定义Hooks
- 服务类
- 状态管理库(如Redux、Recoil)
- 数据验证库(如Yup)
-
示例代码
// hooks/useForm.js import { useState } from 'react'; export function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (e) => { const { name, value } = e.target; setValues({ ...values, [name]: value }); }; return { values, handleChange }; }
数据访问层(Data Access Layer)
-
职责
- API调用
- 数据缓存
- 错误处理
- 数据转换
-
技术栈
- Axios
- React Query
- GraphQL客户端(如Apollo)
- WebSocket
-
示例代码
// services/api.js import axios from 'axios'; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json' } }); export const getUsers = async () => { try { const response = await api.get('/users'); return response.data; } catch (error) { throw new Error(error.response?.data?.message || 'Failed to fetch users'); } };
12.2 目录结构
基础结构
src/
├── assets/ # 静态资源
│ ├── images/ # 图片资源
│ ├── fonts/ # 字体文件
│ └── styles/ # 全局样式
├── components/ # 通用组件
│ ├── ui/ # UI组件
│ ├── layout/ # 布局组件
│ └── shared/ # 共享组件
├── features/ # 功能模块
│ └── featureName/
│ ├── api/ # API接口
│ ├── hooks/ # 自定义Hooks
│ ├── store/ # 状态管理
│ ├── types/ # 类型定义
│ └── views/ # 页面组件
├── lib/ # 工具函数
│ ├── utils/ # 通用工具
│ ├── constants/ # 常量定义
│ └── helpers/ # 辅助函数
├── pages/ # 页面路由
│ ├── Home/ # 首页
│ ├── About/ # 关于页
│ └── NotFound/ # 404页
├── services/ # API服务
│ ├── api.js # API配置
│ ├── auth.js # 认证服务
│ └── storage.js # 存储服务
├── store/ # 全局状态
│ ├── slices/ # Redux切片
│ ├── actions/ # Redux动作
│ └── reducers/ # Redux reducer
├── styles/ # 全局样式
│ ├── theme.js # 主题配置
│ ├── global.css # 全局样式
│ └── variables.css # CSS变量
└── utils/ # 工具函数
├── validators/ # 验证工具
├── formatters/ # 格式化工具
└── hooks/ # 自定义Hooks
模块化结构
src/
├── modules/
│ ├── auth/ # 认证模块
│ │ ├── api/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── store/
│ │ └── views/
│ ├── user/ # 用户模块
│ │ ├── api/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── store/
│ │ └── views/
│ └── product/ # 产品模块
│ ├── api/
│ ├── components/
│ ├── hooks/
│ ├── store/
│ └── views/
12.3 模块化设计
模块划分原则
-
按业务功能划分
- 用户管理
- 用户注册/登录
- 用户信息管理
- 权限控制
- 订单管理
- 订单创建
- 订单查询
- 订单状态管理
- 产品管理
- 产品展示
- 产品分类
- 产品搜索
- 权限管理
- 角色管理
- 权限分配
- 访问控制
- 用户管理
-
按技术特性划分
- 认证模块
- 登录/注册
- 会话管理
- 权限验证
- 支付模块
- 支付接口
- 支付状态管理
- 支付回调处理
- 通知模块
- 消息推送
- 邮件通知
- 站内信
- 日志模块
- 操作日志
- 错误日志
- 访问日志
- 认证模块
-
模块间通信
- 通过props传递数据
// ParentComponent.jsx function ParentComponent() { const [data, setData] = useState(null); return <ChildComponent data={data} onDataChange={setData} />; } // ChildComponent.jsx function ChildComponent({ data, onDataChange }) { return ( <input value={data || ''} onChange={(e) => onDataChange(e.target.value)} /> ); }
- 使用Context API共享状态
// ThemeContext.js const ThemeContext = createContext(); function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); } // ThemedButton.jsx function ThemedButton() { const { theme, setTheme } = useContext(ThemeContext); return ( <button style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }} onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} > Toggle Theme </button> ); }
- 通过事件总线通信
// eventBus.js class EventBus { constructor() { this.events = {}; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); } emit(event, ...args) { if (this.events[event]) { this.events[event].forEach(callback => callback(...args)); } } } export default new EventBus();
- 使用状态管理库
// store.js import { configureStore } from '@reduxjs/toolkit'; import authReducer from './authSlice'; import userReducer from './userSlice'; export default configureStore({ reducer: { auth: authReducer, user: userReducer } });
- 通过props传递数据
模块接口设计
-
组件接口
// modules/auth/components/LoginForm.jsx import PropTypes from 'prop-types'; function LoginForm({ onSubmit, loading, error }) { return ( <form onSubmit={onSubmit}> {error && <div className="error">{error}</div>} <input name="email" type="email" required /> <input name="password" type="password" required /> <button type="submit" disabled={loading}> {loading ? 'Loading...' : 'Login'} </button> </form> ); } LoginForm.propTypes = { onSubmit: PropTypes.func.isRequired, loading: PropTypes.bool, error: PropTypes.string }; LoginForm.defaultProps = { loading: false, error: null };
-
API接口
// modules/auth/api/auth.js import axios from 'axios'; const API_BASE_URL = process.env.REACT_APP_API_URL; export const login = async (credentials) => { try { const response = await axios.post(`${API_BASE_URL}/auth/login`, credentials); return response.data; } catch (error) { throw new Error(error.response?.data?.message || 'Login failed'); } }; export const register = async (userData) => { try { const response = await axios.post(`${API_BASE_URL}/auth/register`, userData); return response.data; } catch (error) { throw new Error(error.response?.data?.message || 'Registration failed'); } }; export const logout = async () => { try { await axios.post(`${API_BASE_URL}/auth/logout`); } catch (error) { throw new Error(error.response?.data?.message || 'Logout failed'); } };
-
状态接口
// modules/auth/store/authSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { user: null, loading: false, error: null }; const authSlice = createSlice({ name: 'auth', initialState, reducers: { loginStart(state) { state.loading = true; state.error = null; }, loginSuccess(state, action) { state.user = action.payload; state.loading = false; }, loginFailure(state, action) { state.error = action.payload; state.loading = false; }, logoutSuccess(state) { state.user = null; } } }); export const { loginStart, loginSuccess, loginFailure, logoutSuccess } = authSlice.actions; export const login = (credentials) => async (dispatch) => { try { dispatch(loginStart()); const user = await loginAPI(credentials); dispatch(loginSuccess(user)); } catch (error) { dispatch(loginFailure(error.message)); } }; export const logout = () => async (dispatch) => { try { await logoutAPI(); dispatch(logoutSuccess()); } catch (error) { console.error('Logout error:', error); } }; export default authSlice.reducer;
-
类型接口(TypeScript)
// modules/auth/types.ts export interface User { id: string; name: string; email: string; role: string; } export interface AuthState { user: User | null; loading: boolean; error: string | null; } export interface LoginCredentials { email: string; password: string; } export interface RegisterData extends LoginCredentials { name: string; }
模块依赖管理
-
模块间依赖
- 使用依赖注入
// modules/auth/authService.js class AuthService { constructor(apiClient) { this.apiClient = apiClient; } async login(credentials) { return this.apiClient.post('/auth/login', credentials); } } // main.js const apiClient = new ApiClient(); const authService = new AuthService(apiClient);
- 使用依赖注入
-
模块版本控制
- 使用语义化版本
{ "dependencies": { "@modules/auth": "^1.2.0", "@modules/user": "~2.0.1" } }
- 使用语义化版本
-
模块更新策略
- 向后兼容
- 版本迁移指南
- 废弃警告
模块测试策略
-
单元测试
// modules/auth/authSlice.test.js import authReducer, { loginStart, loginSuccess, loginFailure } from './authSlice'; describe('auth reducer', () => { it('should handle initial state', () => { expect(authReducer(undefined, {})).toEqual({ user: null, loading: false, error: null }); }); it('should handle loginStart', () => { expect( authReducer(undefined, loginStart()) ).toEqual({ user: null, loading: true, error: null }); }); });
-
集成测试
// modules/auth/authService.test.js import AuthService from './authService'; import MockAdapter from 'axios-mock-adapter'; import axios from 'axios'; describe('AuthService', () => { let mockAxios; let authService; beforeEach(() => { mockAxios = new MockAdapter(axios); authService = new AuthService(axios); }); it('should login successfully', async () => { const credentials = { email: 'test@example.com', password: 'password' }; const responseData = { token: 'abc123' }; mockAxios.onPost('/auth/login').reply(200, responseData); const result = await authService.login(credentials); expect(result.data).toEqual(responseData); }); });
-
E2E测试
// cypress/integration/auth.spec.js describe('Authentication', () => { it('should login successfully', () => { cy.visit('/login'); cy.get('[name="email"]').type('test@example.com'); cy.get('[name="password"]').type('password'); cy.get('form').submit(); cy.url().should('include', '/dashboard'); }); });
模块文档规范
-
README模板
# Auth Module ## 功能描述 - 用户登录/注册 - 会话管理 - 权限验证 ## 安装 ```bash npm install @modules/auth
使用示例
import { login } from '@modules/auth'; const credentials = { email: 'test@example.com', password: 'password123' }; login(credentials) .then(user => console.log('Logged in:', user)) .catch(error => console.error('Login failed:', error));
API 文档
login(credentials)
register(userData)
logout()
依赖
- axios
- redux
-
API文档生成
- 使用JSDoc
/** * 用户登录 * @param {Object} credentials - 登录凭证 * @param {string} credentials.email - 用户邮箱 * @param {string} credentials.password - 用户密码 * @returns {Promise<User>} 返回用户信息 * @throws {Error} 登录失败时抛出错误 */ export async function login(credentials) { // ... }
- 使用JSDoc
-
变更日志
# Changelog ## [1.2.0] - 2023-03-15 ### Added - 新增第三方登录支持 ### Changed - 优化登录错误提示 ### Fixed - 修复会话过期问题
模块发布流程
-
版本管理
npm version patch npm version minor npm version major
-
发布到私有仓库
npm publish --access restricted
-
自动更新
{ "scripts": { "postpublish": "git push && git push --tags" } }
模块最佳实践
-
单一职责原则
- 每个模块只负责一个功能
- 避免过度耦合
-
接口最小化
- 只暴露必要的接口
- 使用私有方法
-
依赖管理
- 最小化外部依赖
- 使用peerDependencies
-
性能优化
- 懒加载模块
- 代码分割
-
安全性
- 输入验证
- 权限控制
- 错误处理
12.4 性能优化策略
代码分割
-
动态导入
const LazyComponent = React.lazy(() => import('./LazyComponent')); function App() { return ( <React.Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </React.Suspense> ); }
-
路由懒加载
const Home = React.lazy(() => import('./pages/Home')); const About = React.lazy(() => import('./pages/About')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> ); }
-
组件级代码分割
const HeavyComponent = React.lazy(() => import('./HeavyComponent').then(module => ({ default: module.HeavyComponent })) );
-
预加载策略
const preloadComponent = (component) => { const Component = React.lazy(component); Component.preload(); }; // 鼠标悬停时预加载 <div onMouseEnter={() => preloadComponent(() => import('./HeavyComponent'))}> Hover to preload </div>
资源优化
-
图片优化
- 使用WebP格式
<picture> <source srcSet="image.webp" type="image/webp" /> <source srcSet="image.jpg" type="image/jpeg" /> <img src="image.jpg" alt="Example" /> </picture>
- 响应式图片
<img srcSet="image-320w.jpg 320w, image-480w.jpg 480w, image-800w.jpg 800w" sizes="(max-width: 320px) 280px, (max-width: 480px) 440px, 800px" src="image-800w.jpg" alt="Example" />
- 图片懒加载
<img src="placeholder.jpg" data-src="real-image.jpg" className="lazyload" alt="Example" />
- 使用WebP格式
-
字体优化
- 字体子集化
@font-face { font-family: 'CustomFont'; src: url('/fonts/custom-subset.woff2') format('woff2'), url('/fonts/custom-subset.woff') format('woff'); unicode-range: U+000-5FF; /* 仅包含常用字符 */ }
- 字体预加载
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin />
- 字体显示策略
@font-face { font-display: swap; /* 使用系统字体直到自定义字体加载完成 */ }
- 字体子集化
-
资源压缩
- Webpack配置示例
module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { compress: true, mangle: true } }), new CssMinimizerPlugin() ] } };
- Webpack配置示例
缓存策略
-
Service Worker
- 缓存策略配置
// service-worker.js const CACHE_NAME = 'v1'; const ASSETS = [ '/', '/index.html', '/main.js', '/style.css' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(ASSETS)) ); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });
- 缓存更新策略
self.addEventListener('activate', (event) => { const cacheWhitelist = [CACHE_NAME]; event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (!cacheWhitelist.includes(cacheName)) { return caches.delete(cacheName); } }) ); }) ); });
- 缓存策略配置
-
HTTP缓存
- Nginx配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Vary "Accept-Encoding"; }
- ETag配置
etag on;
- Nginx配置
-
数据缓存
- React Query缓存配置
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5分钟 cacheTime: 1000 * 60 * 10 // 10分钟 } } });
- React Query缓存配置
渲染优化
-
虚拟列表
- 固定高度列表
import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> );
- 可变高度列表
import { VariableSizeList as List } from 'react-window'; const rowHeights = new Array(1000) .fill(true) .map(() => 25 + Math.round(Math.random() * 50)); const getItemSize = index => rowHeights[index]; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={getItemSize} width={300} > {Row} </List> );
- 固定高度列表
-
批量更新
- React 18自动批处理
function handleClick() { setCount(c => c + 1); setFlag(f => !f); // 自动批处理 }
- 手动批处理
import { unstable_batchedUpdates } from 'react-dom'; function handleClick() { unstable_batchedUpdates(() => { setCount(c => c + 1); setFlag(f => !f); }); }
- React 18自动批处理
-
渲染优先级
- 使用startTransition
import { startTransition } from 'react'; function handleInputChange(value) { setInputValue(value); // 紧急更新 startTransition(() => { setSearchQuery(value); // 非紧急更新 }); }
- 使用startTransition
-
避免不必要的渲染
- 使用React.memo
const MemoizedComponent = React.memo(function Component({ data }) { return <div>{data}</div>; });
- 使用useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 使用useCallback
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
- 使用React.memo
网络优化
-
HTTP/2配置
- Nginx配置
server { listen 443 ssl http2; http2_push /style.css; http2_push /app.js; }
- Nginx配置
-
资源预加载
- 关键资源预加载
<link rel="preload" href="critical.css" as="style"> <link rel="preload" href="main.js" as="script">
- 数据预取
const { data } = useSWR('/api/data', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false });
- 关键资源预加载
-
服务端推送
- 使用HTTP/2 Server Push
- 缓存策略优化
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; }
调试技巧
-
React Profiler
- 性能分析
<Profiler id="App" onRender={onRenderCallback}> <App /> </Profiler>
- 自定义回调
function onRenderCallback( id, phase, actualDuration, baseDuration, startTime, commitTime, interactions ) { console.log('Render time:', actualDuration); }
- 性能分析
-
Chrome DevTools
- 性能面板
- 内存面板
- 网络面板
-
Lighthouse
- 性能评分
- 最佳实践检查
- 可访问性评估
性能监控
-
Web Vitals
- 核心指标监控
import { getCLS, getFID, getLCP } from 'web-vitals'; getCLS(console.log); getFID(console.log); getLCP(console.log);
- 自定义上报
function sendToAnalytics(metric) { const body = JSON.stringify(metric); navigator.sendBeacon('/analytics', body); } getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics);
- 核心指标监控
-
错误监控
- 错误边界
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { logErrorToService(error, errorInfo); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } }
- 全局错误捕获
window.addEventListener('error', (event) => { logErrorToService(event.error); }); window.addEventListener('unhandledrejection', (event) => { logErrorToService(event.reason); });
- 错误边界
性能优化最佳实践
-
关键渲染路径优化
- 最小化关键资源
- 减少关键请求数量
- 优化关键字节数
-
资源加载策略
- 预加载关键资源
- 延迟加载非关键资源
- 按需加载第三方库
-
渲染性能优化
- 避免不必要的重新渲染
- 使用虚拟列表
- 优化复杂组件
-
网络性能优化
- 使用HTTP/2
- 启用Gzip压缩
- 使用CDN加速
-
内存管理
- 清理未使用的引用
- 避免内存泄漏
- 使用WeakMap/WeakSet
-
工具使用
- React DevTools
- Chrome DevTools
- Lighthouse
- Webpack Bundle Analyzer
12.5 测试策略
测试金字塔
-
单元测试
- 测试单个组件或函数
- 快速执行
- 高覆盖率
-
集成测试
- 测试组件间交互
- 验证数据流
- 确保模块协同工作
-
E2E测试
- 模拟用户操作
- 验证完整功能
- 确保用户体验
单元测试
-
组件测试
- 渲染测试
import { render, screen } from '@testing-library/react'; import Button from './Button'; test('renders button with correct text', () => { render(<Button>Click me</Button>); const buttonElement = screen.getByText(/click me/i); expect(buttonElement).toBeInTheDocument(); });
- 交互测试
test('calls onClick handler when clicked', () => { const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click me</Button>); fireEvent.click(screen.getByRole('button')); expect(handleClick).toHaveBeenCalledTimes(1); });
- 渲染测试
-
Hooks测试
- 状态测试
import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from './useCounter'; test('should increment counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); });
- 副作用测试
test('should update document title', () => { const { result } = renderHook(() => useDocumentTitle('Test Title')); expect(document.title).toBe('Test Title'); });
- 状态测试
-
快照测试
- 组件结构验证
import renderer from 'react-test-renderer'; import Component from './Component'; test('matches snapshot', () => { const tree = renderer.create(<Component />).toJSON(); expect(tree).toMatchSnapshot(); });
- 组件结构验证
集成测试
-
组件交互测试
- 表单提交测试
import { render, screen, fireEvent } from '@testing-library/react'; import LoginForm from './LoginForm'; test('submits form with correct values', () => { const handleSubmit = jest.fn(); render(<LoginForm onSubmit={handleSubmit} />); fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password123' } }); fireEvent.click(screen.getByRole('button', { name: /submit/i })); expect(handleSubmit).toHaveBeenCalledWith({ username: 'testuser', password: 'password123' }); });
- 表单提交测试
-
API集成测试
- 模拟API请求
import { render, screen, waitFor } from '@testing-library/react'; import axios from 'axios'; import UserList from './UserList'; jest.mock('axios'); test('fetches and displays users', async () => { axios.get.mockResolvedValue({ data: [{ id: 1, name: 'John Doe' }] }); render(<UserList />); await waitFor(() => { expect(screen.getByText(/john doe/i)).toBeInTheDocument(); }); });
- 模拟API请求
-
状态管理测试
- Redux测试
import configureStore from 'redux-mock-store'; import { Provider } from 'react-redux'; import { render } from '@testing-library/react'; import Component from './Component'; const mockStore = configureStore([]); test('renders with initial state', () => { const store = mockStore({ user: { name: 'John' } }); render( <Provider store={store}> <Component /> </Provider> ); expect(screen.getByText(/john/i)).toBeInTheDocument(); });
- Redux测试
E2E测试
-
Cypress配置
- 基本配置
// cypress.config.js module.exports = { e2e: { baseUrl: 'http://localhost:3000', supportFile: false, viewportWidth: 1280, viewportHeight: 800 } };
- 环境变量
{ "env": { "API_URL": "http://localhost:4000", "AUTH_TOKEN": "test-token" } }
- 基本配置
-
页面导航测试
- 路由测试
describe('Navigation', () => { it('should navigate to about page', () => { cy.visit('/'); cy.get('a[href*="about"]').click(); cy.url().should('include', '/about'); cy.get('h1').contains('About Page'); }); });
- 路由测试
-
表单提交测试
- 完整流程测试
describe('Login Form', () => { it('should submit form and redirect to dashboard', () => { cy.visit('/login'); cy.get('#username').type('testuser'); cy.get('#password').type('password123'); cy.get('form').submit(); cy.url().should('include', '/dashboard'); cy.get('.welcome-message').should('contain', 'Welcome testuser'); }); });
- 完整流程测试
-
API交互测试
- 网络请求测试
describe('API Interaction', () => { it('should fetch data and display results', () => { cy.intercept('GET', '/api/data', { fixture: 'data.json' }).as('getData'); cy.visit('/data'); cy.wait('@getData'); cy.get('.data-item').should('have.length', 5); }); });
- 网络请求测试
测试覆盖率
-
Jest配置
- 覆盖率配置
{ "collectCoverage": true, "coverageReporters": ["text", "lcov"], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } }
- 忽略文件
{ "coveragePathIgnorePatterns": [ "/node_modules/", "/tests/", "/mocks/" ] }
- 覆盖率配置
-
生成报告
- 命令行生成
npm test -- --coverage
- CI集成
- name: Run tests run: npm test -- --coverage - name: Upload coverage uses: codecov/codecov-action@v3
- 命令行生成
-
报告分析
- 查看HTML报告
open coverage/lcov-report/index.html
- 代码质量指标
- 分支覆盖率
- 函数覆盖率
- 行覆盖率
- 语句覆盖率
- 查看HTML报告
测试最佳实践
-
测试命名规范
- 描述性命名
test('should display error message when form is submitted empty')
- Given-When-Then模式
describe('when form is submitted', () => { it('should display error message', () => { // Given render(<Form />); // When fireEvent.click(screen.getByRole('button')); // Then expect(screen.getByText(/required/i)).toBeInTheDocument(); }); });
- 描述性命名
-
测试数据管理
- 使用工厂函数
const createUser = (overrides = {}) => ({ id: 1, name: 'John Doe', email: 'john@example.com', ...overrides });
- 使用faker.js
import { faker } from '@faker-js/faker'; const mockUser = { id: faker.datatype.number(), name: faker.name.fullName(), email: faker.internet.email() };
- 使用工厂函数
-
测试隔离
- 独立测试环境
beforeEach(() => { jest.resetAllMocks(); cleanup(); });
- 避免测试间依赖
test('should increment counter', () => { const { result } = renderHook(() => useCounter()); act(() => result.current.increment()); expect(result.current.count).toBe(1); }); test('should decrement counter', () => { const { result } = renderHook(() => useCounter()); act(() => result.current.decrement()); expect(result.current.count).toBe(-1); });
- 独立测试环境
-
测试性能优化
- 并行执行
{ "jest": { "maxWorkers": "50%" } }
- 测试缓存
{ "cache": true, "cacheDirectory": "/tmp/jest_cache" }
- 并行执行
测试工具集成
-
持续集成
- GitHub Actions配置
name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 - run: npm install - run: npm test
- GitHub Actions配置
-
代码质量检查
- ESLint集成
{ "scripts": { "lint": "eslint 'src/**/*.{js,jsx}'" } }
- Prettier集成
{ "scripts": { "format": "prettier --write 'src/**/*.{js,jsx}'" } }
- ESLint集成
-
测试报告
- JUnit报告
{ "reporters": [ "default", ["jest-junit", { outputDirectory: "test-results" }] ] }
- HTML报告
{ "reporters": [ "default", ["jest-html-reporter", { outputPath: "test-report.html" }] ] }
- JUnit报告
测试策略优化
-
测试优先级
- 核心功能优先
- 高风险模块优先
- 频繁变更模块优先
-
测试自动化
- 持续集成
- 自动部署
- 监控告警
-
测试维护
- 定期重构测试
- 更新测试数据
- 优化测试性能
测试驱动开发(TDD)
-
开发流程
- 编写失败测试
- 实现最小功能
- 重构代码
-
示例
// 1. 编写测试 test('should add two numbers', () => { expect(add(1, 2)).toBe(3); }); // 2. 实现功能 function add(a, b) { return a + b; } // 3. 重构优化 function add(...numbers) { return numbers.reduce((sum, num) => sum + num, 0); }
-
优势
- 提高代码质量
- 减少回归问题
- 促进模块化设计
行为驱动开发(BDD)
-
开发流程
- 定义用户故事
- 编写场景测试
- 实现功能
-
示例
Feature: Login Scenario: Successful login Given I am on the login page When I enter valid credentials Then I should be redirected to dashboard
-
工具支持
- Cucumber.js
- Jest-cucumber
- Cypress-cucumber-preprocessor
测试监控
-
测试结果监控
- 失败率
- 执行时间
- 覆盖率趋势
-
告警机制
- 测试失败告警
- 覆盖率下降告警
- 性能下降告警
-
可视化报告
- 测试趋势图
- 覆盖率图表
- 性能指标
测试环境管理
-
环境隔离
- 开发环境
- 测试环境
- 生产环境
-
环境配置
- 环境变量
- 配置文件
- 数据库隔离
-
环境切换
- 自动化部署
- 环境验证
- 回滚机制
测试数据管理
- 数据隔离
- 独立数据库
- 数据清理
React 完全指南:从入门到精通
1. React 简介
React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发并维护。它具有以下特点:
- 组件化:将 UI 拆分为独立可复用的组件
- 声明式:通过 JSX 描述 UI 状态
- 高效:虚拟 DOM 实现高性能渲染
- 单向数据流:数据自上而下流动
2. 环境搭建
2.1 使用 Create React App
npx create-react-app my-app
cd my-app
npm start
2.2 项目结构
my-app/
├── node_modules/
├── public/
│ └── index.html
├── src/
│ ├── App.css
│ ├── App.js
│ ├── index.css
│ └── index.js
├── package.json
└── README.md
3. 核心概念
3.1 JSX 语法
const element = <h1>Hello, world!</h1>;
3.2 组件
函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
3.3 Props 和 State
- Props:父组件传递给子组件的数据
- State:组件内部维护的状态
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
3.4 事件处理
function ActionButton() {
function handleClick() {
console.log('Button clicked');
}
return (
<button onClick={handleClick}>
Click Me
</button>
);
}
4. 高级特性
4.1 Hooks
useState
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
4.2 Context API
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button theme={theme}>I am styled by theme context!</button>;
}
4.3 React Router
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
5. 最佳实践
5.1 组件设计原则
- 单一职责原则
- 可复用性
- 可组合性
- 可测试性
5.2 性能优化
5.2.1 组件优化
-
React.memo
const MyComponent = React.memo(function MyComponent(props) { // 组件实现 });
-
PureComponent
class MyComponent extends React.PureComponent { render() { return <div>{this.props.value}</div>; } }
-
shouldComponentUpdate
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return nextProps.value !== this.props.value; } render() { return <div>{this.props.value}</div>; } }
5.2.2 Hooks优化
-
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
useCallback
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
-
useReducer
const [state, dispatch] = useReducer(reducer, initialState);
5.2.3 代码分割
-
React.lazy
const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <React.Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </React.Suspense> ); }
-
路由懒加载
const Home = React.lazy(() => import('./routes/Home')); const About = React.lazy(() => import('./routes/About')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> ); }
5.2.4 列表优化
-
虚拟列表
import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> );
-
窗口化渲染
import { VariableSizeList as List } from 'react-window'; const rowHeights = new Array(1000) .fill(true) .map(() => 25 + Math.round(Math.random() * 50)); const getItemSize = index => rowHeights[index]; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={getItemSize} width={300} > {Row} </List> );
5.2.5 渲染优化
-
批量更新
import { unstable_batchedUpdates } from 'react-dom'; function handleClick() { unstable_batchedUpdates(() => { setCount(c => c + 1); setFlag(f => !f); }); }
-
避免不必要的渲染
function ParentComponent() { const [count, setCount] = useState(0); return ( <> <button onClick={() => setCount(c => c + 1)}>Increment</button> <MemoizedChildComponent /> </> ); }
-
使用Fragment
function MyComponent() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> ); }
5.2.6 内存优化
-
清理副作用
useEffect(() => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source]);
-
避免内存泄漏
useEffect(() => { let isMounted = true; async function fetchData() { const result = await someAsyncOperation(); if (isMounted) { setData(result); } } fetchData(); return () => { isMounted = false; }; }, []);
-
使用WeakMap
const cache = new WeakMap(); function getCachedData(obj) { if (!cache.has(obj)) { const result = computeExpensiveValue(obj); cache.set(obj, result); } return cache.get(obj); }
5.2.7 网络优化
-
预加载资源
import { Prefetch } from 'react-static'; function MyComponent() { return ( <Prefetch path="/about"> {({ loading, loaded }) => ( <Link to="/about"> About {loading ? 'Loading...' : 'Loaded'} </Link> )} </Prefetch> ); }
-
数据预取
const { data } = useSWR('/api/data', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false });
-
资源优先级
<link rel="preload" href="critical.css" as="style"> <link rel="preload" href="main.js" as="script">
5.2.8 工具使用
-
React Profiler
import { Profiler } from 'react'; function onRenderCallback( id, phase, actualDuration, baseDuration, startTime, commitTime, interactions ) { console.log('Render time:', actualDuration); } function App() { return ( <Profiler id="App" onRender={onRenderCallback}> <MyComponent /> </Profiler> ); }
-
React DevTools
- 组件性能分析
- 状态调试
- 组件树检查
-
Lighthouse
- 性能评分
- 最佳实践检查
- 可访问性评估
5.3 测试策略
5.3.1 单元测试
-
组件测试
import { render, screen } from '@testing-library/react'; import Button from './Button'; test('renders button with correct text', () => { render(<Button>Click me</Button>); const buttonElement = screen.getByText(/click me/i); expect(buttonElement).toBeInTheDocument(); });
-
Hooks测试
import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from './useCounter'; test('should increment counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); });
-
快照测试
import renderer from 'react-test-renderer'; import Component from './Component'; test('matches snapshot', () => { const tree = renderer.create(<Component />).toJSON(); expect(tree).toMatchSnapshot(); });
5.3.2 集成测试
-
组件交互测试
import { render, screen, fireEvent } from '@testing-library/react'; import LoginForm from './LoginForm'; test('submits form with correct values', () => { const handleSubmit = jest.fn(); render(<LoginForm onSubmit={handleSubmit} />); fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password123' } }); fireEvent.click(screen.getByRole('button', { name: /submit/i })); expect(handleSubmit).toHaveBeenCalledWith({ username: 'testuser', password: 'password123' }); });
-
API集成测试
import { render, screen, waitFor } from '@testing-library/react'; import axios from 'axios'; import UserList from './UserList'; jest.mock('axios'); test('fetches and displays users', async () => { axios.get.mockResolvedValue({ data: [{ id: 1, name: 'John Doe' }] }); render(<UserList />); await waitFor(() => { expect(screen.getByText(/john doe/i)).toBeInTheDocument(); }); });
5.3.3 E2E测试
-
Cypress配置
// cypress.config.js module.exports = { e2e: { baseUrl: 'http://localhost:3000', supportFile: false } };
-
页面导航测试
describe('Navigation', () => { it('should navigate to about page', () => { cy.visit('/'); cy.get('a[href*="about"]').click(); cy.url().should('include', '/about'); cy.get('h1').contains('About Page'); }); });
-
表单提交测试
describe('Login Form', () => { it('should submit form', () => { cy.visit('/login'); cy.get('#username').type('testuser'); cy.get('#password').type('password123'); cy.get('form').submit(); cy.url().should('include', '/dashboard'); }); });
5.3.4 测试覆盖率
-
Jest配置
{ "collectCoverage": true, "coverageReporters": ["text", "lcov"], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } }
-
生成覆盖率报告
npm test -- --coverage
-
查看HTML报告
open coverage/lcov-report/index.html
5.3.5 测试最佳实践
-
测试金字塔
- 70% 单元测试
- 20% 集成测试
- 10% E2E测试
-
测试命名规范
- 描述性测试名称
- Given-When-Then模式
- 避免使用"should"
-
测试数据管理
- 使用工厂函数生成测试数据
- 避免硬编码
- 使用faker.js生成随机数据
-
测试隔离
- 每个测试独立运行
- 使用beforeEach/afterEach清理状态
- 避免测试间依赖
6. 生态系统
6.1 状态管理
Redux
-
核心概念
- Store:应用状态容器
- Action:状态变更描述
- Reducer:状态变更处理
- Middleware:扩展功能
-
示例代码
// store.js import { createStore } from 'redux'; function counterReducer(state = { value: 0 }, action) { switch (action.type) { case 'increment': return { value: state.value + 1 }; default: return state; } } const store = createStore(counterReducer); // Component.js import { useSelector, useDispatch } from 'react-redux'; function Counter() { const count = useSelector(state => state.value); const dispatch = useDispatch(); return ( <div> <span>{count}</span> <button onClick={() => dispatch({ type: 'increment' })}> Increment </button> </div> ); }
Recoil
-
核心概念
- Atom:状态单元
- Selector:派生状态
- Hooks:状态访问
-
示例代码
// state.js import { atom } from 'recoil'; export const countState = atom({ key: 'countState', default: 0, }); // Component.js import { useRecoilState } from 'recoil'; import { countState } from './state'; function Counter() { const [count, setCount] = useRecoilState(countState); return ( <div> <span>{count}</span> <button onClick={() => setCount(count + 1)}> Increment </button> </div> ); }
6.2 样式方案
Styled Components
-
基本用法
import styled from 'styled-components'; const Button = styled.button` background: ${props => props.primary ? 'palevioletred' : 'white'}; color: ${props => props.primary ? 'white' : 'palevioletred'}; font-size: 1em; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; `; function Example() { return ( <div> <Button>Normal</Button> <Button primary>Primary</Button> </div> ); }
-
主题支持
import { ThemeProvider } from 'styled-components'; const theme = { colors: { primary: 'palevioletred', secondary: 'white' } }; function App() { return ( <ThemeProvider theme={theme}> <Button>Styled</Button> </ThemeProvider> ); }
CSS Modules
-
基本用法
/* Button.module.css */ .button { background-color: white; color: palevioletred; font-size: 1em; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; }
import styles from './Button.module.css'; function Button() { return ( <button className={styles.button}> Click me </button> ); }
-
组合样式
.primary { background-color: palevioletred; color: white; }
function Button({ primary }) { return ( <button className={`${styles.button} ${primary && styles.primary}`}> Click me </button> ); }
6.3 表单处理
Formik
-
基本用法
import { Formik, Form, Field } from 'formik'; function LoginForm() { return ( <Formik initialValues={{ email: '', password: '' }} onSubmit={values => { console.log(values); }} > <Form> <Field name="email" type="email" /> <Field name="password" type="password" /> <button type="submit">Submit</button> </Form> </Formik> ); }
-
表单验证
function validate(values) { const errors = {}; if (!values.email) { errors.email = 'Required'; } else if ( !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email) ) { errors.email = 'Invalid email address'; } return errors; }
React Hook Form
-
基本用法
import { useForm } from 'react-hook-form'; function LoginForm() { const { register, handleSubmit } = useForm(); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email')} /> <input {...register('password')} type="password" /> <button type="submit">Submit</button> </form> ); }
-
表单验证
<input {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address' } })} />
6.4 数据获取
React Query
-
基本用法
import { useQuery } from 'react-query'; function UserList() { const { data, isLoading, error } = useQuery('users', () => fetch('/api/users').then(res => res.json()) ); if (isLoading) return 'Loading...'; if (error) return 'Error!'; return ( <ul> {data.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
-
数据缓存
const { data } = useQuery('users', fetchUsers, { staleTime: 1000 * 60 * 5, // 5 minutes cacheTime: 1000 * 60 * 10 // 10 minutes });
SWR
-
基本用法
import useSWR from 'swr'; function Profile() { const { data, error } = useSWR('/api/user', fetcher); if (error) return <div>failed to load</div>; if (!data) return <div>loading...</div>; return <div>hello {data.name}!</div>; }
-
自动重试
const { data } = useSWR('/api/user', fetcher, { onErrorRetry: (error, key, config, revalidate, { retryCount }) => { if (error.status === 404) return; if (retryCount >= 10) return; setTimeout(() => revalidate({ retryCount }), 5000); } });
6.5 UI 库
Material-UI
-
基本用法
import { Button, TextField } from '@mui/material'; function Form() { return ( <form> <TextField label="Email" variant="outlined" /> <TextField label="Password" type="password" variant="outlined" /> <Button variant="contained" color="primary"> Submit </Button> </form> ); }
-
主题定制
import { createTheme, ThemeProvider } from '@mui/material/styles'; const theme = createTheme({ palette: { primary: { main: '#1976d2', }, }, }); function App() { return ( <ThemeProvider theme={theme}> <Form /> </ThemeProvider> ); }
Ant Design
-
基本用法
import { Button, Input } from 'antd'; function Form() { return ( <form> <Input placeholder="Email" /> <Input.Password placeholder="Password" /> <Button type="primary">Submit</Button> </form> ); }
-
主题定制
import { ConfigProvider } from 'antd'; function App() { return ( <ConfigProvider theme={{ token: { colorPrimary: '#00b96b', }, }} > <Form /> </ConfigProvider> ); }
7. 学习资源
- 官方文档:https://reactjs.org/
- React 中文文档:https://zh-hans.reactjs.org/
- React 入门教程:https://react.iamkasong.com/
- React 进阶指南:https://react.iamkasong.com/advanced/
- React 源码解析:https://react.iamkasong.com/implementation/
8. React 18 新特性
8.1 并发渲染(Concurrent Rendering)
- 可中断渲染:允许React在渲染过程中暂停和恢复
- 自动批处理:自动合并多个状态更新
- 过渡更新:区分紧急和非紧急更新
import { startTransition } from 'react';
// 紧急更新
setInputValue(input);
// 非紧急更新
startTransition(() => {
setSearchQuery(input);
});
8.2 新的Hooks
- useId:生成唯一ID
- useSyncExternalStore:与外部存储同步
- useInsertionEffect:用于CSS-in-JS库
8.3 服务端渲染改进
- 流式SSR:支持渐进式HTML流
- 选择性水合:优先水合重要部分
8.4 其他改进
- 新的根API:createRoot
- 严格模式增强:检测不安全的副作用
- 改进的错误处理:自动恢复错误边界
9. Server Components
9.1 概念与优势
- 在服务端渲染组件
- 减少客户端包体积
- 直接访问后端服务
- 自动代码分割
9.2 使用场景
- 数据获取密集型组件
- 静态内容
- 大型依赖组件
- 安全性要求高的组件
9.3 示例代码
// Note.server.js
import db from 'db.server';
function Note({id}) {
const note = db.notes.get(id);
return <NoteView note={note} />;
}
// NoteView.client.js
'use client';
function NoteView({note}) {
return (
<div>
<h1>{note.title}</h1>
<p>{note.content}</p>
</div>
);
}
9.4 注意事项
- 客户端交互限制
- 状态管理方式
- 网络延迟影响
- 开发环境配置
10. 状态管理库对比
10.1 Redux
- 优点:
- 成熟的生态系统
- 强大的中间件支持
- 时间旅行调试
- 严格的单向数据流
- 缺点:
- 样板代码较多
- 学习曲线较陡
- 可能过度设计简单场景
10.2 Recoil
- 优点:
- 原生支持React
- 细粒度状态管理
- 异步数据支持
- 更简单的API
- 缺点:
- 相对较新
- 生态系统不够成熟
- 调试工具有限
10.3 Zustand
- 优点:
- 极简API
- 高性能
- 支持中间件
- 易于集成
- 缺点:
- 功能相对基础
- 社区支持较少
- 缺少内置异步处理
10.4 选择建议
场景 | 推荐方案 |
---|---|
大型复杂应用 | Redux |
中小型应用 | Recoil |
简单应用 | Zustand |
需要时间旅行调试 | Redux |
需要细粒度更新 | Recoil |
追求极简实现 | Zustand |
11. 常见问题与解决方案
11.1 性能问题
- 问题:组件频繁重新渲染
- 解决方案:使用React.memo、useMemo、useCallback优化
- 问题:长列表渲染卡顿
- 解决方案:使用虚拟列表库(如react-window)
11.2 状态管理
- 问题:组件间状态共享困难
- 解决方案:使用Context API或状态管理库
- 问题:状态更新导致意外渲染
- 解决方案:检查依赖数组,使用useEffect正确管理副作用
11.3 路由问题
- 问题:页面刷新后404
- 解决方案:配置服务器支持HTML5 History API
- 问题:路由切换时数据丢失
- 解决方案:使用状态管理或路由缓存
11.4 样式冲突
- 问题:全局样式污染
- 解决方案:使用CSS Modules或CSS-in-JS
- 问题:第三方组件样式覆盖
- 解决方案:使用CSS命名空间或样式优先级调整
11.5 开发环境
- 问题:热更新失效
- 解决方案:检查webpack配置,确保HMR启用
- 问题:TypeScript类型错误
- 解决方案:安装正确的类型定义文件(@types包)
12. 项目架构设计
12.1 分层架构
展示层(Presentation Layer)
-
职责
- UI展示
- 用户交互
- 数据展示
- 样式管理
-
技术栈
- React组件
- CSS-in-JS
- UI库(如Material-UI)
- 动画库(如Framer Motion)
-
示例代码
// components/Button.jsx import styled from 'styled-components'; const StyledButton = styled.button` background: ${props => props.primary ? 'blue' : 'white'}; color: ${props => props.primary ? 'white' : 'blue'}; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; `; function Button({ primary, children }) { return <StyledButton primary={primary}>{children}</StyledButton>; }
业务逻辑层(Business Logic Layer)
-
职责
- 业务规则处理
- 数据转换
- 状态管理
- 业务逻辑复用
-
技术栈
- 自定义Hooks
- 服务类
- 状态管理库(如Redux、Recoil)
- 数据验证库(如Yup)
-
示例代码
// hooks/useForm.js import { useState } from 'react'; export function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (e) => { const { name, value } = e.target; setValues({ ...values, [name]: value }); }; return { values, handleChange }; }
数据访问层(Data Access Layer)
-
职责
- API调用
- 数据缓存
- 错误处理
- 数据转换
-
技术栈
- Axios
- React Query
- GraphQL客户端(如Apollo)
- WebSocket
-
示例代码
// services/api.js import axios from 'axios'; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json' } }); export const getUsers = async () => { try { const response = await api.get('/users'); return response.data; } catch (error) { throw new Error(error.response?.data?.message || 'Failed to fetch users'); } };
12.2 目录结构
基础结构
src/
├── assets/ # 静态资源
│ ├── images/ # 图片资源
│ ├── fonts/ # 字体文件
│ └── styles/ # 全局样式
├── components/ # 通用组件
│ ├── ui/ # UI组件
│ ├── layout/ # 布局组件
│ └── shared/ # 共享组件
├── features/ # 功能模块
│ └── featureName/
│ ├── api/ # API接口
│ ├── hooks/ # 自定义Hooks
│ ├── store/ # 状态管理
│ ├── types/ # 类型定义
│ └── views/ # 页面组件
├── lib/ # 工具函数
│ ├── utils/ # 通用工具
│ ├── constants/ # 常量定义
│ └── helpers/ # 辅助函数
├── pages/ # 页面路由
│ ├── Home/ # 首页
│ ├── About/ # 关于页
│ └── NotFound/ # 404页
├── services/ # API服务
│ ├── api.js # API配置
│ ├── auth.js # 认证服务
│ └── storage.js # 存储服务
├── store/ # 全局状态
│ ├── slices/ # Redux切片
│ ├── actions/ # Redux动作
│ └── reducers/ # Redux reducer
├── styles/ # 全局样式
│ ├── theme.js # 主题配置
│ ├── global.css # 全局样式
│ └── variables.css # CSS变量
└── utils/ # 工具函数
├── validators/ # 验证工具
├── formatters/ # 格式化工具
└── hooks/ # 自定义Hooks
模块化结构
src/
├── modules/
│ ├── auth/ # 认证模块
│ │ ├── api/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── store/
│ │ └── views/
│ ├── user/ # 用户模块
│ │ ├── api/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── store/
│ │ └── views/
│ └── product/ # 产品模块
│ ├── api/
│ ├── components/
│ ├── hooks/
│ ├── store/
│ └── views/
12.3 模块化设计
模块划分原则
-
按业务功能划分
- 用户管理
- 用户注册/登录
- 用户信息管理
- 权限控制
- 订单管理
- 订单创建
- 订单查询
- 订单状态管理
- 产品管理
- 产品展示
- 产品分类
- 产品搜索
- 权限管理
- 角色管理
- 权限分配
- 访问控制
- 用户管理
-
按技术特性划分
- 认证模块
- 登录/注册
- 会话管理
- 权限验证
- 支付模块
- 支付接口
- 支付状态管理
- 支付回调处理
- 通知模块
- 消息推送
- 邮件通知
- 站内信
- 日志模块
- 操作日志
- 错误日志
- 访问日志
- 认证模块
-
模块间通信
- 通过props传递数据
// ParentComponent.jsx function ParentComponent() { const [data, setData] = useState(null); return <ChildComponent data={data} onDataChange={setData} />; } // ChildComponent.jsx function ChildComponent({ data, onDataChange }) { return ( <input value={data || ''} onChange={(e) => onDataChange(e.target.value)} /> ); }
- 使用Context API共享状态
// ThemeContext.js const ThemeContext = createContext(); function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); } // ThemedButton.jsx function ThemedButton() { const { theme, setTheme } = useContext(ThemeContext); return ( <button style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }} onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} > Toggle Theme </button> ); }
- 通过事件总线通信
// eventBus.js class EventBus { constructor() { this.events = {}; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); } emit(event, ...args) { if (this.events[event]) { this.events[event].forEach(callback => callback(...args)); } } } export default new EventBus();
- 使用状态管理库
// store.js import { configureStore } from '@reduxjs/toolkit'; import authReducer from './authSlice'; import userReducer from './userSlice'; export default configureStore({ reducer: { auth: authReducer, user: userReducer } });
- 通过props传递数据
模块接口设计
-
组件接口
// modules/auth/components/LoginForm.jsx import PropTypes from 'prop-types'; function LoginForm({ onSubmit, loading, error }) { return ( <form onSubmit={onSubmit}> {error && <div className="error">{error}</div>} <input name="email" type="email" required /> <input name="password" type="password" required /> <button type="submit" disabled={loading}> {loading ? 'Loading...' : 'Login'} </button> </form> ); } LoginForm.propTypes = { onSubmit: PropTypes.func.isRequired, loading: PropTypes.bool, error: PropTypes.string }; LoginForm.defaultProps = { loading: false, error: null };
-
API接口
// modules/auth/api/auth.js import axios from 'axios'; const API_BASE_URL = process.env.REACT_APP_API_URL; export const login = async (credentials) => { try { const response = await axios.post(`${API_BASE_URL}/auth/login`, credentials); return response.data; } catch (error) { throw new Error(error.response?.data?.message || 'Login failed'); } }; export const register = async (userData) => { try { const response = await axios.post(`${API_BASE_URL}/auth/register`, userData); return response.data; } catch (error) { throw new Error(error.response?.data?.message || 'Registration failed'); } }; export const logout = async () => { try { await axios.post(`${API_BASE_URL}/auth/logout`); } catch (error) { throw new Error(error.response?.data?.message || 'Logout failed'); } };
-
状态接口
// modules/auth/store/authSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { user: null, loading: false, error: null }; const authSlice = createSlice({ name: 'auth', initialState, reducers: { loginStart(state) { state.loading = true; state.error = null; }, loginSuccess(state, action) { state.user = action.payload; state.loading = false; }, loginFailure(state, action) { state.error = action.payload; state.loading = false; }, logoutSuccess(state) { state.user = null; } } }); export const { loginStart, loginSuccess, loginFailure, logoutSuccess } = authSlice.actions; export const login = (credentials) => async (dispatch) => { try { dispatch(loginStart()); const user = await loginAPI(credentials); dispatch(loginSuccess(user)); } catch (error) { dispatch(loginFailure(error.message)); } }; export const logout = () => async (dispatch) => { try { await logoutAPI(); dispatch(logoutSuccess()); } catch (error) { console.error('Logout error:', error); } }; export default authSlice.reducer;
-
类型接口(TypeScript)
// modules/auth/types.ts export interface User { id: string; name: string; email: string; role: string; } export interface AuthState { user: User | null; loading: boolean; error: string | null; } export interface LoginCredentials { email: string; password: string; } export interface RegisterData extends LoginCredentials { name: string; }
模块依赖管理
-
模块间依赖
- 使用依赖注入
// modules/auth/authService.js class AuthService { constructor(apiClient) { this.apiClient = apiClient; } async login(credentials) { return this.apiClient.post('/auth/login', credentials); } } // main.js const apiClient = new ApiClient(); const authService = new AuthService(apiClient);
- 使用依赖注入
-
模块版本控制
- 使用语义化版本
{ "dependencies": { "@modules/auth": "^1.2.0", "@modules/user": "~2.0.1" } }
- 使用语义化版本
-
模块更新策略
- 向后兼容
- 版本迁移指南
- 废弃警告
模块测试策略
-
单元测试
// modules/auth/authSlice.test.js import authReducer, { loginStart, loginSuccess, loginFailure } from './authSlice'; describe('auth reducer', () => { it('should handle initial state', () => { expect(authReducer(undefined, {})).toEqual({ user: null, loading: false, error: null }); }); it('should handle loginStart', () => { expect( authReducer(undefined, loginStart()) ).toEqual({ user: null, loading: true, error: null }); }); });
-
集成测试
// modules/auth/authService.test.js import AuthService from './authService'; import MockAdapter from 'axios-mock-adapter'; import axios from 'axios'; describe('AuthService', () => { let mockAxios; let authService; beforeEach(() => { mockAxios = new MockAdapter(axios); authService = new AuthService(axios); }); it('should login successfully', async () => { const credentials = { email: 'test@example.com', password: 'password' }; const responseData = { token: 'abc123' }; mockAxios.onPost('/auth/login').reply(200, responseData); const result = await authService.login(credentials); expect(result.data).toEqual(responseData); }); });
-
E2E测试
// cypress/integration/auth.spec.js describe('Authentication', () => { it('should login successfully', () => { cy.visit('/login'); cy.get('[name="email"]').type('test@example.com'); cy.get('[name="password"]').type('password'); cy.get('form').submit(); cy.url().should('include', '/dashboard'); }); });
模块文档规范
-
README模板
# Auth Module ## 功能描述 - 用户登录/注册 - 会话管理 - 权限验证 ## 安装 ```bash npm install @modules/auth
使用示例
import { login } from '@modules/auth'; const credentials = { email: 'test@example.com', password: 'password123' }; login(credentials) .then(user => console.log('Logged in:', user)) .catch(error => console.error('Login failed:', error));
API 文档
login(credentials)
register(userData)
logout()
依赖
- axios
- redux
-
API文档生成
- 使用JSDoc
/** * 用户登录 * @param {Object} credentials - 登录凭证 * @param {string} credentials.email - 用户邮箱 * @param {string} credentials.password - 用户密码 * @returns {Promise<User>} 返回用户信息 * @throws {Error} 登录失败时抛出错误 */ export async function login(credentials) { // ... }
- 使用JSDoc
-
变更日志
# Changelog ## [1.2.0] - 2023-03-15 ### Added - 新增第三方登录支持 ### Changed - 优化登录错误提示 ### Fixed - 修复会话过期问题
模块发布流程
-
版本管理
npm version patch npm version minor npm version major
-
发布到私有仓库
npm publish --access restricted
-
自动更新
{ "scripts": { "postpublish": "git push && git push --tags" } }
模块最佳实践
-
单一职责原则
- 每个模块只负责一个功能
- 避免过度耦合
-
接口最小化
- 只暴露必要的接口
- 使用私有方法
-
依赖管理
- 最小化外部依赖
- 使用peerDependencies
-
性能优化
- 懒加载模块
- 代码分割
-
安全性
- 输入验证
- 权限控制
- 错误处理
12.4 性能优化策略
代码分割
-
动态导入
const LazyComponent = React.lazy(() => import('./LazyComponent')); function App() { return ( <React.Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </React.Suspense> ); }
-
路由懒加载
const Home = React.lazy(() => import('./pages/Home')); const About = React.lazy(() => import('./pages/About')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> ); }
-
组件级代码分割
const HeavyComponent = React.lazy(() => import('./HeavyComponent').then(module => ({ default: module.HeavyComponent })) );
-
预加载策略
const preloadComponent = (component) => { const Component = React.lazy(component); Component.preload(); }; // 鼠标悬停时预加载 <div onMouseEnter={() => preloadComponent(() => import('./HeavyComponent'))}> Hover to preload </div>
资源优化
-
图片优化
- 使用WebP格式
<picture> <source srcSet="image.webp" type="image/webp" /> <source srcSet="image.jpg" type="image/jpeg" /> <img src="image.jpg" alt="Example" /> </picture>
- 响应式图片
<img srcSet="image-320w.jpg 320w, image-480w.jpg 480w, image-800w.jpg 800w" sizes="(max-width: 320px) 280px, (max-width: 480px) 440px, 800px" src="image-800w.jpg" alt="Example" />
- 图片懒加载
<img src="placeholder.jpg" data-src="real-image.jpg" className="lazyload" alt="Example" />
- 使用WebP格式
-
字体优化
- 字体子集化
@font-face { font-family: 'CustomFont'; src: url('/fonts/custom-subset.woff2') format('woff2'), url('/fonts/custom-subset.woff') format('woff'); unicode-range: U+000-5FF; /* 仅包含常用字符 */ }
- 字体预加载
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin />
- 字体显示策略
@font-face { font-display: swap; /* 使用系统字体直到自定义字体加载完成 */ }
- 字体子集化
-
资源压缩
- Webpack配置示例
module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { compress: true, mangle: true } }), new CssMinimizerPlugin() ] } };
- Webpack配置示例
缓存策略
-
Service Worker
- 缓存策略配置
// service-worker.js const CACHE_NAME = 'v1'; const ASSETS = [ '/', '/index.html', '/main.js', '/style.css' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(ASSETS)) ); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });
- 缓存更新策略
self.addEventListener('activate', (event) => { const cacheWhitelist = [CACHE_NAME]; event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (!cacheWhitelist.includes(cacheName)) { return caches.delete(cacheName); } }) ); }) ); });
- 缓存策略配置
-
HTTP缓存
- Nginx配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Vary "Accept-Encoding"; }
- ETag配置
etag on;
- Nginx配置
-
数据缓存
- React Query缓存配置
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5分钟 cacheTime: 1000 * 60 * 10 // 10分钟 } } });
- React Query缓存配置
渲染优化
-
虚拟列表
- 固定高度列表
import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> );
- 可变高度列表
import { VariableSizeList as List } from 'react-window'; const rowHeights = new Array(1000) .fill(true) .map(() => 25 + Math.round(Math.random() * 50)); const getItemSize = index => rowHeights[index]; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={getItemSize} width={300} > {Row} </List> );
- 固定高度列表
-
批量更新
- React 18自动批处理
function handleClick() { setCount(c => c + 1); setFlag(f => !f); // 自动批处理 }
- 手动批处理
import { unstable_batchedUpdates } from 'react-dom'; function handleClick() { unstable_batchedUpdates(() => { setCount(c => c + 1); setFlag(f => !f); }); }
- React 18自动批处理
-
渲染优先级
- 使用startTransition
import { startTransition } from 'react'; function handleInputChange(value) { setInputValue(value); // 紧急更新 startTransition(() => { setSearchQuery(value); // 非紧急更新 }); }
- 使用startTransition
-
避免不必要的渲染
- 使用React.memo
const MemoizedComponent = React.memo(function Component({ data }) { return <div>{data}</div>; });
- 使用useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 使用useCallback
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
- 使用React.memo
网络优化
-
HTTP/2配置
- Nginx配置
server { listen 443 ssl http2; http2_push /style.css; http2_push /app.js; }
- Nginx配置
-
资源预加载
- 关键资源预加载
<link rel="preload" href="critical.css" as="style"> <link rel="preload" href="main.js" as="script">
- 数据预取
const { data } = useSWR('/api/data', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false });
- 关键资源预加载
-
服务端推送
- 使用HTTP/2 Server Push
- 缓存策略优化
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; }
调试技巧
-
React Profiler
- 性能分析
<Profiler id="App" onRender={onRenderCallback}> <App /> </Profiler>
- 自定义回调
function onRenderCallback( id, phase, actualDuration, baseDuration, startTime, commitTime, interactions ) { console.log('Render time:', actualDuration); }
- 性能分析
-
Chrome DevTools
- 性能面板
- 内存面板
- 网络面板
-
Lighthouse
- 性能评分
- 最佳实践检查
- 可访问性评估
性能监控
-
Web Vitals
- 核心指标监控
import { getCLS, getFID, getLCP } from 'web-vitals'; getCLS(console.log); getFID(console.log); getLCP(console.log);
- 自定义上报
function sendToAnalytics(metric) { const body = JSON.stringify(metric); navigator.sendBeacon('/analytics', body); } getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics);
- 核心指标监控
-
错误监控
- 错误边界
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { logErrorToService(error, errorInfo); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } }
- 全局错误捕获
window.addEventListener('error', (event) => { logErrorToService(event.error); }); window.addEventListener('unhandledrejection', (event) => { logErrorToService(event.reason); });
- 错误边界
性能优化最佳实践
-
关键渲染路径优化
- 最小化关键资源
- 减少关键请求数量
- 优化关键字节数
-
资源加载策略
- 预加载关键资源
- 延迟加载非关键资源
- 按需加载第三方库
-
渲染性能优化
- 避免不必要的重新渲染
- 使用虚拟列表
- 优化复杂组件
-
网络性能优化
- 使用HTTP/2
- 启用Gzip压缩
- 使用CDN加速
-
内存管理
- 清理未使用的引用
- 避免内存泄漏
- 使用WeakMap/WeakSet
-
工具使用
- React DevTools
- Chrome DevTools
- Lighthouse
- Webpack Bundle Analyzer
12.5 测试策略
12.5 测试策略
单元测试
-
组件测试
import { render, screen } from '@testing-library/react'; import Button from './Button'; test('renders button with correct text', () => { render(<Button>Click me</Button>); const buttonElement = screen.getByText(/click me/i); expect(buttonElement).toBeInTheDocument(); });
-
Hooks测试
import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from './useCounter'; test('should increment counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); });
-
快照测试
import renderer from 'react-test-renderer'; import Component from './Component'; test('matches snapshot', () => { const tree = renderer.create(<Component />).toJSON(); expect(tree).toMatchSnapshot(); });
集成测试
-
组件交互测试
import { render, screen, fireEvent } from '@testing-library/react'; import LoginForm from './LoginForm'; test('submits form with correct values', () => { const handleSubmit = jest.fn(); render(<LoginForm onSubmit={handleSubmit} />); fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password123' } }); fireEvent.click(screen.getByRole('button', { name: /submit/i })); expect(handleSubmit).toHaveBeenCalledWith({ username: 'testuser', password: 'password123' }); });
-
API集成测试
import { render, screen, waitFor } from '@testing-library/react'; import axios from 'axios'; import UserList from './UserList'; jest.mock('axios'); test('fetches and displays users', async () => { axios.get.mockResolvedValue({ data: [{ id: 1, name: 'John Doe' }] }); render(<UserList />); await waitFor(() => { expect(screen.getByText(/john doe/i)).toBeInTheDocument(); }); });
E2E测试
-
Cypress配置
// cypress.config.js module.exports = { e2e: { baseUrl: 'http://localhost:3000', supportFile: false } };
-
页面导航测试
describe('Navigation', () => { it('should navigate to about page', () => { cy.visit('/'); cy.get('a[href*="about"]').click(); cy.url().should('include', '/about'); cy.get('h1').contains('About Page'); }); });
-
表单提交测试
describe('Login Form', () => { it('should submit form', () => { cy.visit('/login'); cy.get('#username').type('testuser'); cy.get('#password').type('password123'); cy.get('form').submit(); cy.url().should('include', '/dashboard'); }); });
测试覆盖率
-
Jest配置
{ "collectCoverage": true, "coverageReporters": ["text", "lcov"], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } }
-
生成覆盖率报告
npm test -- --coverage
-
查看HTML报告
open coverage/lcov-report/index.html
测试最佳实践
-
测试金字塔
- 70% 单元测试
- 20% 集成测试
- 10% E2E测试
-
测试命名规范
- 描述性测试名称
- Given-When-Then模式
- 避免使用"should"
-
测试数据管理
- 使用工厂函数生成测试数据
- 避免硬编码
- 使用faker.js生成随机数据
-
测试隔离
- 每个测试独立运行
- 使用beforeEach/afterEach清理状态
- 避免测试间依赖
13. 部署与运维
13.1 部署策略
静态资源部署
- CDN加速配置
# 使用AWS CloudFront配置示例 aws cloudfront create-distribution \ --origin-domain-name your-bucket.s3.amazonaws.com \ --default-root-object index.html \ --enabled
- 缓存策略优化
- 设置Cache-Control头
- 使用ETag进行缓存验证
- 配置合理的max-age
- 版本控制方案
- 基于文件内容hash生成文件名
- 使用webpack配置示例:
output: { filename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist') }
服务端渲染部署
- Node.js服务器配置
- 使用PM2进行进程管理
pm2 start server.js -i max pm2 save pm2 startup
- 配置Nginx反向代理
server { listen 80; server_name yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
- 使用PM2进行进程管理
- 负载均衡配置
- 使用Nginx实现负载均衡
upstream nodejs { server 127.0.0.1:3000; server 127.0.0.1:3001; server 127.0.0.1:3002; }
- 配置健康检查
location /health { return 200 'healthy'; }
- 使用Nginx实现负载均衡
- 自动扩展策略
- 基于CPU使用率自动扩展
- 基于请求量自动扩展
- 配置自动扩展组(AWS示例)
aws autoscaling create-auto-scaling-group \ --auto-scaling-group-name my-asg \ --min-size 2 \ --max-size 10 \ --desired-capacity 2 \ --vpc-zone-identifier "subnet-xxxxxx"
13.2 CI/CD 配置
- 持续集成
- 代码质量检查
- 单元测试
- 构建验证
- 持续部署
- 自动化部署流程
- 蓝绿部署
- 回滚机制
13.3 监控与告警
- 性能监控
- 页面加载时间
- API响应时间
- 错误率
- 日志管理
- 集中式日志收集
- 日志分级
- 日志分析
- 告警系统
- 关键指标阈值
- 多渠道通知
- 告警分级
13.4 安全策略
- 前端安全
- XSS防护
- CSRF防护
- CSP配置
- 部署安全
- HTTPS强制
- 访问控制
- 安全扫描
13.5 性能优化
资源优化
-
图片优化
- 使用WebP格式
- 响应式图片加载
<picture> <source srcSet="image.webp" type="image/webp" /> <img src="image.jpg" alt="Example" /> </picture>
- 图片懒加载
<img src="placeholder.jpg" data-src="real-image.jpg" className="lazyload" alt="Example" />
-
代码优化
- Webpack配置示例
optimization: { splitChunks: { chunks: 'all', minSize: 20000, maxSize: 50000, }, minimize: true, minimizer: [new TerserPlugin()], }
- Tree Shaking配置
{ "sideEffects": false }
- Webpack配置示例
-
字体优化
- 使用font-display: swap
- 子集化字体文件
- 预加载关键字体
<link rel="preload" href="/fonts/Inter.woff2" as="font" type="font/woff2" crossorigin />
网络优化
-
HTTP/2配置
- Nginx示例
server { listen 443 ssl http2; http2_push /style.css; http2_push /app.js; }
- Nginx示例
-
资源预加载
- 关键资源预加载
<link rel="preload" href="critical.css" as="style"> <link rel="preload" href="main.js" as="script">
- 数据预取
const data = useSWR('/api/data', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false });
- 关键资源预加载
-
服务端推送
- 使用HTTP/2 Server Push
- 缓存策略优化
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; }
调试技巧
-
React DevTools使用
- 组件性能分析
- 状态调试
- 组件树检查
-
性能监控
- 使用React Profiler
<Profiler id="App" onRender={onRenderCallback}> <App /> </Profiler>
- Lighthouse性能分析
- Web Vitals监控
import { getCLS, getFID, getLCP } from 'web-vitals'; getCLS(console.log); getFID(console.log); getLCP(console.log);
- 使用React Profiler
-
内存泄漏检测
- 使用Chrome DevTools Memory面板
- 检测未清理的副作用
useEffect(() => { const subscription = someObservable.subscribe(); return () => subscription.unsubscribe(); }, []);
14. 总结
React 作为现代前端开发的核心技术之一,具有强大的功能和活跃的社区。通过本指南,您已经掌握了 React 的核心概念和常用技巧。建议通过实际项目来巩固所学知识,并持续关注 React 生态的最新发展。