首页首页
前端
非前端
辅助
Github
前端
非前端
辅助
Github
  • 前端基础

    • HTML基础
    • CSS基础
    • JS基础
    • ES6基础
    • HTTP基础
    • 前端缓存
    • 页面性能
    • 数据结构基础
    • 我的文章
  • 前端进阶

    • Vue2基础
    • Vue2进阶
    • Vue3基础
    • Vue3进阶
    • React基础
    • React进阶
    • React18新特性
    • Vue和React对比
    • RN基础
    • RN环境搭建和打包发布
    • 打包工具
    • TS基础
    • Nuxt基础
    • 小程序基础
    • 微前端基础
    • uni-app基础
    • 业务相关
  • 前端代码练习

    • CSS代码练习
    • JS代码练习
    • 算法代码练习
  • 前端代码技巧

    • 工具库
    • 工具函数
    • CSS动画库
    • CSS代码技巧
    • JS代码技巧
    • 项目技巧

1.Class生命周期⭐️

在 ​​React 16.3​​ 之后,部分旧生命周期方法(如 componentWillMount)被废弃,并引入了新的替代方法。

生命周期阶段概览

  • React Class 组件的生命周期可以分为 ​​3 个阶段​​:
    • ​挂载阶段(Mounting)​​​:组件首次被创建并插入 DOM。
    • ​​​更新阶段(Updating)​​​:组件因 props 或 state 变化而重新渲染。
    • ​​卸载阶段(Unmounting)​​​:组件从 DOM 中移除。

此外,React 还提供了 ​​错误处理​​ 生命周期方法(如 componentDidCatch)。

一. 挂载阶段(Mounting)​

1. constructor(props)​​

  • ​调用时机​​:组件初始化时调用。
  • 用途​​:
    • 初始化 state
    • 绑定方法(如 this.handleClick = this.handleClick.bind(this))
  • ⚠️ 注意​​:
    • 必须调用 super(props),否则 this.props 会是 undefined。
    • ​​不要调用 setState​​​,否则会触发额外渲染。
constructor(props) {
  super(props);
  this.state = { count: 0 };
  this.handleClick = this.handleClick.bind(this);
}

2. static getDerivedStateFromProps(nextProps, prevState)​​(不常用)

  • ​调用时机​​:在 render() 之前调用,​​​每次渲染都会触发​​​(包括初始挂载和更新)。
  • ​用途​​:根据 props 的变化更新 state(如 <Modal visible={propVisible}>)。
  • ⚠️ 注意​​:
    • 必须是 ​​​静态方法​​(static)​。
    • ​​避免滥用​​​,通常可以用 useEffect 替代。
static getDerivedStateFromProps(props, state) {
  if (props.userId !== state.userId) {
    return { userId: props.userId }; // 返回新的 state
  }
  return null; // 不更新 state
}

3. componentDidMount()

  • ​调用时机​​:组件挂载到 DOM 后调用 ​​仅执行一次​​​。
  • ​用途​​:
    • 发起 ​​​API 请求​​​
    • 添加 ​​​事件监听​​​(如 window.addEventListener)
    • 操作 ​​DOM​​(如 document.getElementById)
  • ⚠️ 注意​​:
    • ​可以安全调用 setState,但会触发额外渲染(通常用于数据加载)
componentDidMount() {
  fetch('/api/data').then(res => this.setState({ data: res.data }));
  window.addEventListener('resize', this.handleResize);
}

二. 更新阶段(Updating)​

  • 触发更新的条件:
    • props 变化
    • state 变化(this.setState)
    • 父组件重新渲染(即使 props 没变)

1. static getDerivedStateFromProps(nextProps, prevState)​​

  • 同挂载阶段,在 props 变化时更新 state。

替代了被废弃的 componentWillReceiveProps,设计目的是使组件的行为更加可预测。

static getDerivedStateFromProps(nextProps, prevState) {
  // 根据 nextProps 和 prevState 返回新的 state 或 null
  if (nextProps.value !== prevState.value) {
    return { value: nextProps.value }; // 更新 state
  }
  return null; // 不更新 state
}
  • 返回值​​:
    • ​对象​​:合并到当前 state 中(类似 setState)。
    • ​​null​​:不更新 state。

使用场景-Props 变化时同步 State​​

当组件的 state 需要依赖 props 的变化时(受控组件常见)。

class Input extends React.Component {
  state = { value: this.props.defaultValue };
  static getDerivedStateFromProps(nextProps, prevState) {
    // 当父组件传入的 defaultValue 变化时,重置 value
    if (nextProps.defaultValue !== prevState.prevDefaultValue) {
      return {
        value: nextProps.defaultValue,
        prevDefaultValue: nextProps.defaultValue, // 记录上一次的 props
      };
    }
    return null;
  }
}

使用场景-条件性更新 State​

基于 props 的某些条件计算 state。

class ScrollPosition extends React.Component {
  state = { shouldFix: false };
  static getDerivedStateFromProps(nextProps, prevState) {
    // 当滚动位置超过阈值时,固定组件
    if (nextProps.scrollY > 100 && !prevState.shouldFix) {
      return { shouldFix: true };
    }
    return null;
  }
}

​常见问题​​​

    1. ​​为什么 getDerivedStateFromProps 是静态方法?​​
    • 避免开发者误用 this 或执行副作用,确保逻辑纯净。
    1. ​何时使用 getDerivedStateFromProps?​​
    • 仅当 state 需要​​无条件​​或​​有条件​​地根据 props 更新时(如表单重置、滚动位置计算)。
    1. ​为什么推荐用 key 重置组件?​​
    • key 变化时,React 会销毁并重建组件,比手动同步 state 更可靠。

2. shouldComponentUpdate(nextProps, nextState)​​

  • ​调用时机​​:在 render() 之前调用。
  • ​​用途​​:​​优化性能​​,决定是否重新渲染,跳过后续生命周期(render、componentDidUpdate 等)
  • 默认返回 true​​,可以手动比较 props/state 避免不必要的渲染。
  • ​​⚠️ 注意​​:
    • 不要深度比较(如 JSON.stringify),否则性能更差。
    • 可以使用 React.PureComponent 自动浅比较 props/state。
shouldComponentUpdate(nextProps, nextState) {
  return nextProps.id !== this.props.id; // 仅当 id 变化时重新渲染
}

