iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 17
1
Modern Web

I Want To Know React系列 第 17

I Want To Know React - 初探 Key

在上一篇中,我們介紹了 React render list 的語法,其中提及了 render React element list 時都必須要加上 key prop。在這章節中我們將簡介 key 與其使用須知。

Key

Key 可以想成是 React element list 中每個元素的 id,通常會把資料原有的 id 當作是 key 的值。

當用 React render React element list 時,加入正確的 key 可以幫助 React 識別 list 中的 element 並避免不必要的 re-render 以增進效能。

簡介 key 功能

再稍微仔細一點說明 key 的運作原理,key 可以幫助 React 識別並追蹤 list 中各個 React element 的狀況。

當 list 中的每個 React element 都有屬於自己的固定 key(id)時,React 就可以在 list 內容 / 順序變動時,識別出原本就存在 list 中的 React element 的所在位置。

這樣 React 就不需要 re-render 原本就已存在的 React element,只要把原有的 React element 移動到新的位置即可,如此就能減少不必要的 re-render。

簡單來說,key 是 React 提高 render list 效能的一個手段。Key 更詳細的運作原理將在下一章節介紹。

語法

Key 的語法十分簡單,只要在任一 React element 中加入 key prop,並帶入對應的值即可。key 值的型別必須為 string 或 number:

<someTag key={itemId}>...</someTag>

如功能簡介所說,key 是 React render list 的優化手段,因此基本上都是在 render list 時使用, 只要在 list 中最外層 element 加上 key 即可:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

範例中,我們在把 numbers map 成 React element 時,將 key 加入每個 <li> element 的 props 中,而 key 的值則是使用 numbers 中的每個元素(1 2 3 4 5)。

需要注意的是,因為 key 是拿來識別 list 中的每一個元素,因此 key 必須是 list 中的唯一值,且需要一直都對應到同一個 element。

另外,key 是任何 element 或客制 component 內建都有的 prop。任何 React element 帶入 key 都是合法的語法。因此不需要擔心有原生 React element 不支援 key prop,或者自己寫 component 時還要宣告 key prop 等問題。

正確使用 Key

撰寫 key 時有一些常見的誤區需要避免,但只要能理解 Key 是 "以定義 list 中元素 id 的方式,來提高 render list 效能" 這個概念,就可以正確的使用 key。

就讓我們來看看有哪些注意事項吧!

Render React element list 時都應該指定 key

從上面的段落可以知道,加入 key 可以有效地提升效能。React 官方因為希望效能能夠被最佳化,因此建議 render React element 組成的 list 時,都應該要使用 key。

如果 render React element 不使用 key 的話 React 會報警告:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((numbers) =>
  <li>{numbers}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);
// Warning: Each child in a list should have a unique 'key' prop.

讀者可以在這 CodePen 的 console 中看到此警告。

如果我們加上去之後就可以消除錯誤:

const listItems = numbers.map((numbers) =>
  <li key={number}>{numbers}</li>
);
// won't have warning

同樣的,讀者可以看到修正後的 CodePen console 就不會再出現此問題了。

key 應定義在 list 中的每個最上層 React element 上

Key 是用來讓 React 區分 list 中最上層元素的手段。因此 list 中最上層 React element 一定要定義 key

換句話說,如果 key 不是放在最上層 React element 上,則 React 就無法識別 list 中的每個元素。

錯誤示範:

