什么是Hook?
React Hook 是 React 16.8 版本引入的一种新特性,它是一种用于函数组件中添加状态和生命周期的方式。让开发者在不使用类组件的情况下,实现类似类组件的state、生命周期功能,可以提供更好的代码复用、代码可读性。
Hook规则
- 不要在普通的 JavaScript 函数中调用 Hook
- 保证每一次渲染中都按相同顺序调用(不要在循环,条件或嵌套函数中调用 Hook)
Hook运行过程
- 在函数组件中调用Hook函数时(例useState、useEffect等),React会在内部(Fiber的memoizedState上)维护一个Hook链表
- 当组件渲染时,React会按照Hook链表的顺序依次执行每个Hook函数,并根据每个Hook对象来更新对应的状态或执行对应的副作用等
- 当组件重新渲染时,React会再次按照相同的顺序执行每个Hook函数,并将返回的值和上次保存的值进行比较,如果有变化,则触发相应的更新操作
初步形态
因为自己写的 useState 无法控制 React 的重新渲染,所以我通过重新运行 ReactDOM 的 render 来模拟 React 的重新渲染。
import React from "react";
import ReactDOM from "react-dom";
let memorizedValue;
const useState = (initValue) => {
memorizedValue = memorizedValue || initValue;
const setState = (newValue) => {
memorizedValue = newValue;
render();
};
return [memorizedValue, setState];
};
const App = () => {
const [number, setNumber] = useState(0);
return (
<>
<p>number: {number}</p>
<button onClick={() => setNumber(number + 1)}>add</button>
</>
);
};
function render() {
ReactDOM.render(<App />, document.querySelector("#root"));
}
render();
支持多个setState
上述简单 useState 虽然可以正常运行,但是不能设置多个 useState,所以我在上述代码基础上,添加了 states 和 index 去控制多个 useState 的情况(在 React Hook源码中是用链表控制)。
import React from "react";
import ReactDOM from "react-dom";
const states = [];
let index = 0;
const useState = (initValue) => {
const currentIndex = index;
states[currentIndex] = states[currentIndex] || initValue;
const setState = (newValue) => {
states[currentIndex] = newValue;
render();
};
index++;
return [states[currentIndex], setState];
};
const App = () => {
const [number1, setNumber1] = useState(0);
const [number2, setNumber2] = useState(0);
return (
<>
<p>number1: {number1}</p>
<p>number2: {number2}</p>
<button onClick={() => setNumber1(number1 + 1)}>add</button>
<button onClick={() => setNumber2(number2 + 1)}>add</button>
</>
);
};
function render() {
ReactDOM.render(<App />, document.querySelector("#root"));
index = 0;
}
render();
在上述代码,用了 status index 代替了 React Hook 链表结构,真实的链表结构大概这样:
const hook: Hook = {
memoizedState: null, // 保存 hook 数据,例如 useState 就是保存 state
baseState: null, // 已处理的 update 计算出来的 state
baseQueue: null, // 未处理的 update
queue: null, // 当前处理的 update
next: null, // 指向下一个 hook
};
总结
通过上面的例子我们可以得出,在定义、渲染、重新渲染都和Hook的调用顺序有关,所以不能使用条件、循环去调用Hook,因为这些调用方式都有可能改变Hook的调用顺序,所以React才会定义 “保证每一次渲染中都按相同顺序调用” 这个规则。