EasonYou / my-blog

It's my blog recording front-end
2 stars 0 forks source link

[翻译]React hooks-没有魔法,只是数组而已 #10

Open EasonYou opened 4 years ago

EasonYou commented 4 years ago

第一次翻译,会有很多纰漏,主要目的还是自我学习 react-hooks-not-magic-just-arrays

React hooks: 没有魔法,只是数组而已

当你用react hook的时候会有一些奇怪的约束,这里我提供一些思路,供给如何去理解这些规则。

解释下hooks是如何工作的

我听说过有些人对于hooks API的草案的“魔法”非常疑惑且挣扎,所以我想我可以尝试去解释下,至少在表层上,这个功能是如何运作的

hooks的规则

react的核心团队约定了一些规则,开发者需要跟着这些规则去使用。这里有两个主要的使用规则

我认为第二条规则是不言而喻的。hooks是函数组件的一个扩展方法,你需要去用各种方式关联到函数组件。

第一条我想可能对有些人会比较疑惑,因为它看起来在并不自然,而这也是我想解释的点。

hooks中的管理状态都是跟数组有关

让我们看一个简单的hooks API可能的实现方式,可以让我们对它的心智模型有清晰的了解。

如何实现一个useState()

来看一个例子,来验证state hook是如何工作的

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

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

hooks背后的思想就是,你可以在函数返回数组中的第二个值,拿到一个setter,这个setter会控制你对应的state,这个state则由hook来管理

那么react想用这个做什么呢?

来解释下这个在react内部会怎么实现。以下的执行过程会在一个执行上下文中运作,来渲染一个特定的组件。这意味着数组存储在被渲染的组件之外的一层。这个state不与其他的组件共享,但是它可以在这个范围内是被渲染在特定的组件的(todo)

  1. 初始化 创建两个空数组:setter和state 给这个设置一个为0的指针 image

  2. 首次渲染 函数组件第一次执行

每个useState都被调用,当第一次执行,把setter方法到setters数组,把state,放到state数组 image

  1. 后续渲染 随后的每次渲染,指针都会重置,然后从每个数组中读取这些值 image

  2. 事件绑定 每个setter都可以通过其自身的指针找到对应的state的位置,并改变其state数组中该位置的值 image

简单的实现

这里的实例代码,对其做了简单的实现

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

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,让我们来看看这对整个系统有什么不好的地方

不良组件的首次渲染

image

在这里,我们的例子定义了firsName和lastName,这都是正确的数据。但让我们看一下接下来会发生什么

不良组件的二次渲染

image

现在firstName和lastName都被设置成Rudi,因为我们的的存储状态变得不一致了。这是一个显而易见的错误且不能执行,但它让我们了解了上面的hooks规则为什么要这样来做约束。

只要考虑到hooks是去操作数组的数据的,你就不会破坏这个规则

现在对于为什么不能在条件组或者循环中使用hooks应该已经很清晰了。因为我们要去处理数组的指针位置,如果我们在发生渲染的时候改变了它们的顺序,指针将会无法匹配到对应的正确数据或者进行正确的处理程序。

所以管理hooks的诀窍是将它作为一组需要维持其指针状态的数组。

结语

希望我有清楚地描述出hooks的心智模型,以描述其背后到底发生了什么。