【精通react】(五)react 函数时组件为什么需要 hooks?

一、背景:函数式组件曾经的局限

在 React 16.8 之前(即 Hooks 出现前),React 组件有两种写法:

全屏复制

类型 特点
类组件(Class Component) 可以使用状态(state)、生命周期方法、ref 等
函数式组件(Function Component) 只能接收 props,返回 JSX,无法管理状态或生命周期

❌ 问题来了:

  • 函数式组件虽然简洁,但太"被动"了 ------ 它只是一个纯函数。
  • 所有复杂逻辑(如数据获取、状态更新、副作用处理)都必须用类组件实现。
  • 导致大量"简单 UI + 复杂逻辑"的组件被迫写成类组件,代码臃肿。

👉 比如一个按钮组件,想记录点击次数?必须写成类!

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

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return <button onClick={this.handleClick}>Clicked {this.state.count} times</button>;
  }
}

而理想中的写法应该是更简洁的函数形式。


🔧 二、Hooks 的诞生:让函数式组件"拥有能力"

React 团队提出了 Hooks 的概念 ------ 允许你在不编写 class 的情况下使用 state 以及其他 React 特性

✅ 目标:让函数式组件也能做类组件能做的事

于是有了:

  • useState → 使用状态
  • useEffect → 处理副作用(如数据请求、订阅、手动 DOM 操作)
  • useContext → 访问上下文
  • 还有数十个内置和自定义 Hook

现在上面的例子可以写成:

javascript 复制代码
jsx
import { useState } from 'react';

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

✅ 更短、更直观、没有 this 困扰。


🧩 三、为什么不能直接给函数组件加 state?------ 深层原因

你可能会问:

"为什么不直接让函数支持 this.state,像 class 一样?"

答案是:函数执行完就结束了,无法保存状态

⚠️ 函数执行机制 vs 类实例机制

全屏复制

对比项 函数式组件 类组件
执行方式 每次渲染都重新调用函数 实例化一次,多次调用 render() 方法
状态存储位置 无处可存 存在 this.state
this 是否存在 ❌ 不存在(或指向错误) ✅ 存在,指向实例

🌰 举例说明:

javascript 复制代码
js
function Counter() {
  let count = 0; // 局部变量
  return <div>{count}</div>;
}

每次渲染都会重新执行这个函数,count 被重置为 0,无法持久化。

而类组件:

scala 复制代码
js
class Counter extends Component {
  state = { count: 0 }; // 存在于实例上,不会随 render 丢失
}

所以,普通函数无法天然持有状态。必须有一个外部机制来"记住"上次的状态。


💡 四、Hooks 是如何解决这个问题的?------ 核心原理简析

React 实现了一个 状态记忆系统,它:

  1. 在首次渲染时,按顺序注册每个 Hook 的初始值;
  2. 在后续渲染中,按照相同的顺序读取之前保存的状态;
  3. 利用闭包 + 内部索引数组维护状态(简化理解)

🔄 示例:useState 如何工作?

javascript 复制代码
jsx
function Counter() {
  const [count, setCount] = useState(0); // 第1个 Hook
  const [name, setName] = useState('Alice'); // 第2个 Hook

  return (
    <div>
      {count}, {name}
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

React 内部维护一个数组(伪代码):

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

function useState(initialValue) {
  if (hooks[currentIndex] === undefined) {
    hooks[currentIndex] = initialValue;
  }
  const index = currentIndex;
  const value = hooks[index];
  const setValue = (newValue) => {
    hooks[index] = newValue;
    reRender();
  };
  currentIndex++;
  return [value, setValue];
}

⚠️ 所以要求:Hooks 必须始终以相同的顺序被调用,不能出现在条件语句中。


🎯 五、Hooks 带来的五大优势(为什么需要它)

全屏复制

优势 说明
逻辑复用更容易 以前靠 HOC 或 Render Props,嵌套深、难维护;现在用自定义 Hook 封装逻辑(如 useFetch, useLocalStorage
避免 this 的困扰 函数组件没有 this,不再需要绑定事件、担心作用域
更接近函数式编程思想 函数 + 纯净副作用控制,更利于测试和推理
组件逻辑组织更灵活 不再受限于生命周期函数,可以用多个 useEffect 分离不同逻辑
体积更小、性能更好 函数组件比类组件更轻量,Tree-shaking 更友好

📦 六、经典例子:对比三种写法

需求:加载用户数据并显示

1. 类组件写法(繁琐)

javascript 复制代码
jsx
class UserProfile extends Component {
  state = { user: null, loading: true };

  componentDidMount() {
    fetch('/api/user')
      .then(res => res.json())
      .then(user => this.setState({ user, loading: false }));
  }

  render() {
    const { user, loading } = this.state;
    if (loading) return <p>Loading...</p>;
    return <p>Hello, {user.name}</p>;
  }
}

2. 函数式 + Hooks(清晰简洁)

scss 复制代码
jsx
function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, []); // 空依赖 → 相当于 componentDidMount

  if (loading) return <p>Loading...</p>;
  return <p>Hello, {user.name}</p>;
}

3. 自定义 Hook(进一步复用)

scss 复制代码
js
function useUser() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(setUser)
      .finally(() => setLoading(false));
  }, []);

  return { user, loading };
}

然后在任意组件中使用:

javascript 复制代码
jsx
function UserProfile() {
  const { user, loading } = useUser();
  if (loading) return <p>Loading...</p>;
  return <p>Hello, {user.name}</p>;
}

✅ 真正实现了 UI 与逻辑分离、跨组件复用


⚠️ 七、Hooks 的规则(也是其设计约束)

为了保证上述机制正常工作,React 规定了两条重要规则:

  1. 只能在顶层调用 Hooks

    • 不能在循环、条件、嵌套函数中调用
    • 确保每次渲染调用顺序一致
  2. 只能在 React 函数组件或自定义 Hook 中调用

    • 不能在普通 JavaScript 函数中调用

这些规则由 ESLint 插件 eslint-plugin-react-hooks 自动检查。


✅ 八、总结:为什么函数式组件需要 Hooks?

全屏复制

问题 解决方案 Hooks 的角色
函数组件无法保存状态 useState 提供状态管理能力
函数组件无法处理副作用 useEffect 替代生命周期方法
逻辑难以复用 自定义 Hook 封装可复用逻辑单元
类组件过于复杂 改用函数组件 更简洁、易读、易测
相关推荐
讯方洋哥29 分钟前
应用冷启动优化
前端·harmonyos
speedoooo33 分钟前
未来的App不再需要菜单栏?
前端·ui·容器·小程序·web app
猿究院_xyz1 小时前
微信小程序与echarts联动安卓真机测试出现黑色阴影
前端·javascript·微信小程序·小程序·echarts
IT_陈寒1 小时前
Redis性能翻倍的5个冷门技巧,90%开发者都不知道的深度优化方案
前端·人工智能·后端
清水迎朝阳1 小时前
监听 edge大声朗读 样式变化
前端·edge
油丶酸萝卜别吃1 小时前
修改chrome配置,关闭跨域校验
前端·chrome
m0_740043732 小时前
3、Vuex-Axios-Element UI
前端·javascript·vue.js
风止何安啊2 小时前
一场组件的进化脱口秀——React从 “类” 到 “hooks” 的 “改头换面”
前端·react.js·面试
JS_GGbond2 小时前
给数组装上超能力:JavaScript数组方法趣味指南
前端·javascript
前端无涯2 小时前
Tailwind CSS v4 开发 APP 内嵌 H5:安卓 WebView 样式丢失问题解决与降级实战
前端