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 读取状态,顺序变了就会报错
相关推荐
袋鼠云数栈UED团队12 分钟前
基于 Lexical 实现变量输入编辑器
前端·javascript·架构
cipher25 分钟前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
UrbanJazzerati26 分钟前
非常友好的Vue 3 生命周期详解
前端·面试
AAA阿giao28 分钟前
从零构建一个现代登录页:深入解析 Tailwind CSS + Vite + Lucide React 的完整技术栈
前端·css·react.js
兆子龙1 小时前
像 React Hook 一样「自动触发」:用 Git Hook 拦住忘删的测试代码与其它翻车现场
前端·架构
兆子龙2 小时前
用 Auto.js 实现挂机脚本:从找图点击到循环自动化
前端·架构
SuperEugene2 小时前
表单最佳实践:从 v-model 到自定义表单组件(含校验)
前端·javascript·vue.js
昨晚我输给了一辆AE862 小时前
为什么现在不推荐使用 React.FC 了?
前端·react.js·typescript
不会敲代码12 小时前
深入浅出 React 闭包陷阱:从现象到原理
前端·react.js
不会敲代码12 小时前
React性能优化:深入理解useMemo和useCallback
前端·javascript·react.js