使用场景-性能敏感组件​

// 对复杂计算或大数据量的组件进行优化。
class HeavyComponent extends React.Component {
  shouldComponentUpdate(nextProps) {
    // 仅当数据长度变化时重新渲染
    return nextProps.data.length !== this.props.data.length;
  }
  render() {
    return <ExpensiveRender data={this.props.data} />;
  }
}

与 React.PureComponent 的关系​

PureComponent 是内置了 shouldComponentUpdate 的浅比较(shallow compare)的组件,自动对比 props 和 state,适合简单数据。

class User extends React.PureComponent {  // 自动浅比较 props 和 state
  render() {
    return <div>{this.props.userId}</div>;
  }
}

函数组件的替代方案​

(1)useState + useEffect

function Input({ defaultValue }) {
  const [value, setValue] = useState(defaultValue);

  // 当 defaultValue 变化时,重置 value
  useEffect(() => {
    setValue(defaultValue);
  }, [defaultValue]);

  return <input value={value} onChange={e => setValue(e.target.value)} />;
}

(2)完全受控组件​

function Input({ value, onChange }) {
  return <input value={value} onChange={onChange} />;
}

常见问题​​

    1. ​​shouldComponentUpdate 和 PureComponent 的区别?​​
    • shouldComponentUpdate 需手动实现比较逻辑,PureComponent 自动浅比较。
    1. ​为什么不能在 shouldComponentUpdate 中深度比较?​​
    • 深度比较(如递归遍历对象)可能比重新渲染更消耗性能。
    1. ​函数组件如何实现类似功能?​​
    • 使用 React.memo 或 useMemo/useCallback 优化渲染。
    1. ​什么情况下 shouldComponentUpdate 会失效?​​
    • 父组件传递了新的引用类型(如内联函数或对象),导致浅比较无法检测到实际数据未变化。

3. render()

  • ​调用时机​​:必须实现,返回 JSX 或 null 。
  • ​用途​​:计算并返回组件的 UI。
  • ⚠️ 注意​​:
    • ​​必须是纯函数​​​(不能修改 state 或 props)。
    • ​​不能调用 setState​​​,否则会导致无限循环。
render() {
  return <div>{this.state.count}</div>;
}

4. getSnapshotBeforeUpdate(prevProps, prevState)​​

  • ​调用时机​​:在 DOM 更新前调用(render 之后,componentDidUpdate 之前)。
  • ​​用途​​:捕获 DOM 信息(如滚动位置),返回值会传给 componentDidUpdate。
  • ​⚠️ 注意​​:必须配合 componentDidUpdate 使用。
getSnapshotBeforeUpdate(prevProps, prevState) {
  if (prevProps.items.length < this.props.items.length) {
    const list = this.listRef.current;
    return list.scrollHeight - list.scrollTop; // 返回滚动位置
  }
  return null;
}

5. componentDidUpdate(prevProps, prevState, snapshot)

  • 调用时机​​:DOM 更新后调用。
  • 用途​​:
    • 对比新旧 props/state 执行逻辑(如重新请求数据)
    • 手动操作 DOM(如动画)
  • ⚠️ 注意​​:
    • 可以调用 setState,但必须加条件判断,否则会死循环。
componentDidUpdate(prevProps) {
  if (prevProps.userId !== this.props.userId) {
    fetch(`/api/user/${this.props.userId}`);
  }
}

三. 卸载阶段(Unmounting)​

​1. componentWillUnmount()​​

  • ​调用时机​​:组件从 DOM 移除前调用。
  • ​​用途​​:清理副作用,如:
    • 取消 ​​网络请求​​​(AbortController)
    • 移除 ​​​事件监听​​​(window.removeEventListener)
    • 清除 ​​​定时器​​​(clearInterval)
componentWillUnmount() {
  clearInterval(this.timer);
  window.removeEventListener('resize', this.handleResize);
}

四. 错误处理(Error Boundaries)​

​1. static getDerivedStateFromError(error)

  • 调用时机​​:子组件抛出错误时触发。
  • 用途​​:更新 state 显示错误 UI(如 <ErrorFallback />)。
static getDerivedStateFromError(error) {
  return { hasError: true };
}

​2. componentDidCatch(error, info)

  • 调用时机​​:子组件抛出错误后调用。
  • ​​用途​​:记录错误日志(如 Sentry)。
componentDidCatch(error, info) {
  logErrorToService(error, info.componentStack);
}

五. 废弃的生命周期方法(React 16.3+)​

​​废弃方法​​​替代方案​
componentWillMount逻辑移到 constructor 或 componentDidMount
componentWillReceivePropsstatic getDerivedStateFromProps
componentWillUpdategetSnapshotBeforeUpdate

六. 常见问题​​

    1. ​componentDidMount 和 componentDidUpdate 的区别?​​
    • componentDidMount 只在挂载后执行一次,componentDidUpdate 在每次更新后执行。
    1. 如何避免不必要的渲染?​​
    • 使用 shouldComponentUpdate 或 React.PureComponent(自动浅比较 props/state)。
    1. getSnapshotBeforeUpdate 的使用场景?​​
    • 在 DOM 更新前捕获信息(如滚动位置),用于 componentDidUpdate 恢复 UI 状态。
    1. ​为什么废弃 componentWillMount?​​
    • 它可能在 ​​渲染前或渲染后执行​​(SSR 环境下不确定),推荐用 constructor 或componentDidMount 替代。

总结​​

  • ​挂载阶段​​:constructor → getDerivedStateFromProps → componentDidMount
  • ​​更新阶段​​:getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
  • ​卸载阶段​​:componentWillUnmount
  • ​错误处理​​:getDerivedStateFromError + componentDidCatch

2.Hooks 生命周期⭐️

一. 生命周期对应关系(Class vs Hooks)​

​ ​Class 生命周期​​​Hooks 替代方案​
constructoruseState 初始化状态
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [dep])
componentWillUnmountcomponentWillUnmount
shouldComponentUpdateReact.memo + useMemo/useCallback
getDerivedStateFromPropsuseState + useEffect 手动对比 props

2. 核心 Hooks 实现生命周期​

(1)useState:替代 constructor 和状态管理​

const [count, setCount] = useState(0); // 初始化状态

