原文地址: medium.com/@ryardley/r…
译文地址:github.com/xiao-T/note…
本文版权归原作者所有,翻译仅用于学习。
使用图解的方式揭开规范中的使用规则
我是新 API Hooks 的粉丝。然而,使用它有一些奇怪的约束。在这,我为那些想使用新 API,但是,又不理解规则的人,展示一个模型以便于更好的理解规则。

了解 hooks 的工作模式
我听到有些人对于那些 hooks API 的规范难以理解,因此,在表面上,我可以尝试揭示规范中的新语法是如何工作的。
Hooks 的规则
有两条使用规则是 React 核心团队明确规定的,在使用 hooks 时,你应该按照文档的约束来做。
- 不要在循环内部、条件语句和嵌套方法中调用 Hooks
- 只在函数组件中调用 Hooks
第二条不言而喻。为了函数组件有更强大的功能,你需要一些方法来实现。
然而,对于第一条,有点混乱,这有点不符合常规。这也是我想要解释的地方。
Hooks 通过数组管理 State
为了得到一个清晰明确的模型,我们来实现一个简单版本的 Hooks API。
请注意:这只是一种猜测,为了演示我是如何理解的 Hooks,唯一一种实现方式。这并不是 Hooks 内部真正的实现方式。
如何实现 useState()
?
我们通过一个演示,来解释 hook 是如何工作的。
首先,从一个组件开始:
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
Hooks API 背后的理念是:你可以通过 hook 函数返回一个 setter 方法,然后,这个 setter 可以控制 hook 返回的 state。
React 如何处理的呢?
让我们来一一解释 React 内部是如何工作的。以下的内容将会在特定组件的上下文环境中执行。也就是说,在组件被渲染后数据存储在组件外面一层。这些数据并不会和其他组件共享,但是,在后续的渲染中可以被特定的组件的访问。
1)初始化
创建两个空数组:setters
和 state
设置指针为 0

2)第一次渲染
执行函数实现组件的第一次渲染。
第一次渲染时,每次执行 useState()
,添加一个 setter 方法(绑定指针的位置)到 setters
数组中,然后,添加相应的 state 到 setate
数组中。

3)后续渲染
后续的每次渲染指针都会被重置,然后读取相应的数据。

4)事件处理
每个 setter 对应指针的位置都会有一个引用,因此,任何 setter
的调用都会触发 state 数组中相应位置数据的变化。

最后简易版本的实现
接下来的代码只是演示如何实现的。
注意:这并不代表所有的 hooks 都是这么实现的,但是,这可以给你一个很好的启发,可以更好的理解 hooks 如何在组件中工作的。That is why we are using module level vars etc.
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
// Our component code that uses hooks
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={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// This is sort of simulating Reacts rendering cycle
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 'Fred' button
为什么顺序非常重要
现在,如果我们根据一些外部的数据或者组件 state 改变组件中 hooks 的顺序会发生什么呢?
我们来做一些 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
在条件语句中执行。我们来看看这会给系统带来什么问题。
不良组件的第一次渲染

这个时候,我们的变量 firstName
和 lastName
都有正确的数据,但是,让我们看看在第二次渲染时会发生什么。
不良组件的二次渲染

现在,firstName
and lastName
都被设置成了 “Rudi”
,这就让 state 变的不一致了。这明显是个错误,也不能工作。但是,正好可以让我们理解为什么 React Hooks 会存在那些规则了。
React 团队之所以明确使用规则,这是因为如果不遵守规则会导致数据的不一致
想象一下 hooks 是通过数组维护的,你就不会打破规则了
现在,应该清楚的知道了为什么不能在条件语句或者循环中调用 hooks 了。因为,我们通过数组指针来维护 setter
和 state
的对应关系,如果,在渲染时改变了调用顺序,指针就不能正确的匹配 setter
和 state
。
因此,我们需要关心的是:将管理业务的 Hooks 视为一组指针无变化的固定数组。如果,你这么做了,一切将会正常。
总结
希望我已经整理出了一个更加清晰明确的模型,可以更好的理解新 Hooks API 底层是如何工作的。记住真正的值是关联在一起的数组,因此要多注意它们之间的顺序,使用 hooks API 将会得到更高的好处。
Hooks 是 React 组件有效的 API。人们喜欢它是有原因的,如果,你打算用这类模型存储 state,那么你就不要违反使用规则。