Skip to content

react18

什么是 React?

  • React 是一个用于构建用户界面的 JavaScript 库

React 能干什么?

  • 可以通过组件化的方式构建 构建快速响应的大型 Web 应用程序

组件化

  • 组件化 把页面拆分为一个个组件,方便视图的拆分和复用,还可以做到高内聚和低耦合

diff 算法时间复杂度

  • 传统 diff 算法:

    • 通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3)
    • 如果要展示 1000 个节点,得执行上亿次比较。。即便是 CPU 快能执行 30 亿条命令,也很难在一秒内计算出差异。
  • React diff:

  • 首先,进行同级比较,并非循环比较。

  • 这样比较次数就降为一层一次,时间复杂度直接降为O(n)

  • 如果同级相同位置节点不一样,则直接删除替换,简单粗暴。

什么是 JSX

TIP

在 React17 以前,babel 是调用的 React.createElement,并且页面需要引入

在 React17 之后,babel 是调用的(react/jsx-runtimejsx)函数,并且页面无需引入

  • Jsx(JavaScript XML)是一个语法糖,是对 JavaScript 的扩展,即可以写 html 也可以写 js
  • 浏览器支持的是 ECMAScript 标准 ,所以代码在编译阶段会通过babel-preset-react转换成 ReactElement
  • ReactElement 就是我们所说的虚拟节点

createPortal

  • 将组件渲染到对应的容器中ReactDOM.createPortal(child, container)

虚拟的 dom 优缺点

优点

  • 处理了浏览器兼容性问题,避免用户操作真实 DOM
  • 内容经过了 XSS 处理,可以防范 XSS 攻击
  • 容易实现跨平台开发 Android、iOS、VR 应用
  • 更新的时候可以实现差异化更新,减少更新 DOM 的操作

缺点

  • 不利用 SEO
  • 首屏加载速度慢

函数组件和类组件的区别

特性函数组件类组件
定义方式JavaScript 函数ES6 类继承 React.Component
状态管理useState/useReducer Hooksthis.state + this.setState
生命周期useEffect Hook生命周期方法(componentDidMount等)
this 绑定this,避免绑定问题需要处理 this 绑定
代码复用自定义 Hooks高阶组件(HOC)或 Render Props
性能优化React.memo + useMemo/useCallbackPureComponent/shouldComponentUpdate
错误边界❌ 不支持(需配合类组件)✅ 支持(componentDidCatch
实例引用useRef + useImperativeHandle直接通过 ref 获取实例
心智模型函数式编程(无副作用)面向对象编程(实例化)

状态管理机制

jsx
// 函数组件(Hooks)
const Counter = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c+1)}>{count}</button>;
}

// 类组件
class Counter extends React.Component {
  state = { count: 0 };
  render() {
    return <button onClick={() => this.setState({count: this.state.count+1})>
      {this.state.count}
    </button>
  }
}
  • 核心差异:函数组件状态是闭包环境中的独立值,类组件状态是实例的 this.state 属性

生命周期实现

jsx
// 函数组件(useEffect)
useEffect(() => {
  // componentDidMount + componentDidUpdate
  fetchData();
  return () => { /* componentWillUnmount */ }
}, [dependencies]);

// 类组件
componentDidMount() { fetchData(); }
componentDidUpdate(prevProps) {
  if (prevProps.id !== this.props.id) fetchData();
}
componentWillUnmount() { cleanup(); }
  • 核心差异:函数组件通过声明式依赖数组控制副作用,类组件需要手动比较 prevProps

this 绑定问题

jsx
// 类组件典型问题
class Button extends React.Component {
  handleClick() {
    // this 可能为 undefined
    console.log(this.props);
  }

  // 解决方案1: 构造函数绑定
  constructor() {
    this.handleClick = this.handleClick.bind(this);
  }

  // 解决方案2: 箭头函数
  handleClick = () => { ... }
}
  • 函数组件优势:天然规避 this 绑定问题,所有值通过闭包获取

逻辑复用方式

jsx
// 函数组件(自定义 Hook)
const useLogger = (value) => {
  useEffect(() => {
    console.log("Value changed:", value);
  }, [value]);
};

// 类组件(HOC)
const withLogger = (Comp) => {
  return class extends React.Component {
    componentDidUpdate(prevProps) {
      console.log("Props changed:", this.props);
    }
    render() {
      return <Comp {...this.props} />;
    }
  };
};

React 错误边界处理

