理解 React Hooks 心智模型:必须按顺序、不能在条件语句中调用的规则
前言
自从 React 推出 hooks 的 API 后,相信大家对新 API 都很喜欢,但是它对你如何使用它会有一些奇怪的限制。比如,React 官网介绍了 Hooks 的这样一个限制:
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的
useState和useEffect调用之间保持 hook 状态的正确。
useState Hook 的工作原理
这个限制并不是 React 团队凭空造出来的,的确是由于 React Hooks 的实现设计而不得已为之。
为了让大家有一个更清晰的思维模型,我将用数组来模拟useState的简单实现。
首先让我们通过一个例子来看看 hook 是如何工作的。
我们首先从一个组件开始:
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
useState hook 背后的思想是,你可以使用 hook 函数返回的数组的第二个数组项作为 setter 函数,并且该 setter 将控制由 hook 管理的状态。
具体实现
1)初始化
创建两个空数组:setters 和 state
将 cursor 设置为 0

2)第一次渲染
首次运行组件函数。
每次useState()调用,在第一次运行时,都会将一个 setter 函数推送到 setters 数组上,然后将一些状态推送到 state 数组上。

3)后续渲染
每次后续渲染都会重置 cursor,并且仅从每个数组中读取这些值。

4)事件处理
每个 setter 都有对其 cursor 的引用,因此通过触发对 setter 的调用,setter 它将更改状态数组中该位置的状态值。

简单代码实现
下面通过一段简单的代码示例来演示该实现。
注意:这并不是 React 的底层实现,但对于我们理解 react hook 的心智模型非常有帮助。
const state = [];
const setters = [];
let cursor = 0; function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
} export function useState(initVal) {
if (state[cursor] === undefined && setters[cursor] === undefined) {
state.push(initVal);
setters.push(createSetter(cursor));
} const setter = setters[cursor];
const value = state[cursor]; cursor++;
return [value, setter];
} function RenderFunctionComponent() {
const [firstName, setFirstName] = useState('Rudi'); // cursor: 0
const [lastName, setLastName] = useState('Yardley'); // cursor: 1 return (
<div>
<button onClick={() => setFirstName('Richard')}>Richard</button>
<button onClick={() => setLastName('Fred')}>Fred</button>
</div>
);
} function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
} console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley'] // click the 'Richard' button console.log(state); // After-click: ['Richard', 'Yardley']
为什么顺序很重要
现在,如果我们根据某些外部因素甚至组件状态更改渲染周期的钩子顺序会发生什么?
让我们做 React 团队说你不应该做的事情:
let firstRender = true;
function RenderFunctionComponent() {
let initName;
if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
这里我们有useState的一个条件调用。让我们看看这对系统造成的破坏。
Bad Component 第一次渲染

我们的实例变量firstName和lastName包含正确的数据,但让我们看看第二次渲染会发生什么:
Bad Component 第二次渲染

现在,firstName和lastName发生了错位,我们的状态存储变得不一致了。这就是为什么保持正确顺序的重要性。
总结:
通过对 useState 的简单实现来理解 react hooks 的幕后实现逻辑。考虑将状态作为一组数组存在的模型,那么我们不该违反其对应的使用规则。
理解 React Hooks 心智模型:必须按顺序、不能在条件语句中调用的规则的更多相关文章
- 理解 React Hooks
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由志航发表于云+社区专栏 TL;DR 一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classe ...
- 通过 React Hooks 声明式地使用 setInterval
本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他 ...
- 关于React Hooks,你不得不知的事
React Hooks是React 16.8发布以来最吸引人的特性之一.在开始介绍React Hooks之前,让咱们先来理解一下什么是hooks.wikipedia是这样给hook下定义的: In c ...
- React Hooks 深入系列
本文基于近段时间对 hooks 碎片化的理解作一次简单梳理, 个人博客.同时欢迎关注基于 hooks 构建的 UI 组件库 -- snake-design. 在 class 已经融入 React 生态 ...
- 实现 React Hooks
实现 React Hooks UI 开发有两个问题: 展示复用 逻辑复用 展示复用目前基本使用组件化来解决,逻辑复用一直以来都没有特别好的解决方案.React 从一开始的 mixin ,到 高阶组件 ...
- react新特性 react hooks
本文介绍的是react新特性react hooks,本文面向的是有一定react开发经验的小伙伴,如果你对react还不是很熟悉的话我建议你先学习react并多多联系. 首先我们都知道react有3种 ...
- 30分钟精通React今年最劲爆的新特性——React Hooks
你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周 ...
- React Hooks用法大全
前言 在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖 ...
- React Hooks 实现和由来以及解决的问题
与React类组件相比,React函数式组件究竟有何不同? 一般的回答都是: 类组件比函数式组件多了更多的特性,比如 state,那如果有 Hooks 之后呢? 函数组件性能比类组件好,但是在现代浏览 ...
随机推荐
- 配置NFS共享, Linux与Linux的共享
• Network File System,网络文件系统 – 用途:为客户机提供共享使用的文件夹 – 协议:NFS(TCP/UDP 2049).RPC(TCP/UDP 111) 只读的NFS共享 服务 ...
- Redis分布式锁 (图解-秒懂-史上最全)
文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...
- Java8新特性代码示例(附注释)- 方法引用,Optional, Stream
/** * java8中的函数式接口,java中规定:函数式接口必须只有一个抽象方法,可以有多个非抽象方法,同时,如果继承实现了 * Object中的方法,那么也是合法的 * <p> * ...
- 使用Azure WebJob的一点心得
Azure WebApp Service 是非常适合中小型项目的云服务. 从我实际使用的感受来看, 有如下几个优点: 1 部署方便, 可以从VS一键发布 2 缩放方便, scale in / scal ...
- 没有指定非静态方法,Unity与Android通信错误
报错信息: AndroidJavaException: java.lang.NoSuchMethodError: no non-static method with name='InstallApk' ...
- Linux中ls的用法
在linux系统中,可以说一切皆文件.文件类型包含:普通文件,目录,字符设备文件,块设备文件,符号链接文件等 我们可以用file这个命令来查看文件的属性: 这里可以看到1.sh是个脚本文件 下面开始介 ...
- 并发王者课-铂金10:能工巧匠-ThreadLocal如何为线程打造私有数据空间
欢迎来到<并发王者课>,本文是该系列文章中的第23篇,铂金中的第10篇. 说起ThreadLocal,相信你对它的名字一定不陌生.在并发编程中,它有着较高的出场率,并且也是面试中的高频面试 ...
- CentOS-Docker搭建Nextcloud
下载镜像 $ docker pull nextcloud 运行镜像 $ docker run -d --restart=unless-stopped --name nextcloud -v /home ...
- Docker | Docker常用命令学习笔记
@ 目录 前言 1. 帮助命令: version.info.help 2. 镜像命令: images.search pull.rmi 3. 容器命令: pull.run ps.exit .ctrl+P ...
- PHP解决并发问题的几种实现(转)
对于商品抢购等并发场景下,可能会出现超卖的现象,这时就需要解决并发所带来的这些问题了 在PHP语言中并没有原生的提供并发的解决方案,因此就需要借助其他方式来实现并发控制. 方案一:使用文件锁排它锁 ...