【译】React hooks: 不是魔法,只是数组

图解hooks的规则react

我是新的hooks API的超级粉丝。然而,在使用hooks的时候有一些奇怪的限制。在这里,我将为那些对于理解这些规则有困难的人,呈现一个模型来解释如何去思考新的API。数组

Photo by rawpixel.com from Pexels

揭秘hooks是如何工做的

我听闻有些人对于新的hooks API的“魔力”感到很是困惑,所以我想我会至少在使用级别揭秘这个语法是怎么工做的。bash

hooks的原则

对于hooks的使用,React核心团队规定了两条使用规则,这两条规则列在了文档Hooks规则中。函数

  • 不要在循环,条件或者是嵌套函数中使用hooks
  • 只在React函数中调用hooks

后一条规则,我想是显而易见的。为了将这些功能添加到(React)函数组件中,就必需要将这些功能与(React)组件联系起来。 然而,前一条规则,我想多是比较使人疑惑的。由于对于使用一个像这样的API来讲,这条规则彷佛不太常见。这也正是,我今天想要探索内容。ui

在hooks中的状态的管理全是关于数组的

为了获取到一个更加形象的模型,让咱们来看下hooks API的一个简单的使用是什么样的。spa

请注意这只是猜想,也只是使用API的一种方式,用来展示关于hooks,你可能想知道的。这并不必定是API内部如何真正工做的。插件

咱们该如何使用useState()?

让咱们在这里演示一个例子,来论证下state hook的可能的工做方式。code

首先让咱们从一个组件开始:cdn

function RenderFunctionComponent() {
    const [firstName, setFirstName] = useState('Rudi');
    const [lastName, setLastName] = useState('Yardley');
    
    return (
        <Button onClick={() => setFirstName('Fred')}>Fred</Button>
    );
}
复制代码

在hooks API背后的思想是:你可使用一个setter函数做为hook函数返回值的数组的第二个元素。这个setter函数会控制被hook管理的state状态值。

因此React将会对这些作什么呢?

让咱们来解释下这些在React的内部多是怎么工做的。接下来的执行过程是在渲染一个特定组件的执行上下文中运行。这意味着这里存储的数据在渲染组件的外级存放。这个state不与其余组件共享,只维护在一个做用域中,在这个特定组件二次渲染的时候能够获取获得。

1)初始化

建立两个空的数组:settersstate

设置光标为0:

初始化:两个空的数组,光标是0

首次渲染

第一次运行这个函数组件。 每一个useState调用,首次运行的时候,都会将一个setter函数(绑定着一个光标位置)推动setters数组中,而后将一些state状态值推动state数组。

首次渲染:随着光标增长,每个元素被写入数组

二次渲染

每一个二次渲染,光标都会被重制,那些值正是从每个数组中去读取。

二次渲染:随着光标增长,值从数组中读取

事件处理

每个setter跟它的光标位置之间都有一个对应关系。所以任一一个setter被触发调用,在state数组中,相应位置的state的值就会被改变。

Setters会“记住”他们的位置索引值,并据此设置内存值

简单的模拟

这里是一段代码示例来论证执行过程。

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
    return function setterWithCursor(newVal) {
        state[cursor] = newVal;
    }
}

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];
}
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>
    )
}

function MyComponent() {
    cursor = 0; // 重置光标
    return <RenderFunctionComponent />; // 渲染
}

console.log(state); // 渲染以前:[]
MyComponent();
console.log(state);  // 首次渲染: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // 再次渲染: ['Rudi', 'Yardley']

// 点击'Fred'按钮
console.log(state); // 在点击之后:['Fred', 'Yardley']
复制代码

为何顺序是重要的

若是咱们基于一些外在因素或者甚至是组件的状态,来改变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在条件语句中被调用。让咱们来看下这将会对这个系统形成哪些破坏。

有问题组件的首次渲染

渲染一个二次render时不执行的额外的有问题的hook

此时咱们的例子中的变量firstNamelastName包含着正确的数据。可是让咱们来看下第二次渲染的时候发生了什么:

有问题组件的第二次渲染

在渲染之间移除了一个hook,咱们获得了一个错误

当下firstNamelastName都被设置为了Rudi,由于咱们的state的存储变得不一致了。这显然是错误的、而且不能正常工做的。可是这也给了咱们一个解释,为何hooks的原则要被设置成那样。

React团队规定这些规则,由于不遵循这些规则将会致使数据的不一致性

设想hooks是正在操做一系列的数组,你将不会违反这些规则

因此如今应该很明确了,为何你不能在条件或者是循环中调用hooks。由于咱们正在处理一个指向一系列数组的光标。若是你在render之间改变调用的顺序,光标将没法与数据相匹配,同时你的use调用将不会指向正确的数据或者是处理器。

因此这里的技巧是设想hooks是把它的功能逻辑看成一系列数组来处理,同时还须要一个可以保持一致性的光标。若是你能作到这一点,全部的hooks都将能正常工做。

总结

但愿我已经呈现了一个比较清晰的思惟模型,可以帮助你们思考在新的Hooks API下,一切都是怎么运行的。记得这里(hooks)真正的价值是可以将一组相关的逻辑组合在一块儿,所以对于执行顺序要当心。使用hooks API将会有较高的回报。

Hooks对于React组件是一个有用的API插件。这也是人们对于hooks感到比较激动的一个缘由。若是你将这种模型想象成一系列的存放state状态值的数组,那么你应该不会在使用他们的时候违反这些规则。

相关文章
相关标签/搜索