  • 目前还没有办法将错误边界编写为函数式组件。但是你不必自己编写错误边界类。例如,你可以使用 react-error-boundary 包来代替。
js
import React, { Component } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    // 这里可以添加错误日志上报逻辑
    console.error("Error caught by ErrorBoundary:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 显示备用 UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

function App() {
  return (
    <div>
      <ErrorBoundary>
        <h1>Hello, world!</h1>
      </ErrorBoundary>
    </div>
  );
}

React 单向数据流

  • React 的单向数据流是一种设计模式,它确保组件之间的数据流动是单向的,即从父组件流向子组件。这种模式有助于保持组件状态的一致性和可预测性,同时简化了组件之间的通信。
  • 如果子组件修改了父组件传递的数据,会导致数据流变得混乱,难以追踪和调试。因此,React 推荐使用 props 和 state 来管理组件之间的数据流动,确保数据流动是单向的。

受控组件&非受控组件

  • 优先使用受控组件,符合 React 设计原则
  • 必须操作 DOM 时,再使用非受控组件

受控组件

  • 受控组件是指由React组件的状态(state)来控制其表单元素的值或行为的组件。在受控组件中,表单元素的值(如 input、textarea、select 等)以及其他用户输入的操作,都通过React组件的状态来进行管理和更新

  • 受控组件的特点包括:

    • 1.状态控制:表单元素的值被保存在 React 组件的状态中,通过设置状态来控制表单元素的值。
    • 2.事件处理:通过监听表单元素的事件(如 onChange 事件),将用户输入的值更新到组件的状态中。
    • 3.数据流一致性:React 组件的状态是单一数据源,用于更新和渲染表单元素的值。通过确保状态与表单元素的值保持一致,可以实现数据的一致性和同步更新。

非受控组件

  • 非受控组件,将表单数据交给DOM节点来处理,可以使用Ref来获取非受控组件的数据,希望能够赋予表单一个初始值,但是不去控制后续的更新。可以采用 defaultValue 指定一个默认值

  • 非受控组件使用场景,必须手动操作 DOM 元素,state 实现不了,文件上传 <input type=file ref={fileRef}/>

js
import { useState } from "react";
const Controlled: React.FC = () => {
  const [state, setState] = useState < string > "hulei";
  const getValue = () => {
    console.log(state);
  };
  return (
    <>
      <div>受控组件</div>
      <input
        type="text"
        value={state}
        onChange={(e) => setState(e.target.value)}
      />
      <br />
      {state}
      <br />
      <button onClick={getValue}>获取值</button>
    </>
  );
};
js
import { useRef, useState } from "react";
const Uncontrolled: React.FC = () => {
  const [state, setState] = useState < string > "hulei";
  const inputRef = useRef < HTMLInputElement > null;
  const getValue = () => {
    // 通过DOM或者到值
    console.log(inputRef.current?.value);
  };
  return (
    <>
      <div>非受控组件</div>
      <input type="text" defaultValue={state} ref={inputRef} />
      <br />
      {state}
      <br />
      <button onClick={getValue}>获取值</button>
    </>
  );
};

React 时间分片的大概原理

  • 浏览器刷新频率为 60Hz也就是一秒钟 60 帧,大概 (1000/60)16.6 毫秒渲染一次,而 JS 线程渲染线程互斥的,所以如果 JS 线程执行任务时间超过 16.6ms 的话,就会导致掉帧,导致卡顿, React 利用空闲的时间进行更新,不影响 UI 渲染进
  • 把一个耗时任务切分成一个个小任务,分布在每一帧里的方式就叫时间切片

lifeofframe

为什么会出现 fiber

  • React 每次更新都是从根节点开始递归调度并且无法中断,如果 dom 树的结构比较复杂对应的虚拟节点计算会消耗对应的时间

  • 如果此时用户在进行表单填写操作,会感觉的卡顿,因为js渲染UI渲染是互斥

  • fiber 架构利用浏览器空闲时间(requestIdleCallback)执行任务,拒绝长时间占用主线程,放弃递归,只采用循环,因为循环可以被中断,任务拆分,将任务拆分成一个个小任务

fiber 数据结构

  • fiber 是一个链表的数据结构,他的 child 指向第一个子节点,sibling 指向最近弟弟,return 指向父 fiber,即使是执行被打断,也很容易恢复

Fiber结构

优先级

scheduler 优先级

  • scheduler 优先级有 5 种,每一个优先级对应一个过期时间,过期时间越短,对应优先级越高
js
// 无优先级
export const NoPriority = 0;
// 立刻执行优先级(等待时间-1ms)
export const ImmediatePriority = 1;
//用户阻塞操作优先级 用户点击 ,用户输入(等待时间250ms)
export const UserBlockingPriority = 2;
// 正常优先级(等待时间5000ms)
export const NormalPriority = 3;
// 低优先级(等待时间10000ms)
export const LowPriority = 4;
// 空闲优先级(没有过期时间)
export const IdlePriority = 5;

Lane

  • Lane 一共有 31 条车道,对应 5 种事件优先级
js
/**
 * 一共有31条车道
 */
const TotalLanes = 31;
//没有车道,所有位都为0
const NoLanes = 0b0000000000000000000000000000000;
const NoLane = 0b0000000000000000000000000000000;
//同步车道,优先级最高
const SyncLane = 0b0000000000000000000000000000001;
const SyncBatchedLane = 0b0000000000000000000000000000010;
//离散用户交互车道 click
const InputDiscreteHydrationLane = 0b0000000000000000000000000000100;
const InputDiscreteLanes = 0b0000000000000000000000000011000;
//连续交互车道 mouseMove
const InputContinuousHydrationLane = 0b0000000000000000000000000100000;
const InputContinuousLanes = 0b0000000000000000000000011000000;
//默认车道
const DefaultHydrationLane = 0b0000000000000000000000100000000;
const DefaultLanes = 0b0000000000000000000111000000000;
//渐变车道
const TransitionHydrationLane = 0b0000000000000000001000000000000;
const TransitionLanes = 0b0000000001111111110000000000000;
//重试车道
const RetryLanes = 0b0000011110000000000000000000000;
const SomeRetryLane = 0b0000010000000000000000000000000;
//选择性水合车道
const SelectiveHydrationLane = 0b0000100000000000000000000000000;
//非空闲车道
const NonIdleLanes = 0b0000111111111111111111111111111;
const IdleHydrationLane = 0b0001000000000000000000000000000;
//空闲车道
const IdleLanes = 0b0110000000000000000000000000000;
//离屏车道
const OffscreenLane = 0b1000000000000000000000000000000;

事件优先级

js
//离散事件优先级 click onchange
export const DiscreteEventPriority = SyncLane; //1
//连续事件的优先级 mousemove
export const ContinuousEventPriority = InputContinuousLane; //4
//默认事件车道
export const DefaultEventPriority = DefaultLane; //16
//空闲事件优先级
export const IdleEventPriority = IdleLane;

React 18 渲染流程核心思想

React 18 渲染流程的核心思想,我认为其根本目的是为了实现并发特性,从而提升应用的用户体验。它的核心在于将渲染工作拆分为两个截然不同的阶段:可中断的渲染阶段不可中断的提交阶段

首先,是渲染阶段(Render Phase)。 这个阶段的目标是“计算发生了什么变化”。它是一个纯 JS 的计算过程,可以被打断。

  1. setState、useState 触发更新后,React 会从根节点开始进行协调(Reconciliation)
  2. 它会对比新旧虚拟 DOM 树,并为发生变化的 Fiber 节点打上副作用标记(Flags)
  3. 这里有一个关键点:React 18 通过 subtreeFlags 等机制实现了副作用的高效冒泡,使得 React 可以快速知道一棵子树里是否有变化,无需遍历每一个节点,大大提升了协调效率。
  4. 正因为是纯计算,这个阶段可以被高优先级任务中断,等高优任务完成后,再回来继续计算或重新开始,这就是并发渲染的基石。

然后,是提交阶段(Commit Phase)。 这个阶段的目标是“将变化一次性、同步地更新到 DOM 上”,它不可中断,以保证数据一致性。它依次执行三个子阶段

  1. Before Mutation Phase

    • 在这个阶段,DOM 还没有被修改。可以同步地获取更新前的 DOM 快照,比如滚动条的高度等等
    • 对于类组件,这会同步调用 getSnapshotBeforeUpdate() 生命周期方法。这是组件在 DOM 可能更改前能捕获信息的最后机会。
  2. Mutation Phase(Mutation 阶段)

    • 实际操作 DOM。React 会执行插入、更新、删除等操作。
    • 对于函数组件,useLayoutEffect清理函数会在这个阶段同步执行。
    • 因为这个过程是同步且阻塞的,所以用户还看不到 UI 的变化。
  3. Layout Phase(Layout 阶段)

    • 同步执行 useLayoutEffect创建函数和类组件的 componentDidMount/Update 方法。
    • 随后,React 会切换当前树的指针,将 workInProgress tree 变为新的 current tree
    • Layout 完成后,浏览器会重新绘制,用户可以看到更新后的 UI。

最后,在所有这些同步操作完成后,React 会异步地触发 useEffect 的清理和创建函数,从而不阻塞浏览器的绘制。

Fiber 树构建流程

1. 初始化阶段

javascript
// 创建双缓存结构
const root = createFiberRoot(container);
// current: 当前显示的树
// workInProgress: 正在构建的树
root.current.alternate = workInProgress;
workInProgress.alternate = root.current;

2. 开始构建(Render Phase)

javascript
function performConcurrentWorkOnRoot(root) {
  // 构建 workInProgress 树
  renderRootSync(root, lanes);

  // 检查中断条件
  if (shouldYield()) {
    return performConcurrentWorkOnRoot; // 返回恢复函数
  }

  // 进入提交阶段
  commitRoot(root);
}

3. 深度优先遍历流程

dfs_react

4. beginWork 阶段(向下遍历)

javascript
function beginWork(current, workInProgress, renderLanes) {
  // 复用逻辑:检查是否可以复用 current 树节点
  if (current !== null && workInProgress.type === current.type) {
    // 复用 Fiber 节点(双缓存关键)
    cloneChildFibers(current, workInProgress);
  }

  // 根据组件类型处理
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, ...);
    case ClassComponent:
      return updateClassComponent(current, workInProgress, ...);
    case HostComponent:
      return updateHostComponent(current, workInProgress, ...);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, ...);
  }
}

