React 组件可以理解成一个根据输入计算界面的函数:
界面 = f(props, state)props 是父组件提供的输入,state 是组件拥有的可变状态。状态改变后,React 会重新执行组件函数,比较新旧结果,然后把必要变化提交到真实 DOM。
State 是一次渲染的快照
考虑下面的代码:
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setCount(count + 1);
}
return ;
}点击后结果通常是 1,而不是 2。原因是两次表达式都读取本次渲染中的同一个 count 快照,也就是 0。
如果下一次状态依赖上一次结果,应使用函数更新:
setCount(current => current + 1);
setCount(current => current + 1);React依次处理更新队列,第二个函数会接收到第一个函数产生的结果,因此最终得到 2。
Props、State 与单向数据流
父组件管理自己的 state,并将数据作为 props 传给子组件:
function App() {
const [name, setName] = useState("Lin");
return (
setName("Ming")}
/>
);
} 子组件不能直接修改 props,但可以调用父组件传入的回调,请求父组件更新状态。
如果某个状态只有一个组件需要,就放在该组件内部;如果多个组件需要共享,就把状态放到它们最近的共同父组件中。这叫状态提升。
它的价值是让数据只有一个真实来源,使修改路径清楚,也让组件更容易复用和测试。
Effect 用来同步外部系统
组件渲染应该尽量保持为纯计算。定时器、事件监听、WebSocket 和第三方组件等外部系统,需要通过 useEffect 同步:
useEffect(() => {
const id = setInterval(() => {
console.log("tick");
}, 1000);
return () => {
clearInterval(id);
};
}, []);Effect 在界面提交后执行。返回的清理函数负责撤销本次 Effect 创建的资源:
setInterval <-> clearInterval
addEventListener <-> removeEventListener
WebSocket connect <-> close
订阅 <-> 取消订阅依赖变化时,React会先清理上一次 Effect,再使用新依赖建立新的 Effect。组件卸载时也会执行清理。
由现有 props 和 state 能直接计算出的值,通常不需要 Effect。Effect主要用于让 React 与外部系统保持同步。
key 表示组件身份
渲染列表时,React使用 key 判断两次渲染中的元素是否代表同一个对象:
users.map(user => (
)); 稳定的 ID 能让 React 在列表排序后仍然识别每一项。如果使用数组下标,当项目插入、删除或换序时,组件状态可能被错误地保留到另一个项目上。
因此:
key={user.id} // 推荐
key={index} // 动态列表中有风险
key={Math.random()} // 每次变化,会破坏组件身份React的核心并不是“自动修改 HTML”,而是根据 props 和 state 重新计算界面,通过 Effect 管理外部同步,再利用稳定的组件身份准确提交变化。理解这些机制后,Hooks 和组件设计就会变得更容易判断。

没有回应