(2)useEffect:模拟挂载、更新、卸载​

componentDidMount(只执行一次)

useEffect(() => {
  console.log("组件挂载完成");
}, []); // 空依赖数组

componentDidUpdate(依赖变化时触发)

useEffect(() => {
  console.log("count 更新:", count);
}, [count]); // 监听 count 变化

​​componentWillUnmount(清理副作用)

useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer); // 返回清理函数
}, []);

(3)useMemo/useCallback:替代 shouldComponentUpdate

避免不必要的渲染​​:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);

(4)useRef:存储 DOM 引用或可变值​

const inputRef = useRef(null);
useEffect(() => {
  inputRef.current.focus(); // 模拟 componentDidMount 的 DOM 操作
}, []);

三. 完整示例:Hooks 生命周期模拟​

import { useState, useEffect, useRef } from "react";

function LifecycleDemo() {
  const [count, setCount] = useState(0);
  const mountedRef = useRef(false);

  // 模拟 componentDidMount
  useEffect(() => {
    console.log("组件挂载");
    mountedRef.current = true;
    return () => {
      console.log("组件卸载");
      mountedRef.current = false;
    };
  }, []);

  // 模拟 componentDidUpdate
  useEffect(() => {
    if (mountedRef.current) {
      console.log("count 更新:", count);
    }
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

​四. 常见问题​​

    1. ​useEffect 和 componentDidMount 的区别?​​
    • useEffect 是异步的(在渲染后执行),componentDidMount 是同步的。
    1. ​如何精确控制更新逻辑?​​
    • 通过 useEffect 的依赖数组(如 [count])或 useMemo/useCallback 优化性能。
    1. 为什么 Hooks 没有 getDerivedStateFromError?​​
    • 错误边界仍需使用 Class 组件(如 static getDerivedStateFromError)。

总结​​

  • ​挂载阶段​​:useState + useEffect(() => {}, [])
  • ​​更新阶段​​:useEffect(() => {}, [dep]) + useMemo/useCallback
  • 卸载阶段​​:useEffect 的清理函数(return () => {})

3.Hooks的出现是为了解决什么问题⭐️

前言

比如,有这样一个函数组件(没有hooks,无状态),只是包含一个简单的div的Button组件:

function Button() {
  return <div className="btn">disabled</div>;
}

由于需求变更,这个函数组件需要有内部的状态,为此需要重构成class组件

export default class Button extends React.Component {
  state = { enabled: false };
  render() {
    const { enabled } = this.state;
    const btnText = enabled ? "enabled" : "disabled";
    return (
      <div
        className={`btn enabled-${enabled}`}
        onClick={() => this.setState({ enabled: !enabled })}
      >
        {btnText}
      </div>
    );
  }
}

一、Class架构设计问题

1. 代码复用困难

在 Class 组件中,共享状态逻辑很麻烦,只能通过 高阶组件(HOC) 或 Render Props,代码变得复杂且难以维护。

// 创建一个高阶组件(HOC)来共享逻辑
function withMousePosition(WrappedComponent) {
  return class extends React.Component {
    state = { x: 0, y: 0 };
    componentDidMount() {
      window.addEventListener("mousemove", this.handleMouseMove);
    }
    componentWillUnmount() {
      window.removeEventListener("mousemove", this.handleMouseMove);
    }
    handleMouseMove = (event) => {
      this.setState({ x: event.clientX, y: event.clientY });
    };
    render() {
      return <WrappedComponent {...this.props} mouse={this.state} />;
    }
  };
}
// 使用高阶组件
class MyComponent extends React.Component {
  render() {
    return <h1>Mouse Position: {this.props.mouse.x}, {this.props.mouse.y}</h1>;
  }
}
export default withMousePosition(MyComponent);
  • 问题:
    • 需要写一个 HOC,逻辑不直观
    • 逻辑拆分困难,复用时组件层级变多
    • 组件嵌套层级变深,导致 "Wrapper Hell"

2. Class 组件 this 指向困扰

Class 组件使用 this.setState 更新状态,导致 this 需要手动绑定,容易出错。

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.increment = this.increment.bind(this); // 必须绑定
  }
  increment() {
    this.setState({ count: this.state.count + 1 });
  }
  render() {
    return <button onClick={this.increment}>Click Me: {this.state.count}</button>;
  }
}
  • 问题:
    • componentDidMount 里有多个逻辑(数据请求、监听事件)
    • componentWillUnmount 需要手动清理副作用
    • 代码难以拆分和复用

3. 生命周期方法不清晰

Class 组件的生命周期函数 componentDidMount、componentDidUpdate、componentWillUnmount职责混杂,一个生命周期函数可能涉及多个逻辑,导致代码难以管理。

// 生命周期混乱
class Example extends React.Component {
  componentDidMount() {
    this.fetchData();
    window.addEventListener("resize", this.handleResize);
  }
  componentWillUnmount() {
    window.removeEventListener("resize", this.handleResize);
  }
  fetchData() {
    // 获取数据
  }
  handleResize = () => {
    // 处理窗口大小变化
  };
  render() {
    return <div>Example Component</div>;
  }
}
  • 问题:
    • componentDidMount 里有多个逻辑(数据请求、监听事件)
    • componentWillUnmount 需要手动清理副作用
    • 代码难以拆分和复用

二、Hooks 解决的问题

1. useCustom Hook 解决状态存储和复用

import { useState, useEffect } from "react";

// 自定义 Hook
function useMousePosition() {
  const [mouse, setMouse] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const updateMouse = (e) => setMouse({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", updateMouse);
    return () => {
      window.removeEventListener("mousemove", updateMouse);
    };
  }, []);
  return mouse;
}
// 组件中使用
function MyComponent() {
  const mouse = useMousePosition();
  return <h1>Mouse Position: {mouse.x}, {mouse.y}</h1>;
}
  • ✔ 优势:
    • 逻辑封装成 useMousePosition,可复用
    • 组件结构扁平,避免 "Wrapper Hell"
    • 逻辑更清晰

2. useState 让函数组件有状态

import React, { useState } from "react";
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Click Me: {count}</button>;
}
  • ✔ 优势:
    • 去掉了 class,函数式更简单
    • 不用手动绑定 this,不会有 this 相关 bug

3. useEffect 让函数组件有生命周期能力