5. completeWork 阶段(向上回溯)

javascript
function completeWork(current, workInProgress) {
  // 创建 DOM 节点(HostComponent 类型)
  if (workInProgress.tag === HostComponent) {
    const instance = createInstance(
      workInProgress.type,
      workInProgress.pendingProps
    );

    // 挂载到 workInProgress 树
    workInProgress.stateNode = instance;

    // 处理属性更新
    diffProperties(instance, workInProgress.pendingProps);
  }

  // 收集副作用(effect list)
  if (workInProgress.flags > PerformedWork) {
    // 添加到父节点的 effectList
    appendEffectToParent(workInProgress);
  }
}

6. 中断恢复机制

javascript
// 时间片检查(每5ms)
function shouldYield() {
  return performance.now() - startTime > 5 || Scheduler_shouldYield();
}

// 恢复时从断点继续
function resumeWork() {
  let node = workInProgress; // 保存的指针
  while (node !== null && !shouldYield()) {
    // 继续处理当前节点
    node = performUnitOfWork(node);
  }
}

7. 双缓存切换(Commit Phase)

javascript
function commitRoot(root) {
  // 步骤1:切换树指针
  root.current = root.finishedWork; // workInProgress → current

  // 步骤2:执行 DOM 操作
  commitMutationEffects(root);

  // 步骤3:同步更新 alternate 指针
  const current = root.current;
  const workInProgress = current.alternate;
  workInProgress.alternate = current;
  current.alternate = workInProgress;
}

React 18 新特性影响

  1. 并发渲染优化

    • 使用 startTransition 标记低优先级更新
    • 自动批处理:合并多个状态更新
  2. Offscreen API

    jsx
    <Offscreen mode="hidden">
      <ExpensiveComponent />
    </Offscreen>
    • 隐藏的组件保持双缓存状态
    • 复用已有的 Fiber 节点
  3. Suspense 增强

    • 流式渲染时暂停/恢复组件树
    • 部分子树可独立提交

总结要点

React 18 的 Fiber 树构建核心是双缓存架构

  1. 双树结构

    • current 树:当前显示的 UI
    • workInProgress 树:内存中构建的新树
    • 通过 alternate 指针相互连接
  2. 构建过程

    • 深度优先遍历(DFS)
    • beginWork:向下处理组件渲染/Diff(可复用节点)
    • completeWork:向上回溯创建 DOM/收集副作用
  3. 中断恢复

    • 每 5ms 检查 shouldYield()
    • 保存当前 workInProgress 指针
    • 时间片机制确保不阻塞主线程
  4. 提交阶段

    • 原子操作切换双缓存树指针
    • 同步更新 alternate 关系
    • 一次性提交 DOM 变更
  5. React 18 增强

    • 并发渲染:优先级驱动构建过程
    • Offscreen:维持隐藏组件的双缓存
    • Suspense:子树级的中断恢复

例如更新一个组件时:

  1. current.alternate 克隆新树
  2. 深度优先构建 workInProgress
  3. 时间片耗尽时保存进度
  4. 恢复时从中断节点继续
  5. 完成构建后切换双缓存指针

React 的 Diff 原理

  • React 的 Diff 算法是虚拟 DOM 的核心优化策略,通过高效的差异比较最小化 DOM 操作,提高性能。React 的 Diff 算法主要分为以下几部分:

    1、同层比较:仅比较同一层级的节点,不跨层级移动

    3、复用优先:当 key 和 type 相同时复用老 Fiber 节点,否则创建新的 Fiber 节点

    4、多场景优化:针对单节点和多节点不同处理策略

    diff_1

一、单节点 Diff 流程

diff2

  • 1、循环老的 Fiber 节点,如果新的虚拟节点的类型(type)key和老 Fiber 一致,复用老的 Fiber 节点,同时还需要把剩下的 Fiber 兄弟节点标记为删除
  • 2、如果新的虚拟节点的类型(type)key和老 Fiber 的不一致,需要创建新的 Fiber 节点,同时还需要把剩下的 Fiber 兄弟节点标记为删除
  • 3、如果 Fiber 循环完毕后,未能找到能够复用的 Fiber 节点,需要根据新的虚拟 DOM 创建新的 Fiber 节点
js
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
  let child = currentFirstChild;
  let deleted = false;

  while (child !== null) {
    // 1. 检查key和type是否匹配
    if (child.key === element.key && child.type === element.type) {
      // 1.1 复用节点
      const existing = useFiber(child, element.props);
      existing.return = returnFiber;

      // 1.2 删除后续兄弟节点
      let nextChild = child.sibling;
      while (nextChild) {
        deleteChild(returnFiber, nextChild);
        nextChild = nextChild.sibling;
      }

      return existing;
    }

    // 2. 不匹配则标记删除
    deleteChild(returnFiber, child);
    deleted = true;
    child = child.sibling;
  }

  // 3. 未找到匹配节点
  const created = createFiberFromElement(element);
  created.return = returnFiber;
  return created;
}

