React为什么设计Hooks?Hooks解决了什么问题?

一个新的架构设计必然是为了解决旧架构设计的痛点和问题,那我们就需要看旧的架构体系中存在哪些问题。

1. Class架构设计问题

1.1. 代码复用困难

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

Class 组件共享逻辑问题

scala 复制代码
// 创建一个高阶组件(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"

1.2. Class 组件 this 指向困扰

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

this 绑定问题

kotlin 复制代码
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>;
  }
}

问题:

  • this 需要手动绑定,容易忘记,导致 undefined 错误
  • 可以使用箭头函数 increment = () => {},但语法冗长

1.3. 生命周期方法不清晰

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

生命周期混乱

javascript 复制代码
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 需要手动清理副作用
  • 代码难以拆分和复用

2. Hooks 解决了这些问题

Hooks 让我们在 函数组件 里使用状态和生命周期逻辑,避免 this 问题,逻辑更清晰,代码更简洁。

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

自定义 Hook 让我们可以封装可复用的逻辑,不需要 HOC 也能复用状态。

javascript 复制代码
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.2. useState 让函数组件有状态

javascript 复制代码
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

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

javascript 复制代码
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 统一管理副作用,不再需要 componentDidMountcomponentWillUnmount
  • 避免了生命周期拆分困难的问题

3. Hooks是如何解决以上问题的呢?

3.1. Hooks 的状态存储和复用

  1. Class 组件状态存储

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

scala 复制代码
class Counter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
}

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

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

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

scss 复制代码
function Counter() {
  const [count, setCount] = useState(0);
}

底层实现:

ini 复制代码
let hookIndex = 0;
const hooks = [];

function useState(initialValue) {
  if (!hooks[hookIndex]) {
    hooks[hookIndex] = initialValue;
  }
  const setState = (newValue) => {
    hooks[hookIndex] = newValue;
    render(); // 重新渲染
  };
  return [hooks[hookIndex++], setState];
}

Hooks 组件是如何存储状态的?

    1. 每个函数组件都有一个 hooks 数组 ,存储 useState 的状态
    2. 每次 useState 都会从 hooks 数组里取值
    3. 组件重新渲染时, useState 仍然能找到之前的值

这样就不需要 Class 组件的 this.state

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

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

底层实现分析

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

scala 复制代码
class Counter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
}

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

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

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

3.3. 工作原理

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

3.3.1. Fiber 是什么?

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

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

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

3.3.2. Hooks 在 Fiber 中的存储

ini 复制代码
function useState(initialState) {
  const oldHook = currentFiber?.hooks[hookIndex]; // 取上次渲染的状态
  const hook = { state: oldHook ? oldHook.state : initialState };
  
  function setState(newState) {
    hook.state = newState;
    render(); // 触发更新
  }

  currentFiber.hooks[hookIndex++] = hook; // 存入 Fiber
  return [hook.state, setState];
}

存储过程:

  1. React 在 Fiber 结构中存储 useState 的状态
  2. 每次渲染时,Hooks 通过 hookIndex 取回上次的状态
  3. setState 更新状态后,React 触发 重新渲染

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

  1. React 按顺序存储 Hooks
  2. 如果条件变化,Hook 调用顺序会错乱
  3. React 通过 hookIndex 读取状态,顺序变了就会报错
相关推荐
发呆小天才yy1 小时前
uniapp 微信小程序使用图表
前端·微信小程序·uni-app·echarts
@PHARAOH2 小时前
HOW - 在 Mac 上的 Chrome 浏览器中调试 Windows 场景下的前端页面
前端·chrome·macos
月月大王4 小时前
easyexcel导出动态写入标题和数据
java·服务器·前端
JC_You_Know5 小时前
多语言网站的 UX 陷阱与国际化实践陷阱清单
前端·ux
Python智慧行囊5 小时前
前端三大件---CSS
前端·css
Jinuss6 小时前
源码分析之Leaflet中Marker
前端·leaflet
成都渲染101云渲染66666 小时前
blender云渲染指南2025版
前端·javascript·网络·blender·maya
聆听+自律6 小时前
css实现渐变色圆角边框,背景色自定义
前端·javascript·css
牛马程序小猿猴7 小时前
17.thinkphp的分页功能
前端·数据库
huohuopro7 小时前
Vue3快速入门/Vue3基础速通
前端·javascript·vue.js·前端框架