all articles

understand useState better

2019-09-19 @sunderls

js react





最近项目终于开始逐渐转向 hooks 了。开始还是有点不适应,主要是思维方式的转变需要时间。 以前的 componenet 概念太强了,转变到 hooks 的时候,需要想的是主要的逻辑是 UI 的表达,所有其他的 API 请求,事件处理都是只是计算和副作用而已。

现在来好好看看具体各个 hooks 是干什么的。

主要是看到这篇文章 : https://www.netlify.com/blog/2019/03/11/deep-dive-how-do-react-hooks-really-work/ 写的非常好!

1. 基础的 data -> ui

拿一个简单的例子,比如 counter,首先最基本的逻辑就是,输出一个 counter 好了。

首先模拟一个 Component

const Counter = () => {
  let count = 0;
  return count;
};

这里Counter()求简单,直接返回 count,实际上在 React 环境下需要做的是返回 element,这里不考虑。

OK,这里 Counter 是确定的,无论如何 count 都是 0,怎么让 count 动起来呢?

2. closure,闭包

很显然,想要让 count 动起来,放在 Counter 里面是不可以的,因为每次 Counter 被执行,count 都会被新建,而且完了 count 就会被 gc 掉。

只能把 count 放在 Counter 之外

let count = 0;
const Counter = () => {
  return count;
};

但是这样所有的 Counter 都指向了同一个 count,这是不 OK 的,我们需要不同的 Counter 有不同的可变的 count。

所以 count 需要在 Counter 之外,但是不能是唯一,需要用一个方法去生成。 这个方法需要如下要求:

「如果是同一个地方调用,就返回上一次的值;否则就创建一个新的值」

const giveMeACount = () => {
  // 记住我,我来过的话给我上次的值;不然给我个新的。
};

const Counter = () => {
  let count = giveMeACount();
  return count;
};

3. 如何记住调用?

几乎不可能,在 Counter 这个纯函数里面,没有 this,不知道你是谁。Counter 只是负责收到数据,然后计算得出结果。 那么,方法的个体不能用于 target,能够知道的就只有方法的「调用顺序」了。

对于这个 UI 而言,由于用了 function,所以每一次的计算的时候,调用方法是稳定的。(具体这里面的细节还没有具体研究,不过大概就是这意思吧,以后再研究)

OK,我们修改一下代码,调用顺序需要存储,所以用一个 object 来代替。

const Cache = {
  data: [],
  currentIndex: 0,
  giveMeACount(initValue) {
    const index = this.currentIndex;
    // 如果没有数据,插入一个
    if (this.data.length <= index) {
      this.data.push(initValue);
    }

    this.currentIndex = index + 1;

    //返回值和一个setter
    return [
      this.data[index],
      newValue => {
        this.data.splice(index, 1, newValue);
      }
    ];
  },
  // 重置index
  resetIndex() {
    this.currentIndex = 0;
  }
};

更新一下 Counter

const Counter = () => {
  const [count, setCount] = Cache.giveMeACount(0);
  return {
    // 模拟一个click
    click() {
      setCount(count + 1);
    },
    render() {
      return count;
    }
  };
};

模拟一下多个 component 的 render

let counter1 = Counter();
let counter2 = Counter();

counter1.render(); // 0
counter2.render(); // 0

counter1.click(); // 模拟第一个click

Cache.resetIndex(); // 模拟render结束,重置调用顺序

Counter().render(); // 1
Counter().render(); // 0

ok,这样我们就完成了根据调用顺序来 cache 数据的目的。

4. 整理下代码, useState的诞生

我们发现Cache.giveMeACount其实是一个公用的方法,用来存取任何值。

换一个名字

const React = {
  data: [],
  currentIndex: 0,
  useState(initValue) {
    const index = this.currentIndex;
    // 如果没有数据,插入一个
    if (this.data.length <= index) {
      this.data.push(initValue);
    }

    this.currentIndex = index + 1;

    //返回值和一个setter
    return [
      this.data[index],
      newValue => {
        this.data.splice(index, 1, newValue);
      }
    ];
  },
  // 重置index
  resetIndex() {
    this.currentIndex = 0;
  }
};

const Counter = () => {
  const [count, setCount] = React.useState(0);
  return {
    // 模拟一个click
    click() {
      setCount(count + 1);
    },
    render() {
      return count;
    }
  };
};

oh yeah, useState 就这样诞生了

5. 不过远远不是这样

这只是帮助理解了 useState 到底是什么。实际上 React 做的远比这些复杂,比如内部 currentIndex 的 resetIndex 时机,set 的时候触发 rerender, reconsile 的时候如何处理。

不过,至少我们知道了大概的原理吧? 感觉现在用useState更加自信了。



如果觉得有帮助到你的话,
欢迎支付宝donate