React学习笔记(二)

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):

  1. 只对同级节点比(不跨层移动)
  2. 不同类型元素 → 整棵子树销毁重建
  3. 兄弟节点用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
相关推荐
用户7678797737321 天前
后端转全栈之Next.js后端及配置
react.js·next.js
Johnny_FEer1 天前
什么是 React 中的远程组件?
前端·react.js
艾小码1 天前
用了这么久React,你真的搞懂useEffect了吗?
前端·javascript·react.js
江城开朗的豌豆1 天前
React 跨级组件通信:避开 Context 的那些坑,我还有更好的选择!
前端·javascript·react.js
江城开朗的豌豆1 天前
Redux 与 MobX:我的状态管理选择心路
前端·javascript·react.js
薛定谔的算法2 天前
低代码编辑器项目设计与实现:以JSON为核心的数据驱动架构
前端·react.js·前端框架
小lan猫2 天前
React学习笔记(一)
前端·react.js
江城开朗的豌豆2 天前
解密React虚拟DOM:我的高效渲染秘诀 🚀
前端·javascript·react.js
前端人类学2 天前
React框架详解:从入门到精通(详细版)
react.js·redux