function ListItem(props) {
  const value = props.value;
  return (
    // Wrong! There is no need to specify the key here:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Wrong! The key should have been specified here:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

會被 React 發出沒有 key 的警告,這是因為 <li> 並非是 list 的最上層 React element。

如果把 list 中的 React element 展開就能更清楚知道原因了:

[
  <ListItem value={1}>
    <li key="1">1</li>
  </ListItem>,
  <ListItem value={2}>
    <li key="2">2</li>
  </ListItem>,
  <ListItem value={3}>
    <li key="3">3</li>
  </ListItem>,
]

可以看到 key 並非在最上層的 <ListItem> 上,而是在 <li> 中。這樣 React 是無法透過 key 來識別 list 中的最上層 element 了。

正確的作法是將 key 放在 list 的最上層 element <ListItem> 上,不需要因為 <ListItem> 是自定義的 component 就不將 key 放上去。

正確示範:

function ListItem(props) {
  // Correct! There is no need to specify the key here:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Correct! Key should be specified inside the array.
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

此時,如果把 list 中的 React element 展開可以看到以下結果:

[
  <ListItem key="1" value={1}>
    <li>1</li>
  </ListItem>,
  <ListItem key="2" value={2}>
    <li>2</li>
  </ListItem>,
  <ListItem key="3" value={3}>
    <li>3</li>
  </ListItem>,
]

可以看到這次 key 有正確定義在最上層 element <ListItem> 上,如此 React 就可以透過 key 來識別 list 中的各個元素。

key 需要是 list 中每個 React element 的唯一值

Key 是用來讓 React 識別 list 中每個元素的手段,因此 list 中每個 React element 的 key 都需要是唯一值。

換句話說,如果 React element 的 key 不是 list 中的唯一值,則 React 就無法正確識別每個元素。如果有非唯一值得狀況出現,React 就會發出警告。

錯誤示範:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) => (
    <ListItem key="1" id={number.toString()} value={number} />
  ));
  return <ul>{listItems}</ul>;
}
// Warning: Encountered two children with the same key, `1`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

此時如果我們把範例中的 key 修正成 list 中的唯一值即可。

正確示範:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) => (
    <ListItem key={number.toString()} id={number.toString()} value={number} />
  ));
  return <ul>{listItems}</ul>;
}

key 不需要是整個 React app 中的唯一值

同樣的道理,因為 key 是用來最佳化 list 中 React element 的手段,因此 key 只要在 list 中是唯一值即可。

就用以下的範例來證明吧:

function NumberList(props) {
  const numbers = props.numbers;
  const listItemsOne = numbers.map((number) => (
    <ListItem key={number.toString()} value={number} />
  ));
  const listItemsTwo = numbers.map((number) => (
    <ListItem key={number.toString()} value={number} />
  ));
  return (
    <ul>
      {listItemsOne}
      {listItemsTwo}
    </ul>
  );
}

讀者也可以到 CodePen 查看結果。

可以看到一切都可正常顯示,React 也不會報任何的警告。

key 不會出現在 Component 的 props

最後,key 這個 prop 只是拿來給 React 內部的值而已,並非拿來給 component 內使用,因此 key 並不會出現在 component 的 props 中。

如果在 component 內取 key 值的話,拿到的內容會是 undefined,且 React 會發出警告。

錯誤示範:

function ListItem(props) {
	console.log('props.key', props.key);  // undefined
  return <li>{props.value}</li>;
}
// Warning: ListItem: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://fb.me/react-special-props)

如果要從 props 中取得資料 id,需要加上另外自定義的 props 即可。

正確示範:

function ListItem(props) {
	console.log('props.id', props.id);  // undefined
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) => (
    <ListItem key={number.toString()} id={number.toString()} value={number} />
  ));
  return <ul>{listItems}</ul>;
}

從範例中可以看到,自定義 props.id 可以拿到資料 id,且 React 也不會報警告。

小結

此章節介紹了 key 為 React element list 中每個元素的 id。可用來最佳化 list 中的效能。

此外,使用時還有以下幾點注意事項:

  • Render React element list 時都應該指定 key
  • key 應該定義在 list 中的每個最上層 React element 上
  • key 需要是 list 中每個 React element 的唯一值
  • key 不需要是整個 React app 中的唯一值
  • key 不會出現在 component 的 props 中

下章節中,我們將介紹 key 的內部原理。

參考資料


上一篇
I Want To Know React - Render list
下一篇
I Want To Know React - Key & Diff 演算法
系列文
I Want To Know React30

尚未有邦友留言

立即登入留言