React Hooks - useStateはどうやって動いているのか、中身をざっくり解説する

ようやく hooks を始めた、ちょっと慣れに時間がかかりそうけどね。 今まで component 思考が強すぎで、まだ変えられない。

これはから useState はどう動くのか見てみよう

この記事はすごくいいです。おすすめ https://www.netlify.com/blog/2019/03/11/deep-dive-how-do-react-hooks-really-work/

1. data -> ui

簡単な counter を作ろう。これは Component とします。

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

シミュレーションなので、count を返す。まあ、実際 React が色々やっているが、この場では気にしない。 OK、ここの Counter は確実に 0 を render する、毎回毎回 0 を出力する。じゃどうやって動的にする?

2. closure

無論、Counter の中にlet countをするとダメですね。実行されるたびに 0 と初期化されて、gc される。

Counter の外へ出さないといけない。以下のように?

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

これもダメですね。複数の Counter があると、唯一の count になるので。

欲しいのは、違う Counter ではそれぞれの変更可の count を持つこと。

なので、count は必ず Counter の外、しかも唯一ではない。なんか Cache みたいな?function を作りましょう。

この function で満たす条件は

「同じとこから呼ばれたら、前回の値を返す。初回だったら初期値を返す」

こんな感じ

const giveMeACount = () => {
  // 呼ばれたことあるかどうか?
};

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

3. でも、呼ばれたかどうかはどう判断する?

これは無理ですね。Counter はただの計算をやっているので、this がない。 なので、giveMeACount がわかるのは、「誰が読んだのかではなくて、何回め呼ばれただけ」。

UI に対しては、ただの計算になるので、どこに giveMeACount が呼ばれるのは stable のはず(大体はね、実際は絶対色々やっているが、一旦これで行きましょう、時間あったらまた調べます。)

OK,コードを変えよう。呼ばれる順番に基づいて cache する。

const Cache = {
  data: [],
  currentIndex: 0,
  giveMeACount(initValue) {
    const index = this.currentIndex;
    // dataないなら、引数の初期値を挿入
    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をreset
  resetIndex() {
    this.currentIndex = 0;
  }
};

Counter も更新する

const Counter = () => {
  const [count, setCount] = Cache.giveMeACount(0);
  return {
    // click をmockする
    click() {
      setCount(count + 1);
    },
    render() {
      return count;
    }
  };
};

これで、複数の Counter の render をシミュレートしましょう

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

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

counter1.click(); // clickを発火

Cache.resetIndex(); // render終わったら、rerenderを準備するため indexをreset

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

これで、呼ぶ順番に動的な値が cache できるようになりましたね。

4. useStateに変名

見るとわかるが、Cache.giveMeACountは共通の util として使え回せる

名前を変えよう

const React = {
  data: [],
  currentIndex: 0,
  useState(initValue) {
    const index = this.currentIndex;
    // dataないなら、引数の初期値を挿入
    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をreset
  resetIndex() {
    this.currentIndex = 0;
  }
};

const Counter = () => {
  const [count, setCount] = React.useState(0);
  return {
    // click をmockする
    click() {
      setCount(count + 1);
    },
    render() {
      return count;
    }
  };
};

oh yeah、useState の誕生!

5. まだまだ全然足りないけど

これで useState の仕組みはなんとか理解できましたかもね。React ではもっと複雑のロジックをやっているともうけど、例えば resetIndex のタイミング、setter で rerender の trigger, reconsile する時の index と data の処理など。

まあ、前よりuseStateへの理解が深まったかなと思います。いかがでしょうか。

以上では各投稿者の観点であり、zanp.lifeに責任負い兼ねます。

#js#typescript#react#hooks

jser  2019-9-20