二、多节点 Diff 流程(多节点分为三种情况)

diff3

  • 1、老的 Fiber 全部能被复用,新的节点还没有遍历完成(新的节点比老的多)
  • 2、老的 Fiber 复用了部分,新的节点已经遍历完了(新的节点比老的少)
  • 3、老的 Fiber 和新的虚拟 DOM 都没有循环完毕(根据老的 Fiber 生成 map 对象,遍历还未完成的虚拟 DOM,最大程度的复用)

diff2

  • 第一轮比较 A 和 A,相同可以复用,更新,然后比较 B 和 C,key 不同直接跳出第一个循环
  • 把剩下 oldFiber 的放入 existingChildren 这个 map 中
  • 然后声明一个 lastPlacedIndex 变量,表示不需要移动的老节点的索引,默认为 0
  • 继续循环剩下的虚拟 DOM 节点,从 C 开始
  • 如果能在 map 中找到相同 key 相同 type 的节点则可以复用老 fiber,并把此老 fiber 从 map 中删除
  • 如果能在 map 中找不到相同 key 相同 type 的节点则创建新的 fiber 节点
  • 如果是复用老的 fiber,则判断老 fiber 的索引是否小于 lastPlacedIndex
  • 如果小于 lastPlacedIndex 则需要移动老 fiber,lastPlacedIndex 不变
  • 如果大于 lastPlacedIndex 则不需要移动老 fiber,更新 lastPlacedIndex 为老 fiber 的 index
  • 虚拟 DOM 循环结束后把 map 中所有的剩下的 fiber 全部标记为删除

循环链表

  • 实现思路:

    • 1、当 fiber 更新队列为空时,让新的队列queue1.next指向头节点,让头节点指向queue1
    • 2、当queue2进入队列时,让queue2.next指向头节点的next(queue1),让queue1.next指向queue2(形成循环链表结构),让头节点在指向 queue2
    • 3、当queue3进入队列时,让queue3.next指向头节点(queue2)的next(queue1),让头节点(queue2.next)指向queue3,让头节点在指向 queue3
  • 目的:即可以保证链表的顺序也可以减少放入队列的循环次数,在更新状态的时候,需要从第一个进入的队列开始遍历,这里在添加队列的时候只需要改变 2 个指针,在遍历的时候找最后一个队列的下一个就是头节点,并且让最后一个 next 指向 null

循环链表

合成事件

合成事件(SyntheticEvent)是 React 的核心特性之一,它创建了跨浏览器兼容的事件系统,解决了浏览器事件处理的兼容性问题,同时优化了性能。

  • 1、跨浏览器一致性:统一不同浏览器的事件处理接口
  • 2、性能优化:通过事件委托减少内存消耗
  • 3、便捷开发:提供更符合 React 理念的事件处理方式
  • 4、安全控制:防止事件滥用导致的 XSS 攻击

合成事件的实现原理

1. 事件注册

  • 在 React 应用初始化时,React 会基于ReactDOM.render的容器节点(通常是 root)来监听所有支持的事件类型(如clickchange等)。
  • 实际上,React 并不是在一开始就注册所有事件,而是采用惰性注册:当第一次在组件中声明某个事件(比如onClick)时,React 才会在 root 上注册该事件类型。

2. 事件存储

  • React 会维护一个映射,将事件类型组件的事件处理函数关联起来。这个映射存储在组件的 fiber 节点上(具体是在 fiber 的memoizedPropspendingProps中)。

3. 事件触发

当事件在 DOM 上触发时(如用户点击),事件会冒泡到 root 节点。

  • root 节点上绑定的事件监听器(由 React 注册)会被触发。
  • React 通过事件对象(原生事件)的event.target找到实际触发事件的 DOM 节点。
  • 然后,React 从这个 DOM 节点向上遍历,收集所有与事件类型相关的处理函数(例如,在onClick事件中,收集所有节点的onClick处理函数)。这个遍历过程会考虑事件是否被阻止冒泡(stopPropagation)。
  • 接着,React 构造一个合成事件对象(SyntheticEvent),并依次调用收集到的事件处理函数

4. 事件对象池

  • 为了提高性能,React 使用事件对象池。合成事件对象会被重用,在事件回调执行后,其属性会被置空。因此,如果需要在异步环境中使用事件对象(比如在 setTimeout 中),需要调用event.persist()来移除事件对象池中的引用,以便保留事件属性。

合成事件与原生事件的区别:

  • 合成事件的命名采用小驼峰(camelCase),而不是纯小写。
  • 在合成事件中,返回false不会阻止默认行为,必须显式调用preventDefault
  • 由于事件委托,在组件卸载后,React 会自动移除事件处理,避免内存泄漏
js
// 支持的事件类型
const allNativeEvents = ["click"];
const elementEventPropsKey = "__props";

const listeningMarker = `_reactListening` + Math.random().toString(36).slice(2);

// 事件映射
function getEventCallbackNameFromtEventType(eventType) {
  return {
    click: ["onClickCapture", "onClick"],
  }[eventType];
}

// 收集从目标元素到HostRoot之间所有目标回调函数
const collectPaths = (targetElement, container, eventType) => {
  const paths = {
    capture: [],
    bubble: [],
  };
  // 收集事件回调是冒泡的顺序
  while (targetElement && targetElement !== container) {
    // div.__props = {onClickCapture:fn,onClick:fn....}
    const eventProps = targetElement[elementEventPropsKey];
    if (eventProps) {
      const callbackNameList = getEventCallbackNameFromtEventType(eventType);
      // 取出映射的事件
      if (callbackNameList) {
        callbackNameList.forEach((callbackName, i) => {
          // react事件
          const eventCallback = eventProps[callbackName];
          if (eventCallback) {
            if (i === 0) {
              // 由于捕获的执行顺序是从上往下,所以是反向收集
              // 反向插入捕获阶段的事件回调
              paths.capture.unshift(eventCallback);
            } else {
              // 由于冒泡的执行顺序是从下往上,所以是正向收集
              // 正向插入冒泡阶段的事件回调
              paths.bubble.push(eventCallback);
            }
          }
        });
      }
    }
    targetElement = targetElement.parentNode;
  }
  return paths;
};

