[译] React Hooks 揭秘:数组解构融成魔法

用模型解析此提案的执行规则

我超喜欢 React 新出的这个 Hooks API。而在使用它时却有一些奇怪的规则。为了那些纠结于为何要有这些规则的人,在这里我会以模型图的方式来向大家展现这个新的 API。html

警告: Hooks 这项提案仍在测试阶段

这篇文章主要讲述的是关于 React hooks 这项新 API,此时这个提案仍处于 alpha 测试版本。你能够在 React 官方文档中找到稳定的 React API。前端


图片来自网站 Pexels(rawpixel.comreact

拆解 Hooks 的工做方式

“就像魔法同样没法理解”,这是我从一些人口中听到的对于这项新提案的评价,因此我愿意尝试经过拆解这项提案的代码语法去了解它是如何在代码中工做的,至少也要了解它最表层的执行逻辑。android

Hooks 的规则

React 的核心团队规定咱们在使用 hooks 时必须听从 hooks 提案文档中列出的两条规则。ios

  • 不要在循环、条件语句或者嵌套函数中调用 Hooks
  • 只能在 React 函数中调用 Hooks

对于后者我以为是不言而喻的。要将本身的业务代码嵌入到功能组件当中,你天然须要以某种方式将你的代码和组件联系起来。git

至于前者我认为也是使人感到困惑的一点,由于这与正常编程时调用 API 的方式相比起来并不寻常,这也是我今天正想探索的一点。github

在 hooks 中,状态管理都与数组有关

为了让咱们更清晰地理解这个模型,让咱们直接实现一个简单的 hooks API 实例看看它可能长什么样子。编程

请注意这只是推测,而且只是可能的 API 实现方法,主要是为了展现应该经过怎样的思惟去理解它。固然这并不必定就是 API 实际的内部工做方式。并且目前这也只是一个提案,在将来一切均可能发生改变后端

咱们能够怎样实现 useState()

让咱们经过剖析一个例子来演示一下状态钩子的实现可能会怎么运行吧。数组

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

function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi");
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}
复制代码

实现 hooks API 的基本思想是把一个 setter 方法做为钩子函数返回的第二个数组项,以后经过这个 setter 方法来控制钩子管理的状态值。(在这个例子中,setter 方法指 setFirstName,钩子就是 useState 而它管理的值就是"Rudi")

因此让咱们来看看 React 将利用它来作些什么?

让我先解释一下在 React 的内部中它可能会怎么工做。下面图表将为咱们展现一个特定组件在渲染时其内部执行上下文的执行过程。这也意味着存储在这里的数据是从外面传入的。虽然这里的状态没有与其余组件共享,可是它会继续保留在必定范围以内以供后续渲染对应的特殊组件时使用。

1) 初始化

声明两个空数组:settersstate

设置一个 cursor 参数为 0

初始化:两个空数组,cursor 参数为 0


2) 初次渲染

第一次执行组件方法

当调用 useState() 时,第一次会将一个 setter 方法(就是以 cursor 为下标的参数)添加到 setters 数组当中而后再把一些状态添加到 state 数组当中。

初次渲染:cursor ++,变量分别被写入数组当中


3) 后续渲染

后续的每一次渲染过程中 cursor 都将被重置,而渲染的值都只是从每一次的数组当中取出来的。

后续渲染:从数组(以 cursor 为下标)中读取了每一项变量的值


4) 事件代理

由于每个 setter 方法都和其 cursor 绑定以致于经过触发来调用任何 setter 时它都将改变对应索引位置的状态数组的状态值。

Setters “记住”了他们的索引并根据它来写入内存。


以及底层的实现

这里用一段代码示例来展现:

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

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

// 这是我仿写的 useState 辅助类
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>
  );
}

// 这里模拟了 React 的渲染周期
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

console.log(state); // After-click: ['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。一块儿来看这样会对整个系统形成多大的影响。

被“破坏”的组件第一次渲染

渲染一个外部的“坏”钩,以后它将会在下次渲染时消失不见。

在此时咱们在实例中声明的 firstNamelastName 暂时还带着正确的数据,但接下来让咱们看看在第二次渲染时会发生什么:

被“破坏”的组件第二次渲染

在渲染时移除了钩子以后,咱们发现了一个错误。

因为在此时咱们的状态存储了错位的数据使 firstNamelastName 被同时赋值为 “Rudi”。这很明显是不正确的并且它也没有任何做用,可是这也给咱们说明了为何 hooks 具备这样的规则限制。

React 团队正在制定使用规范由于不遵照它们将会使数据错位。

思考一下如何在不违反规则的状况下使用 hooks 操做一组数组

因此如今咱们应该很是清楚为何咱们不能在循环或者条件语句中调用 use 钩子。由于事实上咱们的代码处理是基于数组解构赋值的,若是你更改了渲染时的调用顺序,那么就会使咱们的数据或者事件处理器在解构后没有匹配正确。

因此技巧是考虑让 hooks 用一致的 cursor 来管理数组业务,若是你这么作就可让它正常的工做。

结论

但愿我是创建了一个比较清晰的模型向大家展现了这个新 hooks API 在底层是如何进行工做的。记住这里真正的价值是如何把咱们所好奇的点一步步解析出来,因此你只要多注意 hooks API 的规则,这样咱们就能更好的利用它。

在 React 组件中 Hooks 是一个高效率的 API。这也是人们对它趋之若鹜的缘由,若是你还没想到答案,那么只要把数组存储为状态的形式,就会发现你并无破坏他们的规则。

我但愿未来能够持续再了解一下 useEffects 方法而且尝试对比一下它和 React 的那些生命周期方法有什么区别。


这篇文章尚有不足之处,若是你有更好的建议或者发现了文章中的错误纰漏请及时跟我联系。

你能够在 Twitter 上关注 Rudi Yardley @rudiyardley 或者关注 github _@_ryardley

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索