React 思维模式终极指南
React 思维模式终极指南
1. React 核心心智模型
声明式 vs 命令式
// ❌ 命令式:告诉计算机如何做
const container = document.getElementById('container');
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', () => {alert('Button clicked!');
});
container.appendChild(button);// ✅ 声明式:告诉计算机你想要什么
function Button() {const handleClick = () => alert('Button clicked!');return <button onClick={handleClick}>Click me</button>;
}
React 的核心原则
- UI 是状态的函数:
UI = f(state)
- 单向数据流:数据从父组件流向子组件
- 组件化思维:将 UI 拆分为独立、可复用的部分
2. 组件设计思维
单一职责原则
// ❌ 违反单一职责
function UserProfile({ userId }) {const [user, setUser] = useState(null);const [posts, setPosts] = useState([]);useEffect(() => {// 获取用户信息fetchUser(userId).then(setUser);// 获取用户帖子fetchUserPosts(userId).then(setPosts);}, [userId]);return (<div><h1>{user?.name}</h1><div>{user?.bio}</div><div>{posts.map(post => (<div key={post.id}>{post.title}</div>))}</div></div>);
}// ✅ 单一职责
function UserProfile({ userId }) {return (<div><UserInfo userId={userId} /><UserPosts userId={userId} /></div>);
}function UserInfo({ userId }) {const [user, setUser] = useState(null);useEffect(() => {fetchUser(userId).then(setUser);}, [userId]);return user && (<div><h1>{user.name}</h1><div>{user.bio}</div></div>);
}function UserPosts({ userId }) {const [posts, setPosts] = useState([]);useEffect(() => {fetchUserPosts(userId).then(setPosts);}, [userId]);return (<div>{posts.map(post => (<div key={post.id}>{post.title}</div>))}</div>);
}
组件层次结构设计
// 容器组件 vs 展示组件
function UserListContainer() {const [users, setUsers] = useState([]);const [loading, setLoading] = useState(false);useEffect(() => {setLoading(true);fetchUsers().then(users => {setUsers(users);setLoading(false);});}, []);return <UserList users={users} loading={loading} />;
}// 展示组件 - 只关心如何显示
function UserList({ users, loading }) {if (loading) return <div>Loading...</div>;return (<ul>{users.map(user => (<UserItem key={user.id} user={user} />))}</ul>);
}
3. 状态管理思维
状态提升
// 当多个组件需要共享状态时,将状态提升到最近的共同父组件
function TemperatureConverter() {const [celsius, setCelsius] = useState('');const fahrenheit = celsius !== '' ? (celsius * 9/5 + 32).toFixed(2) : '';return (<div><CelsiusInputvalue={celsius}onChange={setCelsius}/><FahrenheitDisplay value={fahrenheit} /></div>);
}function CelsiusInput({ value, onChange }) {return (<inputtype="number"value={value}onChange={(e) => onChange(e.target.value)}placeholder="Enter Celsius"/>);
}
状态最小化原则
// ❌ 冗余状态
function Form() {const [firstName, setFirstName] = useState('');const [lastName, setLastName] = useState('');const [fullName, setFullName] = useState(''); // 冗余!useEffect(() => {setFullName(`${firstName} ${lastName}`);}, [firstName, lastName]);return <div>{fullName}</div>;
}// ✅ 派生状态
function Form() {const [firstName, setFirstName] = useState('');const [lastName, setLastName] = useState('');const fullName = `${firstName} ${lastName}`; // 派生值return <div>{fullName}</div>;
}
4. 副作用管理思维
useEffect 思维模式
// 思维过程:什么依赖变化时需要重新执行?
function ProductPage({ productId }) {const [product, setProduct] = useState(null);useEffect(() => {// 什么情况下需要重新获取产品信息?// 当 productId 变化时!let cancelled = false;fetchProduct(productId).then(productData => {if (!cancelled) {setProduct(productData);}});// 清理函数:防止已卸载组件的状态更新return () => {cancelled = true;};}, [productId]); // ✅ 正确的依赖数组return product ? <ProductDetails product={product} /> : <Loading />;
}
自定义 Hook 抽象副作用
// 将复杂副作用封装为可复用的 Hook
function useProduct(productId) {const [product, setProduct] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {if (!productId) {setLoading(false);return;}let cancelled = false;setLoading(true);setError(null);fetchProduct(productId).then(productData => {if (!cancelled) {setProduct(productData);setLoading(false);}}).catch(err => {if (!cancelled) {setError(err);setLoading(false);}});return () => {cancelled = true;};}, [productId]);return { product, loading, error };
}// 使用自定义 Hook
function ProductPage({ productId }) {const { product, loading, error } = useProduct(productId);if (loading) return <Loading />;if (error) return <Error message={error.message} />;return <ProductDetails product={product} />;
}
5. 性能优化思维
React.memo 和 useCallback
// ❌ 不必要的重新渲染
function Parent() {const [count, setCount] = useState(0);const [name, setName] = useState('');const handleSubmit = () => {console.log('Submitted:', name);};return (<div><button onClick={() => setCount(c => c + 1)}>Count: {count}</button><Child name={name} onChange={setName} onSubmit={handleSubmit} /></div>);
}// Child 组件在每次 count 变化时都会重新渲染
function Child({ name, onChange, onSubmit }) {console.log('Child rendered'); // 每次都会打印return (<div><input value={name} onChange={e => onChange(e.target.value)} /><button onClick={onSubmit}>Submit</button></div>);
}// ✅ 优化版本
const Child = React.memo(function Child({ name, onChange, onSubmit }) {console.log('Child rendered'); // 只有 name, onChange, onSubmit 变化时才会打印return (<div><input value={name} onChange={e => onChange(e.target.value)} /><button onClick={onSubmit}>Submit</button></div>);
});function Parent() {const [count, setCount] = useState(0);const [name, setName] = useState('');// useCallback 缓存函数引用const handleSubmit = useCallback(() => {console.log('Submitted:', name);}, [name]); // 当 name 变化时重新创建函数return (<div><button onClick={() => setCount(c => c + 1)}>Count: {count}</button><Child name={name} onChange={setName} onSubmit={handleSubmit} /></div>);
}
6. 数据流架构思维
上下文 + 使用Reducer模式
// 复杂状态管理
const AppStateContext = React.createContext();
const AppDispatchContext = React.createContext();function appReducer(state, action) {switch (action.type) {case 'SET_USER':return { ...state, user: action.payload };case 'SET_LOADING':return { ...state, loading: action.payload };case 'ADD_TODO':return { ...state, todos: [...state.todos, action.payload] };default:throw new Error(`Unhandled action type: ${action.type}`);}
}function AppProvider({ children }) {const [state, dispatch] = useReducer(appReducer, {user: null,loading: false,todos: []});return (<AppStateContext.Provider value={state}><AppDispatchContext.Provider value={dispatch}>{children}</AppDispatchContext.Provider></AppStateContext.Provider>);
}// 自定义 Hook 访问状态
function useAppState() {const context = React.useContext(AppStateContext);if (context === undefined) {throw new Error('useAppState must be used within AppProvider');}return context;
}function useAppDispatch() {const context = React.useContext(AppDispatchContext);if (context === undefined) {throw new Error('useAppDispatch must be used within AppProvider');}return context;
}// 在组件中使用
function TodoList() {const { todos } = useAppState();const dispatch = useAppDispatch();const addTodo = (text) => {dispatch({type: 'ADD_TODO',payload: { id: Date.now(), text, completed: false }});};return (<div>{todos.map(todo => (<div key={todo.id}>{todo.text}</div>))}<button onClick={() => addTodo('New todo')}>Add Todo</button></div>);
}
7. 组合优于继承
插槽模式(Slot Pattern)
function Card({ header, children, footer }) {return (<div className="card">{header && <div className="card-header">{header}</div>}<div className="card-body">{children}</div>{footer && <div className="card-footer">{footer}</div>}</div>);
}// 使用
function UserCard({ user }) {return (<Cardheader={<h3>{user.name}</h3>}footer={<button onClick={() => followUser(user.id)}>Follow</button>}><p>{user.bio}</p><p>Followers: {user.followersCount}</p></Card>);
}
渲染属性模式(Render Props)
function DataFetcher({ url, children }) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {fetch(url).then(res => res.json()).then(setData).catch(setError).finally(() => setLoading(false));}, [url]);return children({ data, loading, error });
}// 使用
function UserProfile({ userId }) {return (<DataFetcher url={`/api/users/${userId}`}>{({ data: user, loading, error }) => {if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error.message}</div>;return (<div><h1>{user.name}</h1><p>{user.email}</p></div>);}}</DataFetcher>);
}
8. 错误边界思维
class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false, error: null };}static getDerivedStateFromError(error) {return { hasError: true, error };}componentDidCatch(error, errorInfo) {console.error('Error caught by boundary:', error, errorInfo);// 可以在这里上报错误到监控服务}render() {if (this.state.hasError) {return this.props.fallback || (<div><h2>Something went wrong.</h2><details>{this.state.error && this.state.error.toString()}</details></div>);}return this.props.children;}
}// 使用
function App() {return (<ErrorBoundary fallback={<div>App crashed!</div>}><UserProfile userId="123" /></ErrorBoundary>);
}
9. 测试思维
// 测试友好的组件设计
function LoginForm({ onSubmit }) {const [email, setEmail] = useState('');const [password, setPassword] = useState('');const handleSubmit = (e) => {e.preventDefault();onSubmit({ email, password });};return (<form onSubmit={handleSubmit}><inputtype="email"value={email}onChange={(e) => setEmail(e.target.value)}placeholder="Email"data-testid="email-input"/><inputtype="password"value={password}onChange={(e) => setPassword(e.target.value)}placeholder="Password"data-testid="password-input"/><button type="submit" data-testid="submit-button">Login</button></form>);
}// 测试示例
describe('LoginForm', () => {it('should call onSubmit with email and password', () => {const mockOnSubmit = jest.fn();render(<LoginForm onSubmit={mockOnSubmit} />);fireEvent.change(screen.getByTestId('email-input'), {target: { value: 'test@example.com' }});fireEvent.change(screen.getByTestId('password-input'), {target: { value: 'password123' }});fireEvent.click(screen.getByTestId('submit-button'));expect(mockOnSubmit).toHaveBeenCalledWith({email: 'test@example.com',password: 'password123'});});
});
10. 进阶思维模式
不可变更新模式
// 使用 Immer 简化不可变更新
import produce from 'immer';function TodoApp() {const [todos, setTodos] = useState([]);const addTodo = (text) => {setTodos(produce(draft => {draft.push({ id: Date.now(), text, completed: false });}));};const toggleTodo = (id) => {setTodos(produce(draft => {const todo = draft.find(t => t.id === id);if (todo) todo.completed = !todo.completed;}));};
}
依赖注入思维
// 创建服务上下文
const ApiServiceContext = React.createContext();function ApiServiceProvider({ children, service }) {return (<ApiServiceContext.Provider value={service}>{children}</ApiServiceContext.Provider>);
}// 在组件中注入依赖
function UserProfile({ userId }) {const apiService = useContext(ApiServiceContext);const [user, setUser] = useState(null);useEffect(() => {apiService.getUser(userId).then(setUser);}, [apiService, userId]);return <div>{user?.name}</div>;
}// 测试时可以轻松替换实现
const mockApiService = {getUser: jest.fn().mockResolvedValue({ name: 'Test User' })
};render(<ApiServiceProvider service={mockApiService}><UserProfile userId="123" /></ApiServiceProvider>
);
总结
React 思维模式的核心是:
- 声明式思维:描述 UI 应该是什么样子,而不是如何操作 DOM
- 组件化思维:将复杂 UI 拆分为独立、可复用的组件
- 不可变思维:状态更新应该创建新对象,而不是修改现有对象
- 单向数据流:数据从父组件流向子组件,事件从子组件流向父组件
- 组合思维:通过组件组合而非继承来构建复杂 UI
- 副作用管理:明确副作用依赖,合理使用 useEffect
- 性能意识:理解重新渲染的触发条件,合理使用优化手段
掌握这些思维模式,你将能够编写出更清晰、可维护、高性能的 React 代码。