import React, { useState, useEffect } from "react";

function Example() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize); // 清理副作用
    };
  }, []);
  return <div>Window Width: {width}</div>;
}
  • ✔ 优势:
    • useEffect 统一管理副作用,不再需要 componentDidMount 和 componentWillUnmount
    • 避免了生命周期拆分困难的问题

三、Hooks是如何解决以上问题?

1. Hooks 的状态存储和复用

Class 组件状态存储

在 Class 组件中, state 存在于组件实例上

class Counter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
}

React 内部会创建 Counter 组件的 实例对象

const instance = new Counter();
  • 状态存储在实例 ( instance.state ) 上
  • this 让 state 成为组件的属性
  • 复用逻辑必须靠继承、HOC 或者 Render Props

Hooks 组件状态存储

Hooks 不使用类实例存储状态,而是存储在 React Fiber 的链表中:

function Counter() {
  const [count, setCount] = useState(0);
}
  • 每个函数组件都有一个 hooks 数组,存储 useState 的状态
  • 每次 useState 都会从 hooks 数组里取值
  • 组件重新渲染时, useState 仍然能找到之前的值

2.为什么 Hooks 没有 this 困扰?

  • Hooks 是基于闭包的,而不是基于类实例
  • 每次渲染时,Hooks 创建一个全新的作用域
  • useState 返回的是 局部变量,而不是实例属性,不需要 this

在 React 内部,Class 组件的 state 是绑定在实例上的:

class Counter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
}

而 Hooks 直接使用一个数组或链表来存储状态,避免 this 的问题:

function Counter() {
  const [count, setCount] = useState(0); // 这里 count 只是一个变量,不属于实例
}

在 函数组件重新执行时,它创建了一个新的作用域,count 变量 不会丢失,因为 React 通过一个 隐藏的数组存储每次渲染的状态。

3. 工作原理

Hooks 的运行机制 依赖 React Fiber 架构,它通过 链表结构管理每次组件的状态。

Fiber 是什么?Fiber 是 React 内部维护的 组件更新数据结构,它类似于 链表结构:

const fiber = {
  type: Counter, // 组件类型
  stateNode: null, // 组件实例
  child: null, // 子 Fiber
  sibling: null, // 兄弟 Fiber
  return: null, // 父 Fiber
  hooks: [], // 存储 useState/useEffect 的状态
};

Fiber 中的 hooks 数组存储组件的状态!

4. Hooks 必须在最顶层调用?

  • React 按顺序存储 Hooks
  • 如果条件变化,Hook 调用顺序会错乱
  • React 通过 hookIndex 读取状态,顺序变了就会报错

4.基础Hooks⭐️

React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

React 默认提供的三个最常用的钩子。

  • useState() 状态钩子
  • useEffect() 用于在函数组件中执行​​副作用
  • useLayoutEffect() 和useEffect类似,但是执行时机不同
  • useContext() 跨组件传值​

2.1 useState详解

import React, { useState } from "react";

export default function  Button()  {
  // 第一个成员是一个变量
  // 第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名
  const  [buttonText, setButtonText] =  useState("Click me, please"); // 类似class组件的state和setState

  function handleClick()  {
    return setButtonText("Thanks, been clicked!");
  }

  return  <button  onClick={handleClick}>{buttonText}</button>;
}

useState 进阶用法​

1. 如果新状态​​依赖旧状态​​,推荐使用​​函数式更新​​:

function Counter() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount((prevCount) => prevCount + 1); // 基于旧值计算
  };
  return <button onClick={increment}>Count: {count}</button>;
}
  • 适用场景​​:
    • 避免闭包问题(如 setTimeout 内更新状态)。
    • 确保多次 setState 按预期执行(避免合并更新)。

​2. 惰性初始化​

function ExpensiveInitialState() {
  const [state, setState] = useState(() => {
    const initialState = calculateHeavyValue(); // 只计算一次
    return initialState;
  });
}

​3. 合并更新(对象/数组)​​

React ​​不会自动合并​​对象/数组的更新,需手动处理:

function UserProfile() {
  const [user, setUser] = useState({ name: "Alice", age: 25 });

  const updateName = () => {
    setUser(prev => ({ ...prev, name: "Bob" })); // 保留其他属性
  };
}

​4. setState 是​​异步​​的,不能立即获取新值​​

function AsyncExample() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // ❌ 仍然是旧值
  };
}

✅ 解决方案​​

useEffect(() => {
  console.log(count); // ✅ 在 useEffect 中获取新值
}, [count]);

2.2 useEffect详解

用于在函数组件中执行​​副作用​​,例如数据获取、订阅、手动 DOM 操作等。它相当于class组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。

(1) useEffect 基本语法

useEffect(() => {
  // 副作用逻辑(组件渲染后执行)
  return () => {
    // 清理逻辑(组件卸载或依赖项变化前执行)
  };
}, [dependencies]); // 依赖项数组
  • 第一个参数(回调函数)​​:执行副作用的逻辑。
  • ​第二个参数(依赖项数组)​​:控制 useEffect 何时重新执行。
  • ​返回值(清理函数)​​:在组件卸载或依赖项变化前执行清理逻辑。

(2) useEffect 的 4 种使用方式​

1. 无依赖项([])—— 只在首次渲染后执行

useEffect(() => {
  console.log("组件挂载后执行(类似 componentDidMount)");
}, []); // 空数组表示不依赖任何变量
  • 适用场景​​:
    • 数据初始化(如 fetch 请求)
    • 事件监听(如 window.addEventListener)
    • 定时器(如 setInterval)

2. 有依赖项([dep1, dep2])—— 依赖项变化时执行

const [count, setCount] = useState(0);

useEffect(() => {
  console.log("count 变化时执行(类似 componentDidUpdate)");
}, [count]); // 仅在 count 变化时重新执行
  • 适用场景​​:
    • 数据变化时重新计算(如 useMemo 的替代)
    • 监听 props 或 state 变化(如 props.id 变化时重新请求数据)

3. 无依赖项(无第二个参数)—— 每次渲染后都执行​

useEffect(() => {
  console.log("每次渲染后都执行");
}); // 没有依赖项数组
  • ⚠️ 注意​​:
    • 可能导致性能问题(频繁执行)
    • 适用于需要实时同步的场景(如 document.title 更新)

