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 读取状态,顺序变了就会报错
相关推荐
nt110730 分钟前
大模型实现sql生成 --- 能力不足时的retry
前端·langchain·llm
霸王蟹30 分钟前
Vue的性能优化方案和打包分析工具。
前端·javascript·vue.js·笔记·学习·性能优化
绿草在线38 分钟前
路由Vue Router基本用法
前端·javascript·vue.js
Anlici39 分钟前
embedding 搜索功能怎么实现
前端·人工智能
霸王蟹42 分钟前
Pinia-构建用户仓库和持久化插件
前端·vue.js·笔记·ts·pinia·js
iOS阿玮1 小时前
Apple开发者已入驻微信公众号
前端·app·apple
2013编程爱好者1 小时前
React的Hellow React小案例
前端·javascript·react.js
IT、木易1 小时前
React 中的错误边界(Error Boundaries),如何使用它们捕获组件错误
前端·react.js·前端框架
PyAIGCMaster2 小时前
国内 npm 镜像源推荐
前端·npm·node.js
强国2 小时前
大学生速通前端之入门【第一篇】
前端·javascript