const dispatchEvent = (rootContainerElement, eventType, e) => {
  const targetElement = e.target; // 事件源

  if (targetElement === null) {
    console.error("事件不存在target", e);
    return;
  }
  // 从当前的事件源向上查找所有的,扑捉、冒泡事件,用于模拟原生的事件
  const { capture, bubble } = collectPaths(
    targetElement,
    rootContainerElement,
    eventType
  );
  // 合成原生事件
  const se = createSyntheticEvent(e);
  triggerEventFlow(capture, se);
  // 如果事件被阻止不需要在执行
  if (!se.__stopPropagation) {
    triggerEventFlow(bubble, se);
  }
};

export function listenToAllSupportedEvents(rootContainerElement) {
  //监听根容器,也就是div#root只监听一次
  if (!rootContainerElement[listeningMarker]) {
    rootContainerElement[listeningMarker] = true;
    allNativeEvents.forEach((eventType) => {
      // 注册代理事件
      rootContainerElement.addEventListener(eventType, (e) => {
        dispatchEvent(rootContainerElement, eventType, e);
      });
    });
  }
}

function createSyntheticEvent(e) {
  const syntheticEvent = e;
  syntheticEvent.__stopPropagation = false;
  // 阻止事件传递
  const originStopPropagation = e.stopPropagation;

  syntheticEvent.stopPropagation = () => {
    syntheticEvent.__stopPropagation = true;
    if (originStopPropagation) {
      originStopPropagation();
    }
  };

  return syntheticEvent;
}

// 执行react事件
const triggerEventFlow = (paths, se) => {
  for (let i = 0; i < paths.length; i++) {
    const callback = paths[i];
    callback.call(null, se);
    if (se.__stopPropagation) {
      break;
    }
  }
};

// 将支持的事件回调保存在DOM中
export const updateEventProps = (node, props) => {
  node[elementEventPropsKey] = node[elementEventPropsKey] || {};
  allNativeEvents.forEach((eventType) => {
    // 获取支持的事件类型
    const callbackNameList = getEventCallbackNameFromtEventType(eventType);
    if (!callbackNameList) {
      return;
    }
    // 事件映射
    callbackNameList.forEach((callbackName) => {
      if (Object.hasOwnProperty.call(props, callbackName)) {
        node[elementEventPropsKey][callbackName] = props[callbackName];
      }
    });
  });
  return node;
};

react16 版本事件 bug

  • 比如需要一个事件来控制一个元素的展示,在当前组件注册一个原生的 document 冒泡事件,在处理合成事件让元素展示的时候,如果原生事件处理的是隐藏,其结果就是元素无法展示

  • 出现的原因是 react 冒泡事件先注册,注册在 document,所以 react 冒泡事件先执行,后执行原生的 document 冒泡事件,所以无法展示,通过阻止默认行为(冒泡),无效,因为是同级节点,只能阻止上级

js
// react16版本
function App() {
  let [visibility, setVisibility] = useState(false);
  // 注册冒泡事件
  document.addEventListener("click", () => {
    setVisibility(false);
  });
  let handlerClick = (e: any) => {
    setVisibility(true);
    // 阻止默认行为(冒泡),只能阻止上级,不能阻止同级
    e.stopPropagation();
    // 阻止所有
    e.nativeEvent.stopImmediatePropagation();
  };

  return (
    <div onClick={handlerClick}>
      {visibility && "visibility"}
      点击
    </div>
  );
}

context 用作原理

  • 用作:数据共享,可以避免 props 一层成传递,可以实现跨组件数据共享

  • 原理

    • 1、在创建 createContext 时,会返回一个 context 对象,包含内容提供者(Provider)和消费者(Consumer),他们都有一个_context 属性,都指向同一个引用地址
    • 2、react 在渲染时,如果是 Provider 组件,会给组件_context._currentValue进行赋值,在渲染当前的 children 组件
    • 3、react 在渲染时,如果是 Consumer 组件,会取出_context._currentValue的值,传递给子组件(因为指向同一个引用地址,所以值是一样的)
    • 4、如果是类组件,有 contextType 属性,会对类上的 context 进行赋值
js
// 渲染provider
function mountProviderComponent(vdom) {
  let { type, props } = vdom; // {$$typeof: REACT_PROVIDER,_context: context,};
  // 引用赋值
  type._context._currentValue = props.value;
  // 用于下次新老做对比(渲染的其实是子元素)
  vdom.oldRenderVdom = props.children;
  if (!vdom.oldRenderVdom) return null;
  return createDOM(props.children);
}
js
// 处理函数类件
function mountClassComponent(vdom) {
  let { type: ClassComponent, props, ref } = vdom;
  let classInstance = new ClassComponent(props);
  vdom.classInstance = classInstance;
  // 如果有contextType,把context绑定在实例上
  if (ClassComponent.contextType) {
    classInstance.context = ClassComponent.contextType._currentValue;
  }
  // .....
  return dom;
}
js
function createContext() {
  let context = { $$typeof: REACT_CONTEXT };
  // 提供者
  context.Provider = {
    $$typeof: REACT_PROVIDER,
    _context: context,
  };
  // 消费者
  context.Consumer = {
    $$typeof: REACT_CONTEXT,
    _context: context,
  };
  return context;
}

this.state = {
  color: "red",
};
const Context = React.createContext();
<Context.Provider value={{red: this.state.color}}>
  <div>{this.state.color}</div>
</Context.Provider>


 <Context.Consumer>
    {(props) => {
      return (
        <div>
          <span style={{ color: props.red }}>main</span>
        </div>
      );
    }}
</Context.Consumer>

React 优化方案( shouldComponentUpdate,PureComponent,memo,useMemo,useCallback)

  • 类组件
    • shouldComponentUpdate 组件在更新之前会触发该生命周期,会把上次的 props、state 传递过来,可以根据自己的需求进行对比,如果返回 true 执行更新,如果 false 不更新组件
    • PureComponent 内部实现了 shouldComponentUpdate
  • 函数组件
    • memo 函数组件在更新之前,对比之前的 props,如果未发生变化不执行更新操作
      • 如果未提供自定义对比函数,走 memo 默认的 arePropsEqual,React 将对 props 进行浅层的对比。这意味着如果父组件重新渲染并传递相同的 props 对象引用,则子组件不会重新渲染
    • useMemo 缓存值
    • useCallback 缓存函数

React.memo 默认比较策略

  • React.memo 默认比较函数是浅比较,如果 props 是对象,比较的是引用地址,如果引用地址相同,则不更新组件,如果引用地址不同,遍历对象的第一层,如果 key,value 都相同,则不更新组件,如果不同,则更新组件
