一、从0到1构建React组件:基础逻辑与拆分原则
React的核心是“组件化”——把UI拆成独立、可复用的单元。现在函数组件是绝对主流(类组件已逐步退出核心场景),我们从最基础的函数组件写起:

比如要做一个“用户信息卡片”,先写一个最基础的函数组件:
function UserCard() {
return (
<div style={{ border: '1px solid #eee', padding: '16px', borderRadius: '8px' }}>
<img src="https://via.placeholder.com/80" alt="头像" style={{ borderRadius: '50%' }} />
<h3>张三</h3>
<p>前端开发者 | 热爱React</p>
</div>
);
}
这个组件能显示用户信息,但如果要复用(比如显示不同用户),就得通过props传递数据——把动态内容从外部传入:
// 优化后:接收props动态渲染
function UserCard({ avatar, name, role }) {
return (
<div style={{ border: '1px solid #eee', padding: '16px', borderRadius: '8px' }}>
<img src={avatar} alt={name} style={{ borderRadius: '50%' }} />
<h3>{name}</h3>
<p>{role} | 热爱React</p>
</div>
);
}
// 使用时传递不同数据
function App() {
return (
<div>
<UserCard
avatar="https://via.placeholder.com/80"
name="张三"
role="前端开发者"
/>
<UserCard
avatar="https://via.placeholder.com/80"
name="李四"
role="后端开发者"
/>
</div>
);
}
组件拆分的核心原则是单一职责:一个组件只做一件事。比如“用户列表”可以拆成UserList
(负责列表容器)和UserItem
(负责单个用户项),这样修改单个用户的样式时,不会影响整个列表的逻辑。
二、局部状态管理:useState的正确打开方式
组件内部的状态(比如输入框内容、按钮点击次数)用useState
管理——这是React最基础的状态工具。举个“计数器”的经典例子:
import { useState } from 'react';
function Counter() {
// 解构出状态值count和更新函数setCount,初始值为0
const [count, setCount] = useState(0);
return (
<div>
<p>你点击了{count}次</p>
{/* 点击时更新count:注意setCount是异步的,不能直接依赖当前count的值做计算 */}
<button onClick={() => setCount(prev => prev + 1)}>点我加1</button>
</div>
);
}
这里有个避坑点:setCount
是异步更新,如果你直接写setCount(count + 1)
,在连续点击时可能会丢失状态(比如快速点两次,结果只加1)。解决方法是用函数式更新(传入prev参数),确保每次取到的是最新状态。
三、跨组件通信:从props到useContext的进阶
当组件层级变深(比如爷爷→爸爸→儿子→孙子),用props一层一层传递数据会很麻烦(俗称“props drilling”)。这时候用useContext
解决——它能让数据在组件树中“全局”可用,无需手动传递。
比如做一个“主题切换”功能,让所有组件都能获取当前主题:
1. 创建上下文:定义主题的默认值和操作方法
import { createContext, useState, useContext } from 'react';
// 1. 创建ThemeContext,默认值为light主题
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {}
});
// 2. 创建Provider组件,负责管理主题状态
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
// 切换主题的方法
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// 把状态和方法传递给子组件
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. 封装自定义Hook,简化使用(可选,但更优雅)
export function useTheme() {
return useContext(ThemeContext);
}
-
在根组件包裹Provider:让所有子组件都能访问主题
// index.js import { ThemeProvider } from './ThemeContext'; import App from './App'; ReactDOM.render( <ThemeProvider> <App /> </ThemeProvider>, document.getElementById('root') );
-
在任意组件中使用主题:不用传递props,直接用
useTheme
获取import { useTheme } from './ThemeContext'; function ThemedButton() { const { theme, toggleTheme } = useTheme(); return ( <button onClick={toggleTheme} style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#333' : '#fff', padding: '8px 16px', border: 'none', borderRadius: '4px' }} > 当前主题:{theme}(点击切换) </button> ); }
四、复杂状态管理:Redux Toolkit的实战技巧
当应用变大(比如有购物车、用户登录状态、商品列表),useContext
会不够用——因为它无法高效处理“跨模块的状态共享”和“状态变更日志”。这时候用Redux Toolkit(官方推荐的Redux简化方案)。
以“购物车”为例,步骤如下:
1. 安装依赖:
npm install @reduxjs/toolkit react-redux
-
创建Slice:Slice是Redux的“模块”,包含状态、 reducer和action
// features/cart/cartSlice.js import { createSlice } from '@reduxjs/toolkit'; // 初始状态:空购物车 const initialState = { items: [] }; // 创建cartSlice:name是模块名,initialState是初始状态,reducers是状态变更逻辑 const cartSlice = createSlice({ name: 'cart', initialState, reducers: { // 添加商品:state是当前状态,action是传入的数据(action.payload是商品信息) addItem: (state, action) => { state.items.push(action.payload); // Redux Toolkit允许直接修改state(内部用了Immer库) }, // 删除商品:根据id过滤掉要删除的项 removeItem: (state, action) => { state.items = state.items.filter(item => item.id !== action.payload); } } }); // 导出action creator(用于组件中触发状态变更) export const { addItem, removeItem } = cartSlice.actions; // 导出reducer(用于配置store) export default cartSlice.reducer;
-
创建Store:整合所有Slice的reducer
// app/store.js import { configureStore } from '@reduxjs/toolkit'; import cartReducer from '../features/cart/cartSlice'; // 创建store:reducer对象中的键是模块名,值是对应的reducer export const store = configureStore({ reducer: { cart: cartReducer } });
-
在根组件包裹Redux Provider:
// index.js import { Provider } from 'react-redux'; import { store } from './app/store'; import App from './App'; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
-
在组件中使用Redux:用
useSelector
取状态,useDispatch
触发actionimport { useSelector, useDispatch } from 'react-redux'; import { addItem, removeItem } from './features/cart/cartSlice'; function Cart() { // 从Redux store中取购物车 items const cartItems = useSelector(state => state.cart.items); // 获取dispatch函数,用于触发action const dispatch = useDispatch(); // 模拟商品数据 const product = { id: 1, name: 'React实战教程', price: 99 }; return ( <div> <h3>购物车({cartItems.length}件)</h3> <button onClick={() => dispatch(addItem(product))}>添加商品</button> <ul> {cartItems.map(item => ( <li key={item.id}> {item.name} - {item.price}元 <button onClick={() => dispatch(removeItem(item.id))}>删除</button> </li> ))} </ul> </div> ); }
四、状态管理的选择指南:到底用哪个?
很多开发者会困惑:useState
、useContext
、Redux Toolkit
到底怎么选?给你一张决策表:
场景 | 工具选择 |
---|---|
组件内部的局部状态 | useState |
层级较浅的跨组件通信 | props/useContext |
层级很深或全局状态 | useContext |
复杂应用(多模块、多状态) | Redux Toolkit |
五、组件开发的最佳实践
- 状态尽可能局部化:能放在子组件的状态,就不要提到父组件(比如输入框的内容,放在输入框组件内部比放在父组件更合理)。
- 用自定义Hook复用逻辑:把重复的状态逻辑抽成Hook,比如“获取窗口大小”:
import { useState, useEffect } from 'react'; export function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handleResize); // 组件卸载时移除事件监听 return () => window.removeEventListener('resize', handleResize); }, []); return size; } // 使用时直接调用Hook function WindowSize() { const { width, height } = useWindowSize(); return <p>窗口大小:{width}x{height}</p>; }
- 避免不必要的重新渲染:用
React.memo
包裹纯组件,防止因父组件重新渲染而不必要更新:// 用React.memo包裹子组件,只有props变化时才重新渲染 const MemoizedUserCard = React.memo(UserCard);
原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/180