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
相关推荐
努力往上爬de蜗牛20 小时前
react native token失效 刷新机制
javascript·react native·react.js
梦6501 天前
React 简介
前端·react.js·前端框架
一只小阿乐1 天前
react 中的判断显示
前端·javascript·vue.js·react.js·react
光影少年1 天前
useMemo 和 React.memo区别
前端·react.js·前端框架
小沐°1 天前
React-页码组件
前端·javascript·react.js
消失的旧时光-19431 天前
Flutter 与 React/Vue 为什么思想一致?——声明式 UI 体系的深度对比(超清晰版)
vue.js·flutter·react.js
e***U8201 天前
前端路由懒加载实现,React.lazy与Suspense
前端·react.js·前端框架
terminal0072 天前
浅谈useRef的使用和渲染机制
前端·react.js·面试
weixin79893765432...2 天前
React 性能优化
react.js·性能优化
Dontla2 天前
React Tailwind CSS div布局demo
前端·css·react.js