4. 清理副作用(return 清理函数)​​

useEffect(() => {
  const timer = setInterval(() => {
    console.log("每秒执行");
  }, 1000);

  return () => {
    console.log("(类似 componentWillUnmount)");
    clearInterval(timer); // 组件卸载时清理定时器
  };
}, []);
  • 适用场景​​:
    • 取消订阅(如 WebSocket、EventEmitter)
    • 清理定时器(如 setTimeout、setInterval)
    • 手动 DOM 操作(如 document.removeEventListener)

2.3 useEffect详解

useLayoutEffect 是 React 提供的一个 Hook,与 useEffect 类似,但执行时机不同。它适用于需要在 ​​浏览器绘制前同步执行​​的场景(如测量 DOM 布局、避免 UI 闪烁等)。

执行时机​​:在 React 完成 DOM 更新后,​​浏览器绘制(paint)前​​同步执行。

​​对比项​​ ​​useEffect​​useLayoutEffect
​​执行时机​异步(浏览器绘制后)同步(DOM 更新后,浏览器绘制前)
​​适用场景数据获取、订阅、非关键副作用测量 DOM、强制同步渲染(避免闪烁)
​​性能影响​对渲染性能影响较小可能阻塞渲染,慎用

useLayoutEffect 核心用途​

1. 测量 DOM 元素尺寸/位置​

import { useLayoutEffect, useRef, useState } from "react";

function Tooltip() {
  const ref = useRef(null);
  const [width, setWidth] = useState(0);

  useLayoutEffect(() => {
    // 在浏览器绘制前获取 DOM 尺寸
    const { width } = ref.current.getBoundingClientRect();
    setWidth(width);
  }, []);

  return <div ref={ref}>Tooltip width: {width}px</div>;
}
  • 为什么不用 useEffect?​​
    • 如果使用 useEffect,用户可能会先看到未计算的布局,再闪烁调整,而 useLayoutEffect 能避免这种闪烁。

2. 手动 DOM 操作(如动画库集成)​​

useLayoutEffect(() => {
  const element = document.getElementById("my-element");
  // 在浏览器绘制前应用动画
  element.style.transform = "translateX(100px)";
}, []);

useLayoutEffect 注意事项​​

  • (1) 避免阻塞主线程​​
    • useLayoutEffect 是同步执行的,如果逻辑复杂,会导致页面卡顿。
    • ​​优化方案​​:将耗时计算放到 useEffect 或 requestAnimationFrame 中。
  • (2) 服务端渲染(SSR)问题​​
    • 在 SSR 环境下,useLayoutEffect 会触发警告(因为无法在服务端执行 DOM 操作)。

2.4. useContext

官方定义:在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

Context提供了一个局部的全局作用域,使用Context则无需再手动的逐层传递props。

3种Context的使用方式:

  • React.createContext提供的Provider和Consumer
  • 函数组件:React.createContext提供的Provider和useContext钩子
  • Class组件:React.createContext提供的Provider和class的contextType属性

先定义好createContext

// Context.jsx
import { createContext } from "react";

export default createContext();

2.4.1. React.createContext提供的Provider和Consumer

// App.js
import React, { createContext } from "react";
import MyContext from "./Context";

import FirstTest from "./FirstTest";

export default function App() {
  return (
    //Provider组件接收一个value属性,此处传入一个带有name属性的对象
    <MyContext.Provider value={{ name: `test` }}>
      <FirstTest />
    </MyContext.Provider>
  );
}
// FirstTest.jsx
import React from "react";
import MyContext from "./Context";

const FirstTest = () => {
  return (
    <MyContext.Consumer>
      {(value) => {
        return (
          <div>{JSON.stringify(value)}</div> // test
        );
      }}
    </MyContext.Consumer>
  );
};

export default FirstTest;

2.4.2. 函数组件:React.createContext提供的Provider和useContext钩子

// SecondTest.jsx
import React, { useContext } from "react";
import MyContext from "./Context";

const SecondTest = () => {
  const context = useContext(MyContext);
  return <div>{JSON.stringify(value)}</div>; // test
};

export default SecondTest;

2.4.3. Class组件:React.createContext提供的Provider和class的contextType属性

// ThirdTest.jsx
import React, { Component } from "react";
import context from "./Context";

class ThirdTest extends Component {
  static contextType = context;
  render() {
    const value = this.context;
    return <div>{JSON.stringify(value)}</div>; // test
  }
}

// ThirdTest.contextType = context; //此处与写static关键字作用一致
export default ThirdTest;

5.进阶Hooks⭐️

  • useReducer() 复杂状态管理,useState 的替代方案
  • useCallback() 缓存函数本身,性能优化,接受一个函数和依赖数组,返回一个记忆化的函数引用
  • useMemo() 缓存计算结果,类似Vue的computed,接受一个创建函数和依赖数组,只有在依赖变化时才重新计算,返回缓存的值
  • useRef() 持久化引用
  • useImperativeHandle() 自定义暴露给父组件的实例值

3.1 useReducer详解(看起来写的更复杂了,还需要进一步了解)

useReducer 是 useState 的替代方案,适用于状态逻辑较复杂的组件。

const [state, dispatch] = useReducer(reducer, initialState);

示例:待办事项列表

function todosReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, { text: action.text, completed: false }];
    case 'TOGGLE':
      return state.map((todo, index) =>
        index === action.index ? {...todo, completed: !todo.completed} : todo
      );
    default:
      return state;
  }
}

function TodoList() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  return (
    <>
      <button onClick={() => dispatch({ type: 'ADD', text: 'New Task' })}>
        Add Todo
      </button>
      {todos.map((todo, index) => (
        <div key={index}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => dispatch({ type: 'TOGGLE', index })}
          />
          {todo.text}
        </div>
      ))}
    </>
  );
}
  • 适用场景
    • 状态逻辑复杂,有多个子值
    • 下一个状态依赖于之前的状态
    • 需要集中管理状态更新逻辑

3.2 useCallback详解

useCallback 返回一个记忆化的回调函数,只有当依赖项改变时才会更新。

示例:优化子组件渲染

在不使用 useCallback() 的时候,每次触发 handleAdd 事件时候,都会渲染子组件 ChildA

