当前位置: 首页 > news >正文

React中的Hook到底是个什么鬼

文章目录

    • Hooks本质:
    • Hooks的核心特点:
    • Hooks的设计原理
    • Hooks 的底层实现(简化版)
    • Hooks 的最佳实践
    • Hooks 与 Class 的对比
    • 为什么Hooks依赖调用顺序来正确关联状态与组件实例
    • 如何理解Hooks中的闭包机制

React Hooks 是 React 16.8 引入的函数组件增强机制,它允许你在不编写 class 的情况下使用 state 和其他 React 特性。
简单来说,Hooks 是 React 提供的一系列特殊函数,它们能让你"钩入"(hook into)React 的核心功能。

Hooks本质:

  • 函数组件的扩展工具:让函数式组件可以拥有类组件功能(状态、生命周期等)
  • 逻辑复用功能:代替高阶组件和render props的复杂模式
  • 代码组织方式:按照功能而非生命周期组织代码

Hooks的核心特点:

1、状态管理(useState)

function Counter() {const [count, setCount] = useState(0); // 状态声明return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

函数式组件可以拥有自己的状态,React在内部维护了一个“记忆单元”链表,保存Hook的状态

2、副作用管理(useEffect)

useEffect(() => {document.title = `You clicked ${count} times`;return () => { /* 清理逻辑 */ }; // 类似componentWillUnmount
}, [count]); // 依赖数组

代替了componentDidMount + componentDidUpdate + componentWillUnmount。可以将相关逻辑集中在一起,而非分散在不同生命周期上

3、上下文访问

const value = useContext(MyContext); // 直接获取context值

无需再使用Consumer组价包裹

Hooks的设计原理

1、调用顺序稳定性

  • react依赖Hooks的调用顺序来正确关联状态
  • 因此Hooks不能放在条件判断、循环语句中

2、闭包机制

function Counter() {const [count, setCount] = useState(0);useEffect(() => {const id = setInterval(() => {console.log(count); // 永远捕获声明时的count值}, 1000);return () => clearInterval(id);}, []);return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
  • 每次渲染都有独立的 props/state(闭包特性)
  • 通过函数式更新解决闭包问题:setCount(c => c + 1)

3、调度机制

  • useState/setState 会触发重新渲染
  • useEffect 在浏览器完成布局与绘制后异步执

Hooks 的底层实现(简化版)

let hooks = [];
let currentHook = 0;function useState(initialValue) {const _currentHook = currentHook++;if (hooks[_currentHook] === undefined) {hooks[_currentHook] = initialValue;}const setState = (newValue) => {hooks[_currentHook] = newValue;render(); // 触发重新渲染};return [hooks[_currentHook], setState];
}function useEffect(callback, deps) {const _currentHook = currentHook++;const hasChanged = !deps || !hooks[_currentHook] || deps.some((d, i) => d !== hooks[_currentHook][i]);if (hasChanged) {callback();hooks[_currentHook] = deps;}
}

Hooks 的最佳实践

1、命名规范:自定义Hook必须以“use”开头
2、条件调用:只有在最顶层调用Hooks
3、性能优化

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

4、自定义Hooks:提取可复用逻辑

function useWindowSize() {const [size, setSize] = useState({ width: window.innerWidth });useEffect(() => {const handler = () => setSize({ width: window.innerWidth });window.addEventListener('resize', handler);return () => window.removeEventListener('resize', handler);}, []);return size;
}

Hooks 与 Class 的对比

特性HooksClass Components
状态管理useState/useReducerthis.state
生命周期useEffectcomponentDidMount 等
代码组织按功能组织按生命周期拆分
逻辑复用自定义 HooksHOC/render props
this绑定无this绑定问题需要处理this
学习曲线较简单较复杂

为什么Hooks依赖调用顺序来正确关联状态与组件实例

React Hooks 依赖调用顺序稳定性来正确关联状态与组件实例,这是其核心设计原理之一。这种限制源于 Hooks 的实现机制

一、底层实现机制

React在内部使用链表结构管理Hooks状态,每次渲染时:

1、按顺序记录Hooks调用(如useState->useEffect->useState)
2、用链表节点保存对应状态(节点位置与调用顺序严格对应)
3、下次渲染时候按相同顺序读取

就类似于一个军训的点名场景:
教官按照固定顺序进行点名(调用顺序)
每位学生(Hook)必须每次都站在队伍中相同的位置
教官通过位置编号而非名字来识别每一位学生

具体工作流程分析:

1、react内部会维护一个“记忆链表”,结构如下:

[ useState → useEffect → useMemo → useState → … ]
↓ ↓ ↓ ↓
state effect memoized state

2、渲染更新过程

组件首次渲染:

function Example() {const [count, setCount] = useState(0);     // Hook 1const [name, setName] = useState('Alice'); // Hook 2useEffect(() => { /*...*/ });              // Hook 3
}

内部链表:

  1. useState(0) → 节点A { value: 0 }
  1. useState(‘Alice’) → 节点B { value: ‘Alice’ }
  2. useEffect → 节点C { effectFn, deps }

组件更新渲染:

function Example() {const [count, setCount] = useState(0);     // 读取节点Aconst [name, setName] = useState('Alice'); // 读取节点BuseEffect(() => { /*...*/ });              // 读取节点C
}

工作流程:
1、按顺序遍历链表
2、第一个 useState 读取节点A的状态
3、第二个 useState 读取节点B的状态
4、useEffect 读取节点C的信息

二、条件语句为何破坏稳定性

function MyComponent({ showExtra }) {if (showExtra) {const [extra, setExtra] = useState(null); // 🔴 条件性 Hook}const [count, setCount] = useState(0);     // ⚠️ 调用顺序可能变化
}

渲染过程:
当showExtra条件不变时候,第一次渲染和第二次渲染顺序就是一致的,当条件变化时候,顺序就会发生变化,如第一次为true,那么记录的位置是useState(null)->useState(0),当第二次为false时候,useState(0),那么第二次的位置就会读取到读取第一次的状态,这样值就是错乱的

三、同样的循环语句也会破坏稳定性

function DynamicHooks({ items }) {return items.map((item, i) => {const [value, setValue] = useState(item); // 🔴 循环中的 Hookreturn <div key={i}>{value}</div>;});
}

1、Hooks调用次数变化:items.length改变会导致Hooks数量不一致
2、状态关联错乱:新增、删除等会使Hooks的读取错误位置的状态

四、React的Hooks必须在函数组件的最顶层且无条件的调用

1、状态关联错乱:Hooks与组件的状态是通过调用顺序关联的,条件调用会导致Hooks在不同条件渲染下对应到错误的状态
2、调试困难:难以追踪那些Hooks被调用、被跳过;导致难以复现的bug
3、性能优化失效:React依赖稳定的Hooks顺序进行优化,动态的Hooks会破坏memoization 和 bailout 机制

五、如何解决

1、编译时检查:通过ESLint 插件强制规则

// eslint-disable-react-hooks/rules-of-hooks

2、运行时报错:开发模式下React会抛出错误

六、如何正确实现条件、循环逻辑

方式一:提前调用所有的Hook

function MyComponent({ showExtra }) {const [extra, setExtra] = useState(null); // 始终调用const [count, setCount] = useState(0);return (<>{showExtra && <div>{extra}</div>}<div>{count}</div></>);
}

方式二:拆分组件

function ExtraComponent() {const [extra, setExtra] = useState(null); // 在独立组件中使用return <div>{extra}</div>;
}function MyComponent({ showExtra }) {const [count, setCount] = useState(0);return (<>{showExtra && <ExtraComponent />}<div>{count}</div></>);
}

如何理解Hooks中的闭包机制

React Hooks 的闭包机制是其核心设计特性之一,理解这一机制对于正确使用 Hooks 至关重要,下面详解:

一、闭包机制的本质

在 JavaScript 中,闭包是指函数能够访问并记住其词法作用域的特性。在 React 函数组件中:

1、每次渲染都是一个独立的函数调用
2、每次调用都会创建新的作用域和闭包
3、所有 props 和 state 都被"捕获"在该次渲染的闭包中

function Counter() {const [count, setCount] = useState(0);useEffect(() => {const id = setInterval(() => {console.log(count); // 永远捕获声明时的count值}, 1000);return () => clearInterval(id);}, []);return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

渲染过程中闭包的表现:

1、首次渲染(count=0)

// 闭包1
const count = 0;
useEffect(() => {// 这里的count永远为0setInterval(() => console.log(0), 1000);
}, []);

2、点击按钮后(count=1)

// 闭包2
const count = 1;
// 但之前useEffect中的回调仍然引用闭包1的count(0)

3、事件函数中的闭包

function Example() {const [count, setCount] = useState(0);function handleClick() {setTimeout(() => {console.log(count); // 点击时的count值}, 3000);}return <button onClick={handleClick}>Click</button>;
}
  • 每次点击都会创建一个新的handleClick函数
  • 每个函数都捕获了当次渲染时的count值

解决闭包问题的方案:

1、函数式更新:

setCount(c => c + 1); // 获取最新状态

2、useRef 保存可变值

const countRef = useRef(count);
useEffect(() => {countRef.current = count; // 每次渲染后更新
});// 在任何闭包中访问 countRef.current

3、使用 useReducer

const [state, dispatch] = useReducer(reducer, { count: 0 });
// dispatch总是引用同一个函数

如何理解所有 props 和 state 都被"捕获"在该次渲染的闭包中

在上述代码中我们已经理解了闭包机制的本质。在React组件中,每次渲染都是一个独立的函数调用过程,这个过程会创建一个新的闭包作用域,在这个作用域中:

  • 所有的props和state值都会被“冻结”,就想拍照一样,记录当下渲染时的值
  • 所有函数定义都会被绑到这个闭包中,包括事件函数、effect回调等
  • 即便外部组件重新渲染,这个闭包中的值也不会被改变,即保持渲染时的快照

举例:

function Counter({ initialCount }) {const [count, setCount] = useState(initialCount);function handleClick() {setTimeout(() => {console.log(`Count at render: ${count}, Prop: ${initialCount}`);}, 3000);}return (<div><button onClick={handleClick}>Show Values</button><button onClick={() => setCount(c => c + 1)}>Increment</button></div>);
}

上述代码中假设initialCount 初始值是0
首次渲染时候,initialCount 为0 count也是0
当点击按钮后,count的值变为1 initialCount 依然是0
如果在第一次渲染后点击 Show Values,3秒后将打印

Count at render: 0, Prop: 0
即便组件已经更新 重新渲染了 但是旧的值依然是旧的值 因为在旧的闭包中仍然保持着它创建时的值

http://www.dtcms.com/a/328598.html

相关文章:

  • 北京-4年功能测试2年空窗-报培训班学测开-第七十四天-线下面试-聊的很满意但可能有风险-等信吧
  • mysql中的常见的索引类型及其特点
  • 【活动回顾】开源共建 · 智能体时代的 AI 基础设施全栈实践
  • 并发编程中的 ABA 问题:从原理到实战解决方案
  • Java数据结构之LinkedList
  • 电子电路原理学习笔记---第5章特殊用途二极管---第2天
  • 基于51单片机RFID智能门禁系统红外人流量计数统计
  • -bash: ./restart.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录
  • MySQL 从入门到精通:基础概念与操作指南
  • Sklearn 机器学习 异常值检测 局部异常因子算法LOF
  • SQL连接操作全解析:从入门到精通
  • 某跨国金融机构法律法规自动文本摘要(ATS/文本大意提取)功能规划
  • 嵌入式第二十六天(文件IO相关操作)
  • [Robotics_py] docs | 机器人状态/位姿 | 环境表示_栅格地图
  • 准则 :用“检测到什么”的方式来编写需求条件
  • Python 异常捕获
  • 为什么我换了项目管理软件?
  • 如何在 Odoo 18 管理产品文档手册
  • Redis面试题及详细答案100道(16-32) --- 数据类型事务管道篇
  • 第23章,景深:技术综述
  • 软件测试之功能测试
  • 嵌入式系统学习Day17(文件编程)
  • (树形 dp、数学)AT_dp_v Subtree 题解
  • 架构设计:设计原则
  • 第十一节:加载外部模型:GLTF/OBJ格式解析
  • [MySQL数据库] 数据库简介
  • 【虚拟机】VMwareWorkstation17Pro安装步骤
  • Tricentis Tosca 2025.1 LTS 系统要求
  • 华为OD最新机试真题-国际移动用户识别码(IMSI)匹配-(C卷)
  • Terminal Security: Risks, Detection, and Defense Strategies