阅读 523

React hooks:并不神奇,就是数组

原文地址: medium.com/@ryardley/r…
译文地址:github.com/xiao-T/note…
本文版权归原作者所有,翻译仅用于学习。

使用图解的方式揭开规范中的使用规则

我是新 API Hooks 的粉丝。然而,使用它有一些奇怪的约束。在这,我为那些想使用新 API,但是,又不理解规则的人,展示一个模型以便于更好的理解规则。

img

了解 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)初始化

创建两个空数组:settersstate

设置指针为 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 在条件语句中执行。我们来看看这会给系统带来什么问题。

不良组件的第一次渲染

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

不良组件的二次渲染

现在,firstName and lastName 都被设置成了 “Rudi”,这就让 state 变的不一致了。这明显是个错误,也不能工作。但是,正好可以让我们理解为什么 React Hooks 会存在那些规则了。

React 团队之所以明确使用规则,这是因为如果不遵守规则会导致数据的不一致

想象一下 hooks 是通过数组维护的,你就不会打破规则了

现在,应该清楚的知道了为什么不能在条件语句或者循环中调用 hooks 了。因为,我们通过数组指针来维护 setterstate 的对应关系,如果,在渲染时改变了调用顺序,指针就不能正确的匹配 setterstate

因此,我们需要关心的是:将管理业务的 Hooks 视为一组指针无变化的固定数组。如果,你这么做了,一切将会正常。

总结

希望我已经整理出了一个更加清晰明确的模型,可以更好的理解新 Hooks API 底层是如何工作的。记住真正的值是关联在一起的数组,因此要多注意它们之间的顺序,使用 hooks API 将会得到更高的好处。

Hooks 是 React 组件有效的 API。人们喜欢它是有原因的,如果,你打算用这类模型存储 state,那么你就不要违反使用规则。

文章分类
前端
文章标签