//  父组件
import { useCallback,  useState} from 'react'
import ChildA from './childA'
export default function MyCallBack() {
   const [useInfo, setUseInfo] = useState({
      name: 'Andy',
      age: 18
   })
   const myCallback = () => {
      console.log('==useCallback==')
      return useInfo.name
   }

   const handleAdd = () => {
      setUseInfo({
        name: 'Andy',
        age: 18
      })
   }
 return (
   <div>
       <button onClick={handleAdd}>add</button>
       <ChildA onAddCount={myCallback} ></ChildA>
   </div>
 )
}
// 子组件
import React, { memo } from 'react'

const ChildA = memo(({onAddCount}) => {
    console.log('==ChildA 组件更新了=', count)
    return (
      <button onClick={onAddCount}>子组件</button>
    )
})

当我们点击 add 按钮时候,发现页面打印 “==ChildA 组件更新了=”,说明传入相同的数据时候,会触发子组件渲染

使用 useCallback 时候

const myCallback = useCallback(() => {
  console.log('==useCallback==')
  return useInfo.name
}, [useInfo.name])

在点击 add 按钮 更新相同数据时候,只有父组件渲染,子组件不会再渲染

  • 若要实现 传入相同数据时候,只更新当前组件,而子组件不进行渲染,需使用 useCallback() 和 memo 来处理;

3.3 useMemo详解

useMemo是 React 提供的一个 hook 函数。允许开发人员缓存变量的值和依赖列表。如果此依赖项列表中的任何变量发生更改,React 将重新运行此函数去处理并重新缓存它。如果依赖项列表中的变量值没有改版,则 React 将从缓存中获取值。

基本可以等价于Vue 的 computed

React 官方文档介绍

提示

You may rely on useMemo as a performance optimization 您可以依赖 useMemo 作为性能优化工具

  • 基本用法
import React, { useMemo, useState } from "react";

export default function App() {
  const [myNum, setMyNum] = useState(0);
  const [otherNum, setOtherNum] = useState(0);

  const newVal = useMemo(() => {
    return myNum + otherNum;
  }, [myNum, otherNum]);

  const addOneNum = () => {
    let val = myNum + 1;
    setMyNum(val);
  };

  const addTwoNum = () => {
    let val = otherNum + 2;
    setOtherNum(val);
  };

  return (
    <div className="App">
      result:{newVal}
      <button onClick={addOneNum}>addOneNum</button>
      <button onClick={addTwoNum}>addTwoNum</button>
    </div>
  );
}

useMemo第一个参数是函数,第二个参数是数组,数组里面的元素是依赖项

3.4 useRef - 持久化引用

useRef 返回一个可变的ref对象,其.current属性被初始化为传入的参数

示例1:访问DOM元素

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

示例2:保存可变值

function Timer() {
  const [count, setCount] = useState(0);
  const timerRef = useRef();
  
  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => clearInterval(timerRef.current);
  }, []);

  return <div>Count: {count}</div>;
}
  • 适用场景
    • 访问DOM节点
    • 存储可变值而不触发重新渲染
    • 保存前一次渲染的值

3.5 useImperativeHandle - 自定义暴露给父组件的实例值

useImperativeHandle 可以让你在使用ref时自定义暴露给父组件的实例值。

const FancyInput = React.forwardRef(function FancyInput(props, ref) {
  const inputRef = useRef();
  
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));
  
  return <input ref={inputRef} />;
});

function Parent() {
  const inputRef = useRef();
  
  return (
    <>
      <FancyInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
      <button onClick={() => console.log(inputRef.current.getValue())}>
        Log Value
      </button>
    </>
  );
}
  • 适用场景
    • 暴露子组件的特定方法给父组件
    • 限制父组件对子组件的访问

3.6 useDebugValue - 自定义Hook调试标签

useDebugValue 用于在React开发者工具中显示自定义Hook的标签。

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
  
  // 在开发者工具中显示标签
  useDebugValue(isOnline ? 'Online' : 'Offline');
  
  return isOnline;
}
  • 适用场景
    • 自定义Hook开发
    • 在React开发者工具中提供更有意义的调试信息

6.React diff算法

React 的 Diff 算法基于以下 三大策略 优化性能:

同级比较(Tree Diff)、组件类型一致性、key 属性优化

  • 1.同级比较(Tree Diff)

    • 仅对比 同一层级 的节点,不跨层级比较(复杂度从 O(n³) 降至 O(n))。
    • 如果节点类型不同(如 div → span),直接销毁并重建整个子树。
  • 2.组件类型一致性

    • 相同类型的组件(如 <Button />)会复用实例,触发更新逻辑(如 shouldComponentUpdate)。
    • 不同类型则销毁旧组件,挂载新组件。
  • 3.key 属性优化

    • 对列表节点,key 帮助 React 识别节点的唯一性,减少不必要的销毁/重建。

React的diff 算法采用了 深度优先遍历算法

  • react 采用单指针从左向右进行遍历
  • vue采用双指针,从两头向中间进行遍历

React 不能通过双端对比进行 Diff 算法优化是因为目前 Fiber 上没有设置反向链表。

7.组件通信方式⭐️

一. 父子组件通信​

(1)父 → 子:props 传递数据​

// 父组件
function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} onUpdate={() => setCount(count + 1)} />;
}
// 子组件
function Child({ count, onUpdate }) {
  return <button onClick={onUpdate}>Count: {count}</button>;
}

(2)子 → 父:回调函数​

// 父组件
function Parent() {
  const [message, setMessage] = useState("");
  const handleChildMessage = (msg) => setMessage(msg);
  return <Child onSend={handleChildMessage} />;
}
// 子组件
function Child({ onSend }) {
  return <button onClick={() => onSend("Hello!")}>发送消息</button>;
}

(3)父 → 子:ref 调用子组件方法​

父组件通过 useRef 获取子组件实例并调用其方法(需配合 forwardRef + useImperativeHandle)

// 子组件(暴露方法)
const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    showAlert: () => alert("子组件方法被调用"),
  }));
  return <div>Child</div>;
});
// 父组件
function Parent() {
  const childRef = useRef();
  return (
    <>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.showAlert()}>触发子组件方法</button>
    </>
  );
}

二. 兄弟组件通信​​

(1)状态提升(Lifting State Up)​

将共享状态提升到最近的共同父组件,通过 props 传递。

