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に責任負い兼ねます。