js
function shallowEqual(objA, objB) {
  // 1. 相同引用检查 不更新
  if (objA === objB) return true;

  // 2. 如果有一个是 null,更新
  if (
    typeof objA !== "object" ||
    objA === null ||
    typeof objB !== "object" ||
    objB === null
  ) {
    return false;
  }

  // 3. 键数量检查
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  if (keysA.length !== keysB.length) return false;

  // 4. 键值对检查(仅第一层)
  for (let i = 0; i < keysA.length; i++) {
    const key = keysA[i];

    // 检查键是否存在
    if (!Object.prototype.hasOwnProperty.call(objB, key)) {
      return false;
    }
    // {a:10,b:{}}
    // {a:10,b:{c:10}} // 不会触发更新,因为浅比较(第一层),比较的是引用地址
    if (objA[key] !== objB[key]) {
      return false;
    }
  }

  return true;
}

setState

  • 在 React 事件中调用 setState,会进行批量更新策略,多个 setState 会进行合并
  • 在原生事件和异步任务中调度 setState 不会走批处理,相等于同步执行,里面能获取到新的值
js
// 初始状态:count = 0
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });

// 最终结果:count = 1 (不是3!)
js
// 使用函数形式确保基于最新状态
this.setState((prevState) => ({ count: prevState.count + 1 }));
this.setState((prevState) => ({ count: prevState.count + 1 }));
this.setState((prevState) => ({ count: prevState.count + 1 }));

// 最终结果:count = 3 ✅
js
handleClick = () => {
  this.setState({ count: 42 });
  console.log(this.state.count); // 输出旧值,不是42!

  // 使用回调获取更新后状态
  this.setState({ count: 43 }, () => {
    console.log(this.state.count); // 输出43 ✅
  });
};

为什么会出现 hooks

  • 使得函数组件可以拥有类组件的特性,如状态管理、生命周期方法等,使得函数组件更加灵活和强大。
  • 使得函数组件可以更加简洁和易读,避免了类组件中繁琐的生命周期方法和 this 绑定问题。
  • 使得函数组件可以更加方便地复用状态逻辑,避免了类组件中繁琐的 mixin 和高阶组件等模式。
  • 使得函数组件可以更加方便地测试,避免了类组件中繁琐的 this

hooks 原理

TIP

类组件中的 setState 是状态合并策略 hooks 队列中有多个时,状态不会进行合并,是直接覆盖更新,后者覆盖前者

  • 1、React 在构建 Fiber 时,如果是函数组件首先会调用renderWithHooks进行初始化
  • 2、构建全局 hooks ReactCurrentDispatcher派发器,如果是首次渲染挂载 MountHooksDispatcherOnMount、如果是更新挂载 UpdateHooksDispatcherOnUpdate(派发器就是提供给组件调用的如:useState...)
  • 3、当函数组件在被调用时,如果是初次渲染,会根据调用的情况,构建 hooks 链表
  • 4、每一个 hook 都会创建一个更新的队列和一个 dispatchSetState 函数并且已经绑定 fiber, hook队列,并且 next 指向下一个 hook
  • 5、当 hook 被提交时,其实是调用的 hooks.dispatchSetState 函数,此时会将提交的 新的 State 存储在 hook 队列中,并且触发组件重新渲染
  • 6、当组件重新渲染时,React 会依据 hooks 链表,按照 hooks 的顺序依次执行对应的函数,从而实现对应的特性。

hooks 注意事项

  • 因为函数组件所有的 hook,以链表的形式存储的 fiber 中,当函数组件在更新的时候,会按照链表 next 一个个进行查找
  • 如果在条件中使用 hooks、或者在循环中使用 hooks,会导致无法获取到正确的 hook 链表,从而出现 bug

useMemo 和 useCallback 区别

  • 应用场景如需要缓存的函数,因为函数式组件每次任何一个 state 发生变化,会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费;另外还需要注意的是,useCallback 应该和 React.memo 配套使用,缺了一个都可能导致性能不升反而下降。

  • 相同点

    • 1、useMemo 和 useCallback 都是 reactHook 提供的两个 API,用于缓存数据,优化性能;
    • 2、两者接收的参数都是一样的,第一个参数表示一个回调函数,第二个表示依赖的数据。
    • 3、在依赖数据发生变化的时候,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用
  • 两者的区别

    • 1、useMemo 缓存的结果是回调函数中 return 回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态
    • 2、useCallback 缓存的结果是函数,主要用于缓存函数
js
function useMemo(factory, deps) {
  let hook =
    workInProgressFiber.alternate &&
    workInProgressFiber.alternate.hooks &&
    workInProgressFiber.alternate.hooks[hookIndex];
  let currentMemo = null;
  if (hook) {
    let [lastMemo, lastDeps] = hook;
    // 检测依赖的属性是否发生变化
    let same = deps.every((item, index) => item === lastDeps[index]);
    if (same) {
      // 依赖的属性未发生变化返回之前的值
      currentMemo = lastMemo;
    } else {
      // 返回新的值
      currentMemo = factory();
    }
  } else {
    // 创建新的
    currentMemo = factory();
  }
  workInProgressFiber.hooks[hookIndex++] = [currentMemo, deps];
  return currentMemo;
}
js
function useCallback(callback, deps) {
  let hook =
    workInProgressFiber.alternate &&
    workInProgressFiber.alternate.hooks &&
    workInProgressFiber.alternate.hooks[hookIndex];
  let currentCallback = null;
  if (hook) {
    let [lastCallback, lastDeps] = hook;
    let same = deps.every((item, index) => item === lastDeps[index]);
    if (same) {
      currentCallback = lastCallback;
    } else {
      currentCallback = callback;
    }
  } else {
    currentCallback = callback;
  }
  workInProgressFiber.hooks[hookIndex++] = [currentCallback, deps];
  return currentCallback;
}

useEffect 和 useLayoutEffect

1. useEffect(异步执行)

  • 触发时机:在浏览器完成 绘制(Paint)后异步执行
  • 使用场景
    • 数据获取(API 请求)
    • 事件监听/订阅
    • 非紧急的 DOM 操作
    • 第三方库集成
    • 性能分析/埋点
  • 特点
    • 不会阻塞浏览器渲染
    • 用户可能先看到变化前的 UI,再看到更新后的 UI(可能产生闪烁)
jsx
// 典型用例:数据获取
useEffect(() => {
  fetchData().then((data) => setData(data));
}, []);