function Parent() {
  const [sharedData, setSharedData] = useState("");
  return (
    <>
      <SiblingA data={sharedData} />
      <SiblingB onUpdate={setSharedData} />
    </>
  );
}
function SiblingA({ data }) {
  return <div>接收数据: {data}</div>;
}
function SiblingB({ onUpdate }) {
  return <button onClick={() => onUpdate("New Data")}>更新数据</button>;
}

(2)通过事件总线(Event Bus)​

使用自定义事件(如 EventEmitter)实现兄弟组件通信(非 React 推荐方式,慎用)。

// eventBus.js
const events = new EventEmitter();
export default events;

// 组件A(发布事件)
events.emit("update", "Data from A");

// 组件B(订阅事件)
useEffect(() => {
  events.on("update", (data) => console.log(data));
  return () => events.off("update"); // 清理
}, []);

三. 跨层级组件通信​

(1)Context API​​​

通过 createContext + useContext 实现跨组件数据共享。

// 创建 Context
const ThemeContext = createContext("light");
// 父组件(提供数据)
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}
// 子组件(消费数据)
function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div>当前主题: {theme}</div>;
}

(2)状态管理库(Redux/Zustand/MobX)​

适用于全局状态管理,如用户登录信息、主题等。

// Redux 示例
const store = configureStore({ reducer: counterReducer });
// 组件中获取状态
const count = useSelector((state) => state.counter);
const dispatch = useDispatch();
dispatch(increment());

四. 任意组件通信(高级场景)​

(1)发布-订阅模式(PubSub)​

// 组件A(发布)
PubSub.publish("event", "Data");
// 组件B(订阅)
useEffect(() => {
  const token = PubSub.subscribe("event", (_, data) => console.log(data));
  return () => PubSub.unsubscribe(token);
}, []);

(2)使用 useReducer + Context​

复杂状态逻辑下,结合 useReducer 和 Context 管理状态。

const StateContext = createContext();
const DispatchContext = createContext();

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        <Child />
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

// 子组件中使用
const state = useContext(StateContext);
const dispatch = useContext(DispatchContext);

五. 如何选择通信方式?​

​​场景​​​推荐方式​
父子组件简单通信props + 回调函数
兄弟组件共享状态状态提升或 Context
跨多层组件共享数据Context API 或 Redux
全局复杂状态管理Redux/Zustand/MobX
临时事件通信发布-订阅模式(慎用)

6. 常见问题​​

    1. ​props 和 context 的区别?​​
    • props 需手动逐层传递,context 可跨层级直接访问。
    1. ​Redux 和 Context 如何选择?​​
    • Redux 适合大型应用,Context 适合中小型应用或局部状态。
    1. ​为什么慎用事件总线?​​
    • 容易导致代码难以维护,组件间隐式耦合。

​总结​​

  • ​父子通信​​:props + 回调函数 + ref
  • ​兄弟通信​​:状态提升或 Context
  • ​跨层级通信​​:Context API 或状态管理库
  • ​全局状态​​:Redux/Zustand
  • ​临时通信​​:发布-订阅(谨慎使用)

8.Mobx 相关

1. MobX 核心概念

  • 1.1 基本组成要素
    • ​​Observable (可观察状态)​​:被 MobX 跟踪的状态
    • ​​Action (动作)​​:修改状态的方法
    • ​Computed (计算值)​​:从状态派生的值
    • Reaction (反应)​​:对状态变化的响应
  • 1.2 工作原理
    • Action → State (Observable) → Computed Values → Reactions (如React组件渲染)

2. MobX 基本使用

2.1 创建 Store

import { makeAutoObservable } from "mobx";

class CounterStore {
  count = 0;  // 可观察状态

  constructor() {
    makeAutoObservable(this);  // 自动转换
  }

  // Action
  increment = () => { this.count++ };

  // Action
  decrement = () => { this.count-- };

  // Computed
  get doubleCount() { return this.count * 2 }
}

export default new CounterStore();

2.2 在 React 中使用

import { observer } from "mobx-react-lite";
import counterStore from "./CounterStore";

const Counter = observer(() => {
  return (
    <div>
      <p>Count: {counterStore.count}</p>
      <p>Double: {counterStore.doubleCount}</p>
      <button onClick={counterStore.increment}>+</button>
      <button onClick={counterStore.decrement}>-</button>
    </div>
  );
});

3. MobX 高级特性

3.1 手动定义 Observable

import { observable, action, computed } from "mobx";

class TodoStore {
  @observable todos = [];
  @observable filter = "all";

  @action
  addTodo = (text) => {
    this.todos.push({ text, completed: false });
  };

  @computed
  get filteredTodos() {
    switch (this.filter) {
      case "completed":
        return this.todos.filter(todo => todo.completed);
      case "active":
        return this.todos.filter(todo => !todo.completed);
      default:
        return this.todos;
    }
  }
}

3.2 异步 Actions

import { runInAction } from "mobx";

class UserStore {
  @observable users = [];
  @observable state = "pending"; // "pending", "done", "error"

  @action
  async fetchUsers() {
    this.state = "pending";
    try {
      const response = await fetch("/api/users");
      const users = await response.json();
      
      runInAction(() => {
        this.users = users;
        this.state = "done";
      });
    } catch (error) {
      runInAction(() => {
        this.state = "error";
      });
    }
  }
}

4. MobX 与 React 集成

4.1 函数组件

import { observer } from "mobx-react-lite";
import { useStores } from "./stores";

const TodoList = observer(() => {
  const { todoStore } = useStores();
  
  return (
    <div>
      {todoStore.filteredTodos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
});

9.Redux 相关⭐️

1. Redux 三大原则

  • ​单一数据源​​:整个应用状态存储在单个 store 中
  • 状态只读​​:只能通过 dispatch action 来修改状态
  • 纯函数修改​​:使用纯函数 reducer 处理状态变更

2. Redux 核心组成

  • Action​​:描述发生了什么的对象,必须包含 type 属性
  • ​Reducer​​:纯函数,接收旧 state 和 action,返回新 state
  • ​​Store​​:保存应用状态的对象,提供核心方法:
    • getState() 获取当前状态
    • dispatch(action) 触发状态更新
    • subscribe(listener) 注册监听器

3. Redux 工作流程

  1. 用户触发 UI 事件
  2. 调用 dispatch(action)
  3. Redux store 调用 reducer 函数
  4. Reducer 返回新 state
  5. Store 更新 state 并通知订阅者
  6. UI 根据新 state 重新渲染
  • 基础 Redux 实现
// Action
const increment = () => ({ type: 'INCREMENT' });

// Reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT': return state + 1;
    default: return state;
  }
}

