自从 React 推出 hooks 的 API 后,相信你们对新 API 都很喜欢,可是它对你如何使用它会有一些奇怪的限制。好比,React 官网介绍了 Hooks 的这样一个限制:javascript
不要在循环,条件或嵌套函数中调用 Hook, 确保老是在你的 React 函数的最顶层以及任何 return 以前调用他们。遵照这条规则,你就能确保 Hook 在每一次渲染中都按照一样的顺序被调用。这让 React 可以在屡次的
useState
和useEffect
调用之间保持 hook 状态的正确。java
这个限制并非 React 团队凭空造出来的,的确是因为 React Hooks 的实现设计而不得已为之。react
为了让你们有一个更清晰的思惟模型,我将用数组来模拟useState
的简单实现。数组
首先让咱们经过一个例子来看看 hook 是如何工做的。函数
咱们首先从一个组件开始:spa
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 管理的状态。设计
建立两个空数组:setters
和 state
code
将 cursor 设置为 0blog
首次运行组件函数。事件
每次useState()调用,在第一次运行时,都会将一个 setter 函数推送到 setters 数组上,而后将一些状态推送到 state 数组上。
每次后续渲染都会重置 cursor,而且仅从每一个数组中读取这些值。
每一个 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
的一个条件调用。让咱们看看这对系统形成的破坏。
咱们的实例变量firstName
和lastName
包含正确的数据,但让咱们看看第二次渲染会发生什么:
如今,firstName
和lastName
发生了错位,咱们的状态存储变得不一致了。这就是为何保持正确顺序的重要性。
经过对 useState 的简单实现来理解 react hooks 的幕后实现逻辑。考虑将状态做为一组数组存在的模型,那么咱们不应违反其对应的使用规则。