Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active November 4, 2024 09:22
Show Gist options
  • Save mizchi/fa00890df2c8d1f27b9ca94b5cb8dd1d to your computer and use it in GitHub Desktop.
Save mizchi/fa00890df2c8d1f27b9ca94b5cb8dd1d to your computer and use it in GitHub Desktop.
(翻訳) React Hooks は魔法ではなく、ただの配列だ

(翻訳) React Hooks は魔法ではなく、ただの配列だ

この記事は https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e の日本語訳です。途中で力尽きて雑な翻訳になってる箇所がいくつかあります。


どのように Hooks が動いているか

私は、新しい Hooks の API の "magic" な挙動に悩んでいる人が結構いると聞いています。なのでこの構文の提案が、少なくとも表面的なレベルではどう動いているか、解説してみようと思います。

Hooks のルール

Hooks のドキュメントには React コアチームの規定する、あなたが知るべき2つのルールがあります。

  • Hooksをループの中、条件文の中、ネストした関数の中で呼んではいけない
  • React Function からのみ Hooks を呼んでもいい

後者は明白でしょう。関数コンポーネントに振る舞いを付与するのに、あなたはその振る舞いとコンポーネントを紐付けることができる必要があります。

前者は、不自然に思えて、紛らわしいかもしれません。これについて説明しようと思います。

Hooks の状態管理はほとんど全て配列

考え方を整理するために、簡単なHooksの実装を見てましょう。

これは、実装を考えるための、APIを実装する方法の一つでしかないことに注意してください。

どのようにAPIが内側で動いているか理解する必要はありません。これもまた一つの実装例です。将来的にこの動き方は変更される可能性があります。

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) 初期化

2つの空の setters と state を生成します。

カーソルを 0 に設定します。

2) 初回の render

コンポーネント関数を初めて実行します。

どの useState() の呼び出しも、最初は、(カーソル位置に紐付けられた) setter 関数を setters の配列にプッシュし、state を state の配列にプッシュします。

3): 続いて起こる render

どの二回目の render もカーソルはリセットされ、これらの値は単に配列から読まれます。

4): イベントハンドリング

どの setter もそのカーソル位置への参照をもっていて、setterを実行することで、そのカーソル位置の state を書き換えます。

これがこの実装を実演するコードです。

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']

なぜ順序が大事なのか

もし今、外部の要素の順番に基づいたこの hook の順番を変更すると、何が起こるでしょうか?

React team がやるべきではない、と言っていることをやってみましょう。

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>
  );
}

駄目な初回レンダリング

この時点では firstName と lastName には正しいデータが含まれていますが、二回目のレンダーを見てみましょう。

駄目な二回目のレンダリング

今、firstName と lastName の両方が Rudi にセットされて一貫性がなくなっています。これはわかりやすくエラーで動いていないわけですが、これはなぜ hooks のルールがこのやり方でダメになるかの考え方を教えてくれます。

これらの規則を遵守しなければ、データの一貫性が失われます

ルールを壊さない配列の hook を考えよう

これらのことから、条件文やループの中で use hook を呼び出せないのはなぜか分かります。レンダー内の呼び出しの順序を変更すると、配列のカーソル位置を扱うのでカーソルが一致せず、正しいデータまたはハンドラーを指さなくなります。

このトリックは、一貫したカーソルが必要な hook の管理を考えるためのものです。これが意識できていれば、全てがうまくいくでしょう。

結論

幸いなことに hook API の裏で何が起こっているかの考え方を整理することができたと思います。

ここで覚えておいてほしいことは、順序に気をつけることで、Hooks API は良い結果をもたらすということです。

Hooks は React Components への効果的なプラグインです。これは Hooks API に人々が熱狂している理由で、あなたはどこに配列のセットが存在しているか考えていれば、これらのルールを破らずに済むことが出来ます。

私は近い将来、useEffects メソッドを見てみて、React Component の Lifecycle と比較してみたいと思っています。

You can follow Rudi Yardley on Twitter as @rudiyardley or on Github as @ryardley

@okunokentaro
Copy link

hooray! 素晴らしい早さですね。

- どの二回目の render もカーソルはリセットされ、
+ その後のどの render でもカーソルはリセットされ、

全部原文と比較しましたけど、一箇所だけ訳がしっくりこなかったので Each subsequent renderについて考えてみました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment