React生命周期
React 的"生命周期"指的是 组件从诞生到死亡的全过程,以及在这个过程中 React 自动调用的那些回调函数 / Hook。
函数组件用 Hook(useEffect 等)表达生命周期,类组件用固定的成员函数;核心都是"挂载 → 更新 → 卸载"三个阶段。
函数组件(主流)
| 阶段 | 对应 Hook | 触发时机 | 常见用途 | 
|---|---|---|---|
| 挂载 | useEffect(..., []) | 组件第一次渲染到屏幕后 | 发请求、开定时器、绑定事件 | 
| 更新 | useEffect(..., [dep1, dep2]) | 指定依赖变化并渲染后 | 发请求、根据新 props 重新计算 | 
| 卸载 | useEffect(() => { return () => { ... } }, []) | 组件即将被移除 | 清定时器、解绑事件、取消请求 | 
| 每次渲染后 | useEffect(...)不加依赖 | 每次渲染完成后 | 日志、埋点 | 
基本模版
            
            
              jsx
              
              
            
          
          useEffect(() => {
  // 1. 挂载 + 更新(依赖改变)时执行
  console.log('did mount / did update');
  return () => {
    // 2. 下一次 effect 运行前 或 卸载时执行
    console.log('cleanup: will unmount / prev cleanup');
  };
}, [dep]);类组件生命周期示例
            
            
              jsx
              
              
            
          
          function Demo({ id }) {
  useEffect(() => {
    // componentDidMount
    console.log('挂载/更新 id=', id);
    return () => {
      // componentWillUnmount
      console.log('卸载/清理 id=', id);
    };
  }, [id]);
}类组件(老项目/遗留项目)
三大阶段&经典方法
| 阶段 | 方法 | 说明 | 
|---|---|---|
| 挂载 | constructor | 初始化 state、绑定事件 | 
| static getDerivedStateFromProps | 罕见,根据 props 计算 state | |
| render | 返回 JSX | |
| componentDidMount | 第一次渲染完成;发请求、开定时器 | |
| 更新 | static getDerivedStateFromProps | 同上 | 
| shouldComponentUpdate | 返回 false 可跳过渲染(性能优化) | |
| render | 重新渲染 | |
| getSnapshotBeforeUpdate | 在 DOM 更新前获取滚动位置等信息 | |
| componentDidUpdate | DOM 更新完成后;可发请求、比较 prevProps | |
| 卸载 | componentWillUnmount | 清理工作 | 
过时 API(16.3 已废弃带UNSAFE_)
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
新项目绝对不要使用;旧项目迁移可加UNSAFE_前缀兼容
函数组件 vs 类组件映射速查
| 类生命周期 | 函数组件等价写法 | 备注 | 
|---|---|---|
| componentDidMount | useEffect(()=>{...}, []) | 仅执行一次 | 
| componentDidUpdate | useEffect(()=>{...}, [dep])或useEffect(...) | 加依赖可模拟 | 
| componentWillUnmount | useEffect(()=>{ return ()=>{...} }, []) | cleanup 函数 | 
| shouldComponentUpdate | React.memo+ 自定义比较 /useMemo | 性能优化 | 
常见误区
- useEffect不是生命周期"一对一":它同时覆盖"didMount + didUpdate + willUnmount"的任意组合,看依赖数组。
- cleanup不只卸载才执行 :依赖变化导致- effect重新运行前,先运行上一次- cleanup。
- 异步请求别直接setState:组件已卸载再setState会报警告 → 在cleanup里取消请求/标记废弃。
React的Diff算法
React 的 diff 算法 是 Virtual DOM 的 reconciliation(协调)引擎,用来以最小代价把旧 DOM 树变成新 DOM 树。
"用 O(n) 时间,把两次渲染的差异算出来,再批量打补丁。"
背景
- 每次setState会生成一棵新的Virtual DOM树(JS 对象)。
- 直接按新树销毁再重建真实 DOM → 性能爆炸。
- 于是React在内存里比两棵树,只改真正变化的部分。
三个经典约束(Trade-off)
React 故意给自己加了 三条强假设,把 O(n³) 的完全对比降成 O(n):
- 只对同级节点比(不跨层移动)
- 不同类型元素 → 整棵子树销毁重建
- 兄弟节点用key标识身份,维持复用
算法流程(自顶向下,深度优先)
层级对比(Tree Diff)
- 同层节点按顺序比,不回头、不跨层。
- 发现标签名不同(如<div>→<p>)→ 拆掉旧树,新建新树,子节点全部丢弃不复用。
组件对比(Component Diff)
- 同类型组件 → 继续比它的render结果;
- 不同类型 → 直接unmount旧组件,mount 新组件。
元素对比(Element Diff)------ 兄弟列表最核心
对同层兄弟进行三趟指针扫描:
| 场景 | 行为 | 
|---|---|
| 旧列表有,新列表也有 → key 相同且类型相同 | 复用旧节点,仅移动位置 | 
| 旧列表有,新列表无 | 删除 | 
| 旧列表无,新列表有 | 新增 | 
移动判断:用最长递增子序列(LIS) 算法,找出最少 DOM 移动次数。
Key 的作用
示例代码:
            
            
              jsx
              
              
            
          
          // 旧
<ul>
  <li key="a">A</li>
  <li key="b">B</li>
</ul>
// 新
<ul>
  <li key="b">B</li>
  <li key="c">C</li>
</ul>- 有 key → React 知道 b 只是顺序变,a 被删,c 是新增 → 只做一次插入 + 一次删除。
- 用索引当 key → 看起来"省内存",实则全部复用失败,可能出现输入框错位、动画异常等 bug。 key 必须稳定、唯一、可预测(数据库 id 最佳)。
伪代码
            
            
              js
              
              
            
          
          function diffChildren(oldVNodes, newVNodes, parentDom) {
  let oldStart = 0, newStart = 0;
  let oldEnd = oldVNodes.length - 1;
  let newEnd = newVNodes.length - 1;
  while (oldStart <= oldEnd && newStart <= newEnd) {
    if (sameNode(oldVNodes[oldStart], newVNodes[newStart])) {
      patch(oldVNodes[oldStart], newVNodes[newStart]); // 复用
      oldStart++; newStart++;
    } else if (sameNode(oldVNodes[oldEnd], newVNodes[newEnd])) {
      patch(...);
      oldEnd--; newEnd--;
    } else {
      // 乱序 → 建 map + LIS 移动
      mapRemainingOldNodes();
      moveOrInsert();
    }
  }
  // 新增剩余 / 删除剩余
}真实 DOM 打补丁(Commit 阶段)
diff 完后得到一串最小操作队列(insert、move、update、remove),进入不可中断的 Commit 阶段,一次性应用:
- 更新文本 / 属性
- 插入/移动/删除节点
- 触发 useEffect / componentDidUpdate
Fiber 之后的优化
React 16+ Fiber 架构把 diff 过程切成可中断的小任务:
- render 阶段(找 diff)可打断、可调度优先级;
- commit 阶段(应用 diff)同步执行,保证一致性。
常见误区
| 误区 | 正解 | 
|---|---|
| "diff 对比真实 DOM" | 始终对比 Virtual DOM(JS 对象) | 
| "key 用 index 就行" | 会导致全部节点复用失败 | 
| "跨层移动也高效" | 超三层移动 React 直接销毁重建整棵子树 | 
| "diff 完立即看到页面" | 中间还有批量更新、调度、paint |