<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React/Vue里的key到底有什么用?看完這篇你就知道了!(附demo代碼)

          共 4984字,需瀏覽 10分鐘

           ·

          2021-04-25 10:09

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

            作者 |  大唐西域都護(hù)

          來源 |  urlify.cn/3eqY3a

          76套java從入門到精通實(shí)戰(zhàn)課程分享

          網(wǎng)上有很多博客講到,React、Vue里的key,與 Virtual DOM 及 DOM diff 有關(guān), 可以用來唯一標(biāo)識DOM節(jié)點(diǎn),提高diff效率,云云。

          這大致是對的,但是,大多講得語焉不詳,像是在背答案。

          具體怎么個(gè)提效法?為什么說用數(shù)組下標(biāo)當(dāng)作key是“反模式”?講了一堆,能不能來個(gè)眼見為實(shí),show me the code?

          本文以React為例,嘗試稍微刨一刨,但又不刨到太底層,以足夠幫助理解為度。

          1. VNode diff

          首先介紹 Virtual DOM 結(jié)點(diǎn)(后續(xù)簡稱Virtual Node, VNode)是如何創(chuàng)建出來的。

          現(xiàn)實(shí)中的React項(xiàng)目幾乎都會用到JSX,而JSX不能直接執(zhí)行,需要先經(jīng)babel編譯成js代碼,比如:

          <div className="content">Hello world!</div>

          會被編譯成

          React.createElement("div", {
              className: "content"
          }, "Hello world!");

          (點(diǎn)擊這里查看在線編譯)

          所以,只要調(diào)用 React.createElement 這個(gè)靜態(tài)方法,就可以創(chuàng)建出一個(gè)VNode。

          無需深入VNode 的具體數(shù)據(jù)結(jié)構(gòu),只要看看這個(gè)工廠方法的參數(shù),就可以知道 DOM diff 到底 diff 了哪些內(nèi)容。

          根據(jù)React官方文檔,該方法可以接收≥3個(gè)參數(shù):

          • 第一個(gè)參數(shù)是type,指定結(jié)點(diǎn)類型,如果是HTML原生結(jié)點(diǎn),那么會是一個(gè)字符串,比如"div";如果是React組件,那么就會是一個(gè)class或function;

          • 第二個(gè)參數(shù)是props,是一個(gè)對象或者null。比如前面的例子中,div標(biāo)簽上的"className"屬性就被加到這里來了;

          • 第三(及第四,第五,……)個(gè)參數(shù)是childNode,該結(jié)點(diǎn)的子節(jié)點(diǎn)。前面的例子中,div的子節(jié)點(diǎn)是一個(gè)內(nèi)容為"Hello world!"的TextNode

          是滴,DOM diff 具體diff 的東西,就是這幾個(gè)參數(shù)。為什么不會有別的?因?yàn)槟菢硬环蟁eact的設(shè)計(jì)理念:Data => UI 單向映射。

          2. 動(dòng)態(tài)列表的diff困局

          我們知道React在調(diào)用setState觸發(fā)render時(shí),會對新舊 Virtual DOM 做比較,力爭以最小的代價(jià)完成新DOM渲染任務(wù)。

          結(jié)合上面提到的幾個(gè)參數(shù),具體比較過程大致是這樣的:

          • 首先比較type。如果type不同,那沒什么好說的,直接銷毀重新create一個(gè);如果type相同,再往后看:

          • 其次比較props,如果有變化,那就把變化的部分update;如果沒變化,那就再往后看:

          • 最后比較子節(jié)點(diǎn),同樣地,有變化就update,沒變化就啥都不做

          這在DOM結(jié)構(gòu)固定的一般情況下是很好用的,但當(dāng)我們希望從一個(gè)list映射出列表、而且這個(gè)list里的項(xiàng)隨時(shí)可能變化時(shí),就有點(diǎn)麻煩了。

          比如說,原本list是這樣的:

          [
            {name: 'Smith', job: 'Engineer'},
            {name: 'Alice', job: 'HR'},
            {name: 'Jenny', job: 'Designer'}
          ]

          然后,Jenny被移到了最前面,那么Smith和Alice就相應(yīng)后移了,變成了

          [
            {name: 'Jenny', job: 'Designer'},
            {name: 'Smith', job: 'Engineer'},
            {name: 'Alice', job: 'HR'}
          ]

          對于React來說,如果它不知道這三個(gè)結(jié)點(diǎn)“本來”是誰,只是按照位置對應(yīng)關(guān)系逐個(gè)去檢查,會發(fā)現(xiàn)每個(gè)結(jié)點(diǎn)都變了:

          • Smith => Jenny

          • Alice => Smith

          • Jenny => Alice

          于是React得出結(jié)論:列表中的所有結(jié)點(diǎn),全都需要update,重新渲染!

          且慢!有沒有更好的方法?

          3. 借助key破局

          如果,React“知道”這三個(gè)結(jié)點(diǎn)“本來”是誰,那么事情就會簡單很多:

          不需要更新任何DOM結(jié)點(diǎn),只需把Jenny對應(yīng)的結(jié)點(diǎn)摘下來,再插入到新的位置,完事。

          但React怎么會知道誰是誰呢?

          這需要我們開發(fā)者手動(dòng)告訴它,于是key出場了。

          在做DOM diff 時(shí),如果同一個(gè)父組件下的兩個(gè)VNode擁有同樣的key,就會被視為同一個(gè)結(jié)點(diǎn),如果React據(jù)此判斷出,這個(gè)結(jié)點(diǎn)在列表中的排位發(fā)生了變化,就會像上面說的那樣,進(jìn)行“摘下-插入”處理。

          為了證明這一點(diǎn),亮代碼!

          首先上一個(gè)故意整出bug的版本:

          class App extends React.Component {
            state = {
              list: [0, 1, 2]
            }

            add() {
              const list = this.state.list;
              this.setState({ list: [list.length, ...list] });
            }

            render() {
              return (
                <div className="App">
                  <button onClick={() => this.add()}>Input sth below, then click me</button>
                  <ul>
                    {            // 注意:這里故意用index作為key,引發(fā)bug
                      this.state.list.map((item, index) => (
                          <li key={index}>
                            <span>Item-{item}</span>
                            <input type="text" />
                          </li>
                        )
                      )
                    }
                  </ul>      
                </div>
              );
            }
          }ReactDOM.render(  <App />,  document.getElementById('root'));

          可以用 create-react-app起個(gè)項(xiàng)目,在本地試試這段代碼。演示效果如下,先在第二行文本框里輸入一些1:

          然后,點(diǎn)擊上面的按鈕,會發(fā)現(xiàn)……

          輸入了一串1的文本框沒有跟著Item-1走,而是留在了“原位”!

          這就是用數(shù)組下標(biāo)作key引發(fā)的典型bug。原因就在于新列表里Item-0和原列表里的Item-1擁有同樣的key,被React視為同一個(gè)結(jié)點(diǎn),所以只是“就地”更新了子節(jié)點(diǎn)(文本),并沒有挪動(dòng)結(jié)點(diǎn)的位置。

          而這個(gè)bug的巧妙之處就在于使用了<input>,它可以在VNode的type、props、children均無變化的前提下,被用戶行為改變其樣式(輸入的內(nèi)容),從而讓我們直觀地看到結(jié)點(diǎn)所處位置。感謝React官方提供了這個(gè)巧妙的case。

          好,下面我們來修復(fù)這個(gè)bug。

          修復(fù)方法很簡單:把 key={index} 改成 key={item} 就行了。

          保存,刷新重試,我們就可以得到:

          這下,對應(yīng)關(guān)系正確了,React正確地識別出了3個(gè)舊結(jié)點(diǎn),直接把新結(jié)點(diǎn)插入到列表開頭,而舊結(jié)點(diǎn)沒有變化。

           

          看到這里,你應(yīng)該明白key到底有什么用,以及為什么index不宜做key了吧。

          另外,如果沒有指定key,那么React會默認(rèn)使用index作為key,所以,只要是動(dòng)態(tài)列表,為了性能著想,請盡量用unique id作為key。






          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 

          瀏覽 70
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品乱伦视频 | 黄色AAA毛片 | 免费一级黄色a片 | 奇米7777狠狠狠狠视频 | 色婷婷播放 |