0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React の key は便利だけど重複したら地獄

0
Posted at

React の key は便利だけど重複したら地獄

key の便利なところ

key は兄弟要素を識別する ID

key は、同じ親を持つ兄弟要素を React が識別するための ID。

<>
  <A key="a" />
  <B key="b" />
</>

React は "a""b" で、それぞれの要素を区別する。前回の render と今回の render で、どの要素がどれなのかを対応付けるのに使う。

key は props として渡らない

key は React が内部で使うだけ。component から参照できない。

function A(props) {
  console.log(props.key);
}
<A key="abc" />

結果

undefined

公式にそのまま書いてある。

Note that your components won’t receive key as a prop. It’s only used as a hint by React itself. If your component needs an ID, you have to pass it as a separate prop: <Profile key={id} userId={id} />.

Rendering Lists – react.dev

component 側でも値が欲しいなら、別の props として渡す。

<A key={id} id={id} />

key={id} は React 用、id={id} は自分用。

key が変わらなければ作り直されない

まず前提。key が同じままなら、React は「同じ要素」と判断して再利用する。unmount も mount もしない。

<ProgressTimer key={questionId} />

questionId が変わらない限り、ProgressTimer は作り直されない。中の useState<progress> の進行状況もそのまま残る。

そもそも key を付けなくても同じ。key が無いと React は「同じ位置・同じ型なら同じ要素」として対応付ける。

<ProgressTimer />

親が再 render されても、ProgressTimer が同じ位置・同じ型でいる限り作り直されない。state は残る。key を書かないデフォルトの挙動がまさにこれ。

同じ key(または key 無しで同じ位置)= 同じ要素として使い回す。これが基本の挙動。次はこの逆。

key を変えると component が作り直される

key は list 表示だけの道具じゃない。値を変えると component を強制的に作り直せる。

<ProgressTimer key={questionId} />

questionId が変わるたびに React は古い ProgressTimer を unmount して、新しいのを mount する。React から見ると別の component。

公式いわく、これは list 専用の機能じゃない。

Keys aren’t just for lists! You can use keys to make React distinguish between any components.

Preserving and Resetting State – react.dev

Component が作り直されると state は初期化される

re-mount なので内部の状態は全部最初から。

  • useState
  • useRef
  • <progress> の進行状況
  • useEffect の実行状態
function ProgressTimer() {
  const [value, setValue] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setValue((v) => v + 1);
    }, 100);

    return () => clearInterval(id);
  }, []);

  return <progress value={value} max={100} />;
}

親でこう置く。

<ProgressTimer key={questionId} />

questionId が変わるたびに progress は0から、interval も作り直し。「state を完全にリセットしたい」ときに使う手。

cleanup が無いと interval が積み上がる

key を変えるだけでは setInterval / setTimeout は止まらない。unmount のときに useEffect の cleanup が走って初めて止まる。

useEffect(() => {
  const id = setInterval(/* ... */);
  return () => clearInterval(id);
}, []);

これがあると、こう流れる。

key 変更 → unmount → cleanup → clearInterval() → 新しい component が mount

cleanup を書かないと、

useEffect(() => {
  setInterval(/* ... */);
}, []);
key 変更 → 新しい component が mount → 古い interval は残る → 新しい interval も動く
  • progress が異常な速度で進む
  • timer が多重で動く
  • memory leak

key で作り直すなら cleanup は必須。

key は重複しても警告だけ

<>
  <A key={key} />
  <B key={key} />
</>

key が両方同じ値でもエラーにはならない。ただし同じ値だと dev mode で warning。

Warning: Encountered two children with the same key

値が違えば無言。key の重複は error ではなく warning。

🔥 key が重複したら地獄 🔥

warning で済むならいいか → 死

<>
  <TodoItem key="1" text="React" />
  <TodoItem key="1" text="Vue" />
</>

React は両方 "1" と認識する。どっちがどっちか対応付けられない。

map で書くとこう。

{todos.map((todo) => (
  <TodoItem key={todo.name} />
))}

todo.name が重複しうる値だと

  • useState の値が別の要素へ引き継がれる(入力内容・checkbox の状態がズレる)
  • DOM が更新されない・focus が別要素へ飛ぶ
  • 再利用できるはずの要素を作り直す → useEffect が再実行、スクロール位置や入力中の内容が飛ぶ

前回 render との対応付けをミスるから

React は兄弟要素をこう覚えている。

前回
key="1" → Todo A
key="2" → Todo B

次の render でこうなると、

key="1" → Todo B
key="1" → Todo C

どっちが前回の "1" なのか判断できない。ここで要素を誤って再利用する。

このズレ部分は自分でまだ再現実験していない。

Keys tell React which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted.

Rendering Lists – react.dev

まとめ

  • key の重複はエラーではなく warning
  • props.key では取れない。値が欲しいなら別 props で渡す
  • key が変わらなければ作り直されない(state はそのまま)
  • key を変えると component が unmount → mount され、state が初期化される(リセット用途に使える)
  • ただし useEffect の cleanup が無いと interval などが積み上がる
  • 重複すると前回 render との対応付けを React がミスって state がズレる
  • key は同じ親の兄弟要素の中で一意にする

気をつけよ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?