# 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);
}
1
2
3
4
5

# 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
}
1
2
3
4
5
6

# 3. render()

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

# 4. 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);
}
1
2
3
4

# 二. 更新阶段(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
}
1
2
3
4
5
6
7
  • 返回值​​:
    • ​对象​​:合并到当前 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;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

使用场景-条件性更新 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
2
3
4
5
6
7
8
9
10

常见问题​​​

    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 变化时重新渲染
}
1
2
3

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

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

与 React.PureComponent 的关系​

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

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

函数组件的替代方案​

(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)} />;
}
1
2
3
4
5
6
7
8
9
10

(2)完全受控组件​

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

常见问题​​

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

# 3. render()

  • 重新计算 UI。

# 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;
}
1
2
3
4
5
6
7

# 5. componentDidUpdate(prevProps, prevState, snapshot)

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

# 三. 卸载阶段(Unmounting)​

# ​1. componentWillUnmount()​​

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

# 四. 错误处理(Error Boundaries)​

# ​1. static getDerivedStateFromError(error)

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

# ​2. componentDidCatch(error, info)

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

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

​​废弃方法​ ​​替代方案​
componentWillMount 逻辑移到 constructor 或 componentDidMount
componentWillReceiveProps static getDerivedStateFromProps
componentWillUpdate getSnapshotBeforeUpdate

# 六. 常见问题​​

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

# 总结​​

  • ​挂载阶段​​:constructorgetDerivedStateFromPropsrendercomponentDidMount
  • ​​更新阶段​​:getDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate
  • ​卸载阶段​​:componentWillUnmount
  • ​错误处理​​:getDerivedStateFromError + componentDidCatch

# 2.Hooks 生命周期

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

​ ​Class 生命周期​ ​​Hooks 替代方案​
constructor useState 初始化状态
componentDidMount useEffect(() => {}, [])
componentDidUpdate useEffect(() => {}, [dep])
componentWillUnmount componentWillUnmount
shouldComponentUpdate React.memo + useMemo/useCallback
getDerivedStateFromProps useState + useEffect 手动对比 props

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

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

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

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

componentDidMount(只执行一次)

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

componentDidUpdate(依赖变化时触发)

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

​​componentWillUnmount(清理副作用)

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

# (3)useMemo/useCallback:替代 shouldComponentUpdate

避免不必要的渲染​​:

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

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

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

# 三. 完整示例: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# ​四. 常见问题​​

    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>;
}
1
2
3

由于需求变更,这个函数组件需要有内部的状态,为此需要重构成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>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 一、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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 问题:
    • 需要写一个 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>;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 问题:
    • componentDidMount 里有多个逻辑(数据请求、监听事件)
    • componentWillUnmount 需要手动清理副作用
    • 代码难以拆分和复用

# 3. 生命周期方法不清晰

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

// 生命周期混乱
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>;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 问题:
    • 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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • ✔ 优势:
    • 逻辑封装成 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>;
}
1
2
3
4
5
  • ✔ 优势:
    • 去掉了 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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • ✔ 优势:
    • useEffect 统一管理副作用,不再需要 componentDidMountcomponentWillUnmount
    • 避免了生命周期拆分困难的问题

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

# 1. Hooks 的状态存储和复用

Class 组件状态存储

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

class Counter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
}
1
2
3
4
5
6

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

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

Hooks 组件状态存储

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

function Counter() {
  const [count, setCount] = useState(0);
}
1
2
3
  • 每个函数组件都有一个 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 };
  }
}
1
2
3
4
5
6

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

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

在 函数组件重新执行时,它创建了一个新的作用域,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 的状态
};
1
2
3
4
5
6
7
8

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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# useState 进阶用法​

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

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

​2. 惰性初始化​

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

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

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

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

  const updateName = () => {
    setUser(prev => ({ ...prev, name: "Bob" })); // 保留其他属性
  };
}
1
2
3
4
5
6
7

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

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

  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // ❌ 仍然是旧值
  };
}
1
2
3
4
5
6
7
8

✅ 解决方案​​

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

# 2.2 useEffect详解

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

# (1) useEffect 基本语法

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

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

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

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

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

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

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

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

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

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

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

  return () => {
    console.log("(类似 componentWillUnmount)");
    clearInterval(timer); // 组件卸载时清理定时器
  };
}, []);
1
2
3
4
5
6
7
8
9
10
  • 适用场景​​:
    • 取消订阅(如 WebSocketEventEmitter
    • 清理定时器(如 setTimeoutsetInterval
    • 手动 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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 为什么不用 useEffect?​​
    • 如果使用 useEffect,用户可能会先看到未计算的布局,再闪烁调整,而 useLayoutEffect 能避免这种闪烁。

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

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

# useLayoutEffect 注意事项​​

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

# 2.4. useContext

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

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

3种Context的使用方式:

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

先定义好createContext

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

export default createContext();
1
2
3
4

# 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>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 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;
1
2
3
4
5
6
7
8
9
10

# 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5.进阶Hooks

  • useReducer() 复杂状态管理,useState 的替代方案
  • useCallback() 记忆函数
  • useMemo() 缓存记忆值,类似Vue的computed
  • useRef() 持久化引用
  • useImperativeHandle() 自定义暴露给父组件的实例值

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

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

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

示例:待办事项列表

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>
      ))}
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  • 适用场景
    • 状态逻辑复杂,有多个子值
    • 下一个状态依赖于之前的状态
    • 需要集中管理状态更新逻辑

# 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>
 )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 子组件
import React, { memo } from 'react'

const ChildA = memo(({onAddCount}) => {
    console.log('==ChildA 组件更新了=', count)
    return (
      <button onClick={onAddCount}>子组件</button>
    )
})
1
2
3
4
5
6
7
8
9

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

# 使用 useCallback 时候

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

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

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

# 3.3 useMemo详解

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

基本可以等价于Vue 的 computed

React 官方文档介绍

TIP

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>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

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>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 示例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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 适用场景
    • 访问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>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 适用场景
    • 暴露子组件的特定方法给父组件
    • 限制父组件对子组件的访问

# 3.6 useDebugValue - 自定义Hook调试标签

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

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

# 6.React diff算法

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

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

  • 1.同级比较(Tree Diff)

    • 仅对比 同一层级 的节点,不跨层级比较(复杂度从 O(n³) 降至 O(n))。
    • 如果节点类型不同(如 divspan),直接销毁并重建整个子树。
  • 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>;
}
1
2
3
4
5
6
7
8
9

# (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>;
}
1
2
3
4
5
6
7
8
9
10

# (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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 二. 兄弟组件通信​​

# (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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# (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
2
3
4
5
6
7
8
9
10
11
12

# 三. 跨层级组件通信​

# (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>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

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

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

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

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

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

# (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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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

​​场景​ ​​推荐方式​
父子组件简单通信 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();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 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>
  );
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 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;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 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";
      });
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 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>
  );
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

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

  • 原理:提供 dispatchgetState 的扩展点
  • 常用: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});
  }
}
1
2
3
4
5
6
7
8
9

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

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

# MobX 与 Redux 对比

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

# 10.Redux Toolkit 相关

# Redux Toolkit 解决了什么问题

  • 简化 Redux 样板代码
  • 内置 immer 实现不可变更新
  • 提供 createSlice 自动生成 actionreduce
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'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 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、并发渲染等新特性
lastUpdate: 6/2/2025, 5:44:04 PM