2. useLayoutEffect(同步执行)

  • 触发时机:在 DOM 更新后、浏览器 绘制(Paint)前同步执行
  • 使用场景
    • 读取/修改 DOM 布局(如元素尺寸、位置)
    • 同步更新 UI 避免视觉闪烁
    • 需要基于 DOM 计算的动画
  • 特点
    • 阻塞浏览器绘制
    • 保证用户看到的是最终 UI(避免中间状态)
jsx
// 典型用例:测量 DOM 元素
useLayoutEffect(() => {
  const { width } = divRef.current.getBoundingClientRect();
  setWidth(width); // 在绘制前更新尺寸
}, []);

⚠️ 错误使用示例

jsx
// ❌ 错误:在 useEffect 中修改布局导致闪烁
useEffect(() => {
  buttonRef.current.style.padding = "10px 20px"; //
js
function useEffect(callback, deps) {
  let hook =
    workInProgressFiber.alternate &&
    workInProgressFiber.alternate.hooks &&
    workInProgressFiber.alternate.hooks[hookIndex];

  if (hook) {
    let [lastCallback, lastDeps] = hook;
    let same = deps && deps.every((item, index) => item === lastDeps[index]);
    // 如果关联的deps未发生变化不做任何操作
    if (same) {
      workInProgressFiber.hooks[hookIndex++] = [lastCallback, deps];
    } else {
      // 如果发生变化,执行销毁的回调
      lastCallback && lastCallback();
      setTimeout(() => {
        // 存储新的
        workInProgressFiber.hooks[hookIndex++] = [callback(), deps];
      });
    }
  } else {
    setTimeout(() => {
      // callback 返回的函数,在下次渲染前执行
      workInProgressFiber.hooks[hookIndex++] = [callback(), deps];
    });
  }
}
js
function useLayoutEffect(callback, dependencies) {
  let hook =
    workInProgressFiber.alternate &&
    workInProgressFiber.alternate.hooks &&
    workInProgressFiber.alternate.hooks[hookIndex];
  if (hook) {
    let [lastCallback, lastDeps] = hook;
    let same =
      dependencies &&
      dependencies.every((item, index) => item === lastDeps[index]);
    if (same) {
      workInProgressFiber.hooks[hookIndex++] = [lastCallback, dependencies];
    } else {
      lastCallback && lastCallback();
      // DOM 更新完成后,浏览器绘制之前
      queueMicrotask(() => {
        lastCallback = callback();
        workInProgressFiber.hooks[hookIndex++] = [lastCallback, dependencies];
      });
    }
  } else {
    // DOM 更新完成后,浏览器绘制之前
    queueMicrotask(() => {
      workInProgressFiber.hooks[hookIndex++] = [callback(), dependencies];
    });
  }
}

useRef&createRef 区别

  • 1、都可以在组件中创建ref对象createRef组件函数组件都可以使用,useRef只能在函数组件中使用
  • 2、在函数组件使用createRef时,每次组件更新都会重新去初始化(指向不同的引用地址)
  • 3、useRef存储在fiber链表中,每次组件在更新不会重新去初始化(指向相同的引用地址)
js
// mount 阶段
function mountRef<T>(initialValue: T): {| current: T |} {
  // 获取 hook 对象
  const hook = mountWorkInProgressHook();
  const ref = { current: initialValue };
  if (__DEV__) {
    Object.seal(ref);
  }
  // 存储
  hook.memoizedState = ref;
  return ref;
}

// update 阶段
function updateRef<T>(initialValue: T): {| current: T |} {
  const hook = updateWorkInProgressHook();
  // 返回之前的引用
  return hook.memoizedState;
}

useStateuseReducer 使用场景

1. useState - 简单状态管理

使用场景

  • 管理单个简单值(字符串、数字、布尔值)
  • 处理独立状态(无复杂依赖关系)
  • 组件内轻量级状态(少量状态变量)
  • UI 元素状态(输入框值、开关状态等)

2. useReducer - 复杂状态逻辑

使用场景

  • 管理关联状态组(多个相互影响的状态)
  • 处理复杂状态更新逻辑
  • 需要状态历史追溯的场景
  • 购物车、分步表单

📌 使用原则指南

优先选择 useState

  1. 状态是独立的基本类型(string/number/boolean)
  2. 状态更新逻辑简单(直接赋值)
  3. 组件内状态数量较少(≤3 个)

必须使用 useReducer

  1. 状态是相互关联的对象(如用户资料{name, age, avatar})
  2. 下一个状态依赖前一个状态(如计数器队列)
  3. 需要处理复杂状态转换(如购物车、表单多步骤)
  4. 需要跨组件/钩子共享状态逻辑(可复用 reducer)
  5. 大型组件需要优化更新性能(避免多次渲染)

💡 总结选择策略

场景推荐 Hook
表单输入控制useState
开关/复选框状态useState
简单计数器useState
购物车/复杂业务对象useReducer
多步骤表单useReducer
需要状态历史记录的功能useReducer
全局状态共享(中小型应用)useReducer + Context

useRefuseState 区别

  • useState 在更新的时候导致组件重新渲染
  • useRef 在更新的时候不会导致组件重新渲染

useRef 可以解决闭包陷阱问题

js
// 1、先点击第一个按钮6次
// 2、在点击第二个按钮1次
// 3、立马在点击第一个按钮4次
import { useState } from "react";
const App = () => {
  // setState更新会导致组件更新
  const [state, setState] = useState(0);
  function handleClick() {
    setTimeout(() => {
      // 4、由于闭包的特性,当前的值是6,所以打印6
      console.log(state);
    }, 3000);
  }
  return (
    <>
      {/* setState更新会导致组件更新所以是10 */}
      <button onClick={() => setState(state + 1)}>{state}</button>
      <button onClick={handleClick}>print</button>
    </>
  );
};
export default App;
js
// 1、先点击第一个按钮6次
// 2、在点击第二个按钮1次
// 3、立马在点击第一个按钮4次
import { useRef } from "react";
const App = () => {
  const state = useRef(0);
  function handleClick() {
    setTimeout(() => {
      // 4、由于都是指向同一个引用地址所以是10
      console.log(state.current);
    }, 3000);
  }
  return (
    <>
      <button
        onClick={() => {
          state.current++;
        }}
      >
        {/* useRef值的改变不会导致组件更新,所以一直是0 */}
        {state.current}
      </button>
      <button onClick={handleClick}>print</button>
    </>
  );
};
export default App;

如何拿到 useState 更新的值

  • 1、可通过useEffect,将更新值作为依赖条件
  • 2、在更新 useState 之前,先计算值在更新 useState

forwardRef(组件转发)

WARNING

  • 如果对函数组件,使用ref会抛出如下警告,因为函数组件类型组件不同,没有实例所以对函数组件直接使用ref是毫无意义的
  • Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
  • forwardRef 可以让父组件的ref获取到子组件的 DOM,同时也可以配合useImperativeHandle,将子组件的函数暴露给父组件ref
js
import React, { useEffect, useRef, forwardRef } from "react";
const InputText = (props: any, ref: any) => {
  return (
    <>
      <div>
        <input ref={ref} />
      </div>
    </>
  );
};
const ForwardInput = forwardRef(InputText);
const App: React.FC = () => {
  const inputRef = useRef < HTMLInputElement > null;
  useEffect(() => {
    if (inputRef.current) {
      // 获取到子组件的input
      inputRef.current.focus();
    }
  });
  return (
    <>
      <ForwardInput ref={inputRef}></ForwardInput>
    </>
  );
};
export default App;
  • 1、forwardRef 是一个函数组件,它接收一个组件,返回一个对象,包含$$typeof类型,和render(render 是外部传递的组件),
  • 3、当组件在渲染的时候,内部会保留父组件传递的ref的引用,如果是$$typeof类型是REACT_FORWARD_REF,调用render渲染组件,并且把 props,ref 传递给对应的组件
js
function forwardRef(render) {
  return {
    $$typeof: REACT_FORWARD_REF,
    render,
  };
}

if (type && type.$$typeof === REACT_FORWARD_REF) {
  return mountForwardComponent(vdom);
}

function mountForwardComponent(vdom) {
  const { type, props, ref } = vdom;
  const renderVdom = type.render(props, ref);
  if (!renderVdom) return null;
  return createDOM(renderVdom);
}

useImperativeHandle

  • 1、useImperativeHandle 其实就是一个普通的函数,接受2个参数一个是父组件的ref,另外是一个函数返回一个对象
  • 2、当 useImperativeHandle 在被调用时,内部会调用第二个参数将返回的对象,存储在父组件ref.current
  • 3、因为是同一个引用地址,所以通过父组件的ref.current能够调用到子组件的定义的函数
js
import React, {
  useEffect,
  useRef,
  forwardRef,
  useImperativeHandle,
} from "react";

interface IInputText {
  onClick: () => void;
}

const InputText = (props: any, ref: any) => {
  useImperativeHandle(ref, () => {
    return {
      onClick: () => {
        console.log("子组件的函数");
      },
    };
  });
  return (
    <>
      <div></div>
    </>
  );
};
const ForwardInput = forwardRef(InputText);
const App: React.FC = () => {
  const inputRef = useRef < IInputText > null;
  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.onClick();
    }
  });
  return (
    <>
      <ForwardInput ref={inputRef}></ForwardInput>
    </>
  );
};
export default App;
js
// 实现
export function useImperativeHandle(ref, handler) {
  ref.current = handler();
}

