魯斯前端布魯斯前端

文章中英模式

布魯斯前端React面試題目 - 解釋 React Key 的用途

深入解析React中key屬性的作用、如何正確使用key、常見錯誤以及對性能的影響。了解為何不應使用索引作為key,key如何協助React識別元素變化,以及React協調算法與key的關係。

影片縮圖

懶得看文章?那就來看影片吧

React Key 的作用是什麼?

在React中,key 是一個特殊的props,主要用於幫助React識別列表中的元素。它就像是每個元素的「身分證」,讓React能快速找出哪些元素有變化。

Key的主要作用:

  • 1. 提升DOM更新效率
  • 2. 保持元件狀態不丟失
  • 3. 避免不必要的重新渲染
  • 4. 確保列表元素的唯一性

Key 與 React 渲染列表的關係

當React渲染列表時,它需要知道哪些元素需要更新、新增或刪除。

沒有key:React只能按位置比較

原列表:

位置0: 蘋果

位置1: 香蕉

位置2: 橘子

新增「芒果」到開頭:

位置0: 蘋果→芒果 (更新)

位置1: 香蕉→蘋果 (更新)

位置2: 橘子→香蕉 (更新)

位置3: 新增橘子

有key:React可按身分比較

原列表:

key="a": 蘋果

key="b": 香蕉

key="c": 橘子

新增「芒果」到開頭:

key="m": 新增芒果

key="a": 蘋果 (不變)

key="b": 香蕉 (不變)

key="c": 橘子 (不變)

使用索引作為key的問題

⚠️ 當列表順序會有所改變時,索引key會導致不必要的DOM更新:

// 如果列表會更新,不好的做法
{items.map((item, index) => (
  <Item key={index} data={item} />
))}

// 好的做法
{items.map(item => (
  <Item key={item.id} data={item} />
))}

Key 也可被用於強制重新創建組件

元件的實例、狀態和DOM與其key密切相關。當key改變時,即使元件類型相同,React也會:

  • 1. 完全卸載舊元件實例(觸發useEffect的清理函數)
  • 2. 創建新元件實例(重新觸發useEffect
  • 3. 重置元件的所有內部狀態
  • 4. 重建DOM節點

這種行為可以被有意利用來強制重置元件狀態:

// 使用key重置表單元件
function ResetableForm({ formData, resetKey }) {
  return (
    <Form 
      key={resetKey}  // 當resetKey改變時,整個Form元件會重置
      initialValues={formData}
    >
      {/* 表單內容 */}
    </Form>
  );
}

// 使用案例
function ProfileEditor() {
  const [user, setUser] = useState(null);
  const [resetCounter, setResetCounter] = useState(0);
  
  // 當用戶切換時,我們可以透過改變key來重置表單
  return (
    <div>
      <UserSelector onSelect={setUser} />
      <ResetableForm 
        formData={user} 
        resetKey={user?.id || resetCounter} 
      />
      <button onClick={() => setResetCounter(c => c + 1)}>
        重置表單
      </button>
    </div>
  );
}

Key 的最佳實踐

選擇正確的key

  • 優先使用穩定、唯一的ID: 資料通常有固有的ID,如資料庫主鍵、UUID等
  • 其次考慮內容雜湊: 如果沒有ID,可基於內容生成雜湊值
  • 避免使用隨機值: 每次渲染產生的隨機key會導致所有元件重新渲染
  • 最後才考慮索引: 只在項目穩定不變、沒有重新排序時使用索引

常見錯誤

// ❌ 錯誤:使用索引作為可變列表的key
{items.map((item, index) => (
  <TodoItem key={index} item={item} />
))}

// ❌ 錯誤:使用隨機值或時間戳作為key
{items.map(item => (
  <TodoItem key={Math.random()} item={item} />
))}

// ✅ 正確:使用固有ID
{items.map(item => (
  <TodoItem key={item.id} item={item} />
))}

// ✅ 替代方案:結合索引和穩定識別屬性
{items.map((item, index) => (
  <TodoItem key={`${item.name}-${index}`} item={item} />
))}

🔥 常見面試題目

(一) React中為什麼需要key?不提供key會有什麼後果?

解答: React用key來識別列表中的元素,就像每個人的身分證號碼。有了key,React才能:

  • 1. 知道哪些元素變了、新增了或刪除了
  • 2. 決定能否重用元素而不是重建
  • 3. 正確保留元素狀態

沒有key的後果:

原始列表

項目A (輸入中)
項目B

刪除A後

項目B (輸入丟失)

沒提供key時,React默認用索引,這在列表變動時會導致:

  • 1. 性能下降(過度重建DOM)
  • 2. UI混亂(如錯誤的選中狀態)
  • 3. 狀態錯亂(如輸入框內容跑錯位置)

(二) 為什麼不建議使用索引作為key?什麼情況下可以使用?

解答: 索引只是位置編號,不代表項目本身。當列表變動時,同一索引可能對應不同項目:

// 原列表:索引與項目的對應
[0: 蘋果, 1: 香蕉, 2: 橘子]

// 刪除蘋果後:同樣的索引現在對應不同項目!
[0: 香蕉, 1: 橘子]

這會導致:

  • 1. 元件被不必要地重建
  • 2. 表單輸入值跑位
  • 3. 狀態混亂

只有在這些條件全部滿足時才能用索引:

  • 1. 列表永遠不會重排序
  • 2. 不會添加/刪除項目
  • 3. 項目沒有ID
  • 4. 元件沒有狀態

(三) 如何正確生成key值?有哪些常見策略?

解答: 好的key應該是穩定且唯一的,按優先順序:

  1. 數據ID:最理想的選擇
    <Item key={user.id} />
  2. 內容雜湊:當沒有ID時
    <Item key={hash(item.content)} />
  3. 生成ID:初始化時創建
    // 初始化時添加ID
    const data = items.map(i => ({...i, id: uuid()}))