// Store
const store = createStore(counter);

// 订阅 state 变化
const unsubscribe = store.subscribe(() => {
  console.log('State changed:', store.getState());
});

// 使用
store.dispatch(increment());
console.log(store.getState()); // 1

4. Redux 中间件原理?常用中间件有哪些?

  • 原理:提供 dispatch 和 getState 的扩展点
  • 常用:redux-thunk(异步)、redux-saga(复杂副作用)、redux-logger(日志)

5. Redux 如何处理异步操作?​

  • 使用中间件如 redux-thunk 或 redux-saga
  • thunk 示例:
const fetchData = () => async dispatch => {
  dispatch({type: 'FETCH_START'});
  try {
    const res = await api.getData();
    dispatch({type: 'FETCH_SUCCESS', payload: res});
  } catch (err) {
    dispatch({type: 'FETCH_ERROR', error: err});
  }
}

6. Redux 与 Context API 如何选择?​​

  • Redux:大型应用、复杂状态管理、需要时间旅行调试
  • Context:中小型应用、简单状态共享

MobX 与 Redux 对比

特性MobXRedux
编程范式响应式编程函数式编程
性能优化自动手动
调试工具有限强大(Redux DevTools)
适用场景中小型应用,快速开发大型应用,需要严格架构

10.Redux Toolkit 相关

Redux Toolkit 解决了什么问题

  • 简化 Redux 样板代码
  • 内置 immer 实现不可变更新
  • 提供 createSlice 自动生成 action 和 reduce
import { configureStore, createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: { name: '', age: 0 },
  reducers: {
    setName: (state, action) => {
      state.name = action.payload;
    },
    setAge: (state, action) => {
      state.age = action.payload;
    }
  }
});

const store = configureStore({
  reducer: {
    user: userSlice.reducer
  }
});

// 使用
store.dispatch(userSlice.actions.setName('Alice'));

11.React fiber相关⭐️

React Fiber 是 React 16 中引入的​​全新协调算法,其核心目的是解决 React 15 及之前版本在渲染大型应用时的​​性能瓶颈​​和​​用户体验问题​​

(1) 不可中断的递归更新​

  • ​问题​​:组件的更新过程是一个​​深度优先递归调用​​,一旦开始渲染,必须一次性完成整个组件树的更新(类似函数调用栈)。
  • ​后果​​:
    • 如果组件树很大(例如复杂页面),JavaScript 会长时间占用主线程(Main Thread),导致:
      • 掉帧(Jank)​​:浏览器无法及时处理用户交互(如点击、滚动)。
      • ​卡顿​​:页面响应延迟,尤其在低端设备上。

(2) 缺乏优先级调度​

  • 问题​​:所有更新任务具有相同的优先级,无法区分​​高优先级更新​​(如用户输入)和​​低优先级更新​​(如数据加载)。
  • ​​后果​​:
    • 用户点击按钮时,可能被一个耗时的渲染任务阻塞,导致交互延迟。

(3) 同步渲染(Blocking Rendering)​​

  • ​问题​​:渲染过程是同步的,无法拆分为小块任务。
  • ​后果​​:
    • 无法利用浏览器的空闲时间(Idle Time)分片渲染。

React Fiber 的解决方案​

Fiber 通过以下机制彻底重构了 React 的渲染流程:

​(1) 可中断的异步渲染

  • Fiber 将渲染任务拆解为多个小单元​​,每个单元对应一个组件或 DOM 节点。
  • ​​关键改进​​:
    • 使用 ​​链表结构​​(而非递归)遍历组件树,允许渲染过程​​暂停、恢复或丢弃​​。
    • 通过浏览器的 ​​requestIdleCallback​​(现改为 Scheduler)在空闲时间执行任务,避免阻塞主线程。

(2) 优先级调度

​Fiber 为不同任务分配优先级​​:

优先级类型场景示例调度策略
​​Immediate​用户输入、动画同步执行(最高优先级)
​​High​交互反馈(如按钮状态)尽快执行
​​Low​数据加载、离屏渲染空闲时执行

结果​​:用户交互(如输入)始终优先响应,避免卡顿。

​(3) 增量渲染(Incremental Rendering)​​

  • Fiber 将渲染分为两个阶段​​:
      1. ​​Reconciliation Phase(协调阶段)​​: 计算哪些组件需要更新(可中断,不直接操作 DOM)。
      1. Commit Phase(提交阶段)​​: 一次性提交所有变更到 DOM(同步执行,不可中断)。
  • ​优势​​:协调阶段可以分片执行,避免长时间占用主线程。

​(4) 错误边界(Error Boundaries)​​

​​Fiber 支持在组件树中捕获异常​​,避免因局部错误导致整个应用崩溃(React 15 中未处理错误会白屏)

Fiber 的实际优化场景​​

  • ​(1) 大型列表渲染​​
    • ​​旧版问题​​:渲染 1000 条列表项会卡死页面。
    • ​​Fiber 优化​​:分片渲染,优先显示可视区域内容(结合 React.lazy 或虚拟列表)。
  • ​(2) 动画和交互响应​​
    • ​​旧版问题​​:动画帧率不稳定。
    • Fiber 优化​​:高优先级更新(如动画)优先执行,低优先级任务(如日志上报)延后。
  • (3) Suspense 和并发模式(Concurrent Mode)​​
    • ​​功能​​:允许组件等待异步数据时显示占位符(如 loading)。
    • 底层依赖​​:Fiber 的异步渲染能力。

总结:Fiber 的核心价值​

​​问题​​​React 15(Stack)​​​React 16+(Fiber)​​
主线程阻塞​严重(同步递归)轻微(可中断的异步分片)
优先级调度​不支持支持(Immediate/High/Low)
​​用户体验卡顿、掉帧流畅、响应迅速
​​扩展性​难以优化支持 Suspense、并发渲染等新特性
最后更新: 2025/12/24 23:46
Prev
React基础
Next
React18新特性