react 新增了什么生命周和删除了什么生命周期,为什么要删除

  • 1、componentWillMount、2、componentWillUpdate 3、componentWillReceiveProps
  • React 废弃的这三个生命周期函数,大都是因为新版本的异步渲染,在调用 render 生成虚拟 DOM 阶段,由于更高级的操作到来而被打断,导致 render 之前的操作都会重来。使得之前的逻辑可能会被重复调用而弃用。

Vue vs React

  • Vue 使用模版拥抱 html

  • Vue 是真正意义上的做到了组件级更新,每一个组件就对应一个渲染的 effect,让响应式数据去收集 effect

  • Vue 采用的是递归的方式来渲染页面不可中断

  • React 使用的 JSX 拥抱 JS

  • React 更新都是从跟节点开始调度,会讲一个大的任务拆分成多个小的任务单元

  • React 更新策略是循环的方式,有任务优先级的概念,可中断执行

原子组件

原子组件是前端工程领域的重要设计思想,源自原子设计理论(Atomic Design)。它将 UI 拆分为不可再分的原子单元,通过组合构建复杂界面。以下是其核心优势:

🧱 一、模块化设计优势

  1. 超高复用性

    • 原子组件(按钮/输入框/标签)可跨项目复用
    • 减少重复代码量高达 60-80%
    jsx
    // 原子按钮组件 - 全站复用
    const Button = ({ children, variant }) => (
      <button className={`btn-${variant}`}>{children}</button>
    );
  2. 一致性保障

    • 统一设计规范(颜色/间距/交互)
    • 避免不同页面出现风格差异
    jsx
    // 所有输入框保持相同样式和行为
    <TextInput placeholder="邮箱" />
    <TextInput placeholder="密码" type="password" />

⚙️ 二、工程效率提升

  1. 开发提效

    • 新功能通过组合现有原子快速搭建
    • 团队成员并行开发不同原子单元
    jsx
    // 分子组件 = 原子组合
    const SearchBar = () => (
      <div className="search-bar">
        <TextInput />
        <Button variant="search">搜索</Button>
      </div>
    );
  2. 维护简化

    • 修改原子属性全局生效
    • 故障定位更精准(问题隔离在特定原子)
    diff
    // 修改主色只需调整原子样式
    .btn-primary {
    -  background: blue;
    +  background: #1677ff;
    }

🧪 三、质量保障机制

  1. 可测试性增强

    • 原子组件可独立单元测试
    • 测试用例更聚焦核心功能
    jsx
    // 按钮组件测试用例
    test("按钮点击触发回调", () => {
      const onClick = jest.fn();
      render(<Button onClick={onClick}>OK</Button>);
      userEvent.click(screen.getByText("OK"));
      expect(onClick).toHaveBeenCalled();
    });
  2. 性能优化

    • 精细化的按需加载
    • 避免无关代码打包
    jsx
    // 动态加载复杂原子组件
    const HeavyChart = React.lazy(() => import("./Chart"));

🎨 四、设计协作优化

  1. 设计-开发无缝对接

    • 设计稿直接映射原子组件库
    • Figma/Sketch 设计系统自动同步
  2. 视觉一致性

    • 间距系统:4px基准倍数的原子间距
    • 颜色系统:有限的主色/辅助色原子集合

💎 总结:原子组件核心价值

维度收益
开发效率组件复用率提升 300%+
维护成本修改范围减少 70%
产品质量Bug 率下降 40%-60%
团队协作设计-开发对接时间缩短 50%
长期演进系统可扩展性提升 200%

📌 黄金准则:当同一 UI 元素出现 3 次以上时,应抽象为原子组件