iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 13
1
Modern Web

激戰 ReactJS 30天系列 第 13

【Day13】 搭火車排排隊 - Lists and Keys

祝大家 2018新年快樂 =D
雖然是值得慶祝的日子
但挑戰還是依然要繼續
新的一年希望能夠在最開始就達成持續30天的小成就
那麼廢話不多說
下面正文開始啦~~~

當資料變多的時候
我們習慣將他們整理好再拿來使用
資料庫的存在就是最好的證明
當我們取出排列整齊的資料時
React 也可以讓我們使用 **清單(Lists)**來擺放他們
今天就要來學習在 React 中如何使用清單啦

List

清單(Lists)
讓資料一列一列擺放整齊的網頁元素
在之前的範例中
曾經使用過 javascript 的map函式
map是將陣列內容劃分逐一取出處理並放入一新陣列的函式
在 React 的世界中
我們可以透過下面的方式去產生一個清單:

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
   render() {
      const numbers = [1, 2, 3, 4, 5];
      const listItems = numbers.map((number) =>
         <li>
            {number}
         </li>
      );
      return (
         <ul>{listItems}</ul>
      );
   }
}
export default App;

這段程式碼是透過變數存放陣列型態的清單物件
最後render變數物件
另一個方式是JSX的單行(inline)寫法:

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
   render() {
      const numbers = [1, 2, 3, 4, 5];
      return (
         <ul>{
            numbers.map((number) => <li> {number} </li>)
         }</ul>
      );
   }
}
export default App;

這兩個寫法的結果相同
差別只有再放入資料的方式
執行結果:

這兩段程式碼的執行結果都有一個警告(Warning)
Each child in an array or iterator should have a unique "key" prop.
出現的不是錯誤
而且程式碼可以正常執行
那為什麼會出現這條警告呢?
下面先來說說這個被特別標註的key吧!

Keys

鍵值(Keys)
通常目的是用來識別不同的元件
在這裡也是一樣的目的
React 清單的鍵值是一個 獨特的字串
獨特的程度並不是全域(global)
所以不同的區塊就可以重複使用這些鍵值

那為什麼鍵值沒有被給予會出現警告呢?

鍵值的存在與否不影響程式執行
但是在效能上會出現差異
要解釋這一點
我們需要先更進一步認識一下
React 濕背秀的 diff演算法

Diff Algorithm

在最開始的時候有提到過
React 會先運行 diff演算法
然後才會渲染元素更新畫面

那有沒有 diff演算法 的差別在哪?

當網頁內容有所變動
傳統 DOM 頁面刷新方式就是整個架構捨棄
再重新建構一個更新版本的架構樹
網頁小元素少的時候可能感覺不出差異
但當東西一多
每次都要重新繪製並組織數以千計的網頁元素
這時候就能夠看出 一個人有沒有耐心 有多大的差異了
DOM 之所以被說世界慢宇宙慢就是因為這樣

diff演算法在網頁刷新的時候
透過render先繪製一套新長相的架構樹
接著從根(root)開始比較差異
他可以透過比較節點找出有差異的部分
比較的項目除了元素標籤本身
標籤內所具備的屬性也能被比較
舉例來說:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

這段程式碼能夠被 diff演算法 識別出
節點從div變成了span
而 Counter 組件則沒有差異

<div className="before" title="stuff" />

<div className="after" title="stuff" />

在這裡 diff演算法 能夠識別出
只有className這個屬性發生變化
並且只針對這個屬性去做刷新

<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />

儘管細小到屬性內的屬性值
diff演算法都還是能夠找出差異
並且單就屬性值不一樣的地方做更新

透過 diff演算法找出差異
之後再就這些節點透過真正的 DOM 進行刷新
效能就可以不再因為網頁大小而產生差異
達到真正提升效能的目的

知道了diff演算法後
感覺疑惑還是沒有得到答案阿
這個和那個鍵值到底有啥毛關係?
好的
現在馬上回到正題

Using Keys

先看一下下面兩段程式碼:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

剛剛提過
diff演算法比較差異是從根的部分開始
如果是這從這段程式碼的上段變成下段
那麼比較之後會被解讀成

<ul>
  <li>Connecticut</li>  // Duke → Connecticut
  <li>Duke</li>         // Villanova → Duke
  <li>Villanova</li>    // 新增 Villanova
</ul>

但是應該要被解釋成

<ul>
  <li>Connecticut</li>  // 新增 Connecticut
  <li>Duke</li>
  <li>Villanova</li>
</ul>

之所以出現這個問題
就是因為他是從根往下一一比對
如果沒有個機制讓 React 能夠辨別 相同的東西移位
那就會造成多餘的效能被浪費
所以囉
鍵值(Keys)就是為了做這件事而誕生
清單被變更的時候
React 會透過鍵值去比對是不是同一個元素
鍵值是可以被使用者自訂的
React 也建議使用者自訂能夠被識別的特殊字串來當作鍵值
因為鍵值不影響元素本身
單純是用來提升網頁效能
所以沒有設定並不會造成錯誤
也不會影響程式的運行
鍵值可以這樣加入到清單元素中:

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
   render() {
      const numbers = [1, 2, 3, 4, 5];
      return (
         <ul>{
            numbers.map((number) => 
               <li key = {number.toString()}> {number} </li>)
         }</ul>
      );
   }
}
export default App;

上面的程式碼是將陣列資料直接轉換成字串當成鍵值
key = {number.toString()}就是設定鍵值的方式
大括號內放字串型態的資料
在這邊有一個需要注意的小地方
鍵值是在陣列中判斷是不是同一個物件
所以如果有把清單的資料列提取獨立成組件
那鍵值的設定要跟著整個資料陣列
而不是提取出去的組件內
讓我用下面的範例來說明:

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
   render() {
      const numbers = [1, 2, 3, 4, 5];
      const listItem = numbers.map((number)=>
         <ListNumber key = { number.toString() } value = { number } />
         );
      return (
         <ul>{listItem}</ul>
      );
   }
}

class ListNumber extends React.Component{
   constructor(){
      super();
   }
   render(){
      return (
         <li>{this.props.value}</li>
      );      
   }
}
export default App;

ListNumber 是資料列提取出來的組件
主要只用來接收並產生出元素
鍵值的設定依然要放在資料列陣列的位置
也就是 App 組件內的 listItem 變數
執行結果:

那個有點討厭的 Warning 也就不見啦
以上就是 React 的清單和鍵值

參考資料

  1. tutorialspoint-ReactJS Tutorial
  2. React 官方文件

>>> 隊友任意門 <<<


Day13 end
by 瑞Ray ⎝( OωO)⎠


上一篇
【Day12】 Rendering
下一篇
【Day14】 我問你要答R - Forms
系列文
激戰 ReactJS 30天31

1 則留言

0
jackithome
iT邦新手 5 級 ‧ 2019-05-07 17:23:39

親愛的大大您好,我想請問一下關於 Using Keys 資料比對的部分,
請問一下React.js的比對機制是同樣的key,比較內容不同,還是說是比較有沒有這個key內容?

比如說原本→ <li key = "1"> 原key </li>
後來改成→ <li key = "1"> 偷偷修改key </li>
那他是會渲染改過的,還是說因為是相同key,就認為是相同物件,不做渲染?
如果是後者,我應該改成→<li key = "2"> 偷偷修改key02 </li>

我要留言

立即登入留言