修復 React 代碼中煩人的 Warning

2021-03-02 前端大全

(給前端大全加星標,提升前端技能)

作者:code秘密花園 公號 / ConardLi

缺少 Keyimg

react官方文檔是這樣描述key的:

Keys可以在DOM中的某些元素被增加或刪除的時候幫助React識別哪些元素發生了變化。因此你應當給數組中的每一個元素賦予一個確定的標識。

react的diff算法是把key當成唯一id然後比對組件的value來確定是否需要更新的,所以如果沒有key,react將不會知道該如何更新組件。你不傳 key 也能用是因為 react 檢測到子組件沒有 key 後,會默認將數組的索引作為 key。react根據key來決定是銷毀重新創建組件還是更新組件,原則是:

key相同,組件有所變化,react會只更新組件對應變化的屬性。key不同,組件會銷毀之前的組件,將整個組件重新渲染。重複 Keywarningimg

從上面提到的 key 的作用可以知道,如果出現兩個相同的 key,則渲染可能出現異常。

錯誤案例:img

常見的錯誤是,在使用 antd 的 table 組件時,每個列的 dataIndex 屬性同時也會作為 key,注意兩個列的 dataIndex 不要相同。

P 標籤包含內聯元素warningimg

在 HTML5 中,標準制定者重新定義了HTML元素的分類,並根據這一新的分類定義了元素的內容模型(Content Model) -- 對於一個元素而言,哪些子元素是合法的,而哪些子元素是非法的。需要注意的是,HTML5中的這種元素分類與inline、block沒有任何關係,任何元素都可以在CSS中被定義為display:inline或者display:block。另外,除了這7大分類,還存在一些較小的分類,如Palpable、Script-Supporting等。

img

Metadata

顧名思義,Metadata元素意指那些定義文檔元數據信息的元素 — 其作用包括:影響文檔中其它節點的展現與行為、定義文檔與其它外部資源之間的關係等。以下元素屬於Metadata:base, link, meta, noscript, script, style, template, title。

Flow

所有可以放在body標籤內,構成文檔內容的元素均屬於Flow元素。因此,除了base, link, meta, style, title等只能放在head標籤內的元素外,剩下的所有元素均屬於Flow元素。

Sectioning

Sectioning意指定義頁面結構的元素,具體包含以下四個:article, aside, nav, section。

Heading

所有標題元素屬於Heading,也即以下6個元素:h1, h2, h3, h4, h5, h6。

Phrasing

所有可以放在p標籤內,構成段落內容的元素均屬於Phrasing元素。因此,所有Phrasing元素均屬於Flow元素。在HTML5標準文檔中,關於Phrasing元素的原始定義為:

Phrasing content is the text of the document, as well as elements that mark up that text at the intra-paragraph level. Runs of phrasing content form paragraphs.

對於這一定義,個人認為不應當使用「text」這一容易引起誤解的詞,事實上,一個元素即使不是文本,只要能包含在p標籤中成為段落內容的一部分,就可以稱之為Phrasing元素。比如:audio、video、img、select、input等元素(經測試,這些元素都可以放置在p標籤中)。一個不太精確的類比是:HTML5中的Phrasing元素大致就是HTML4中所定義的inline元素。Phrasing元素內部一般只能包含別的Phrasing元素。

Embedded

所有用於在網頁中嵌入外部資源的元素均屬於Embedded元素,具體包含以下9個:audio, video, img, canvas, svg, iframe, embed, object, math。

Interactive

所有與用戶交互有關的元素均屬於Interactive元素,包括a, input, textarea, select等。

內容模型(Content Model)

根據以上元素分類,HTML5標準文檔定義了任何元素的內容模型 — 對於該元素而言,何種子元素才是合法的。

對於p元素而言,其內容模型為Phrasing, 這意味著p元素只接受Phrasing元素為子元素,而對於像div這樣的非Phrasing元素則並不接受。類似的,li元素的內容模型為Flow,因此任何可以放置在body中的元素都可以作為li元素的子元素。

錯誤案例img

直接寫 html 元素時我們可能會有意識的避免 p 標籤包含 div,使用 antd 時有些組件可能會不太注意,比如 Divider 是使用 div 實現的,不能作為 p 標籤的子元素。

頁面可能正常解析,但不符合語義。這是因為瀏覽器自帶容錯機制,對於不規範的寫法也能夠正確的解析,各瀏覽器的容錯機制不同,所以儘量按規範來寫。

Props 類型錯誤warning

組件接收的 props 類型與預定義的不符。

錯誤案例

以上的 case 最容易產生這種 warning,當我們定義了一個高階組件,此組件是對已有 From 組件的一個封裝,同時我們額外接收一個 param 參數來做一個其他事情,其他的參數我們要傳回 Form。這時如果不做額外的操作,param 參數也會被傳入 Form 組件,它是一個意外的參數,這就會讓 React 拋出 warning,我們可以做下面的處理:

componentWillReceiveProps 棄用warningimgcomponentWillReceiveProps

這些生命周期經常被誤解或濫用,它們的潛在濫用可能會對異步渲染造成更大的問題,未來其會被逐漸棄用,現在使用如果沒有加 UNSAFE_ 前綴,則會在控制臺拋出錯誤。

React Fiber 引入了異步渲染,有了異步渲染之後,React 組件的渲染過程是分時間片的,不是一口氣從頭到尾把子組件全部渲染完,而是每個時間片渲染一點,然後每個時間片的間隔都可去看看有沒有更緊急的任務(比如用戶按鍵),如果有,就去處理緊急任務,如果沒有那就繼續照常渲染。

根據 React Fiber 的設計,一個組件的渲染被分為兩個階段:第一個階段(也叫做 render 階段)是可以被 React 打斷的,一旦被打斷,這階段所做的所有事情都被廢棄,當 React 處理完緊急的事情回來,依然會重新渲染這個組件,這時候第一階段的工作會重做一遍;第二個階段叫做 commit 階段,一旦開始就不能中斷,也就是說第二個階段的工作會穩穩噹噹地做到這個組件的渲染結束。

兩個階段的分界點,就是 render 函數。render 函數之前的所有生命周期函數(包括 render)都屬於第一階段,之後的都屬於第二階段。在 React v16.3 之前,render 之前的生命周期函數(也就是第一階段生命周期函數)包括這些:

componentWillReceiveProps

上面提到的濫用,其實就是在這些生命周期中產生了副作用,這些生命周期都應該是純函數,不應該產生任何副作用。到了 React v16.3,React 乾脆引入了一個新的生命周期函數 getDerivedStateFromProps,這個生命周期函數是一個 static 函數,在裡面根本不能通過 this 訪問到當前組件,輸入只能通過參數,對組件渲染的影響只能通過返回值。沒錯,getDerivedStateFromProps 應該是一個純函數,React 就是通過要求這種純函數,強制開發者們必須適應異步渲染。

錯誤案例

已棄用寫法:

class ExampleComponent extends React.Component {
    state = {
      isScrollingDown: false,
    };
    componentWillReceiveProps(nextProps) {
      if (this.props.currentRow !== nextProps.currentRow) {
        this.setState({
          isScrollingDown:
            nextProps.currentRow > this.props.currentRow,
        });
      }
    }
  }

推薦寫法:

class ExampleComponent extends React.Component {
    state = {
      isScrollingDown: false,
      lastRow: null,
    };
    static getDerivedStateFromProps(props, state) {
      if (props.currentRow !== state.lastRow) {
        return {
          isScrollingDown: props.currentRow > state.lastRow,
          lastRow: props.currentRow,
        };
      }
      return null;
    }
  }

getSnapshotBeforeUpdate 無返回值warning

如果組件實現了 getSnapshotBeforeUpdate() 生命周期,則它的返回值將作為 componentDidUpdate() 的第三個參數 「snapshot」 參數傳遞。否則此參數將為 undefined。

getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)之前調用。它使得組件能在發生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)。此生命周期的任何返回值將作為參數傳遞給 componentDidUpdate()。此用法並不常見,但它可能出現在 UI 處理中,如需要以特殊方式處理滾動位置的聊天線程等。

錯誤案例

已棄用寫法:

componentWillUpdate(nextProps, nextState) {
    if (this.props.list.length < nextProps.list.length) {
      this.previousScrollOffset =
        this.listRef.scrollHeight - this.listRef.scrollTop;
    }
}

componentDidUpdate(prevProps, prevState) {
    if (this.previousScrollOffset !== null) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight -
        this.previousScrollOffset;
      this.previousScrollOffset = null;
    }
}

在上面的示例中,componentWillUpdate用於讀取DOM屬性。但是,使用異步渲染時,「render」階段生命周期(如componentWillUpdate和render)和「commit」階段生命周期(如componentDidUpdate)之間可能會有延遲。如果用戶在此期間進行了諸如調整窗口大小的操作,則scrollHeight從中讀取的值componentWillUpdate將不準確。

正確用法:

getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.list.length < this.props.list.length) {
      return (
        this.listRef.scrollHeight - this.listRef.scrollTop
      );
    }
    return null;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot !== null) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight - snapshot;
    }
  }

誤用純函數 Renderwarningimg

上面我們提到 render 函數也屬於 render 階段的生命周期,所以它一定也要是純函數,有時候為了方便我們會在 render 函數中做一些狀態更改,這種用法是錯誤的。

錯誤案例img

上面的案例中,在 render 中根據 hash 值對狀態做了更改,正確的用法是這種操作應該在狀態初始化時完成,而不是在 render 函數中。

react hot loaderimg

這個是 react-hot-loader 的一個 bug,react-hot-loader react-dom 補丁對其進行了修復 https://www.npmjs.com/package/react-hot-loader#hot-loaderreact-dom

安裝 @hot-loader/react-dom ,在 webpack 配置中通過 alias 將 @hot-loader/react-dom 指向 react-dom 即可。

【Mobx】observableArraywarningimg

不同於 sort 和 reverse 函數的內置實現,observableArray.sort 和 observableArray.reverse 不會改變數組本身,而只是返回一個排序過/反轉過的拷貝。在 MobX 5 及以上版本中會出現警告。推薦使用 array.slice().sort() 來替代。

錯誤案例
store.data.sort((a, b) => a.status - b.status);

上面的代碼不會直接改變 array,推薦下面的寫法:

store.data = store.data.slice().sort((a, b) => a.status - b.status);

覺得本文對你有幫助?請分享給更多人

推薦關注「前端大全」,提升前端技能

點讚和在看就是最大的支持❤️

相關焦點

  • 如何用純css打造類materialUI的按鈕點擊動畫並封裝成react組件
    但隨著對用戶體驗的越來越重視,對交互體驗要求的提高以及css3等新標準的出現,使得web更加大放異彩, 各種動效的實現都變得非常容易.筆者在研究materialUI框架時對於它的交互及其讚嘆.所以為了自己能實現一個類似materialUI的按鈕點擊動畫,並封裝到自己的UI庫中,筆者特地總結了一些思路,希望可以和廣大的前端工程師們一起探討.
  • 寫React Hooks前必讀
    如果對於某些場景,確實不需要「exhaustive-deps」,可在代碼處加:// eslint-disable-next-line react-hooks/exhaustive-deps如若有發現hooks相關lint導致的warning,不要全局autofix除了hooks外,正常的lint基本不會改變代碼邏輯
  • React Status 中文周刊 #8 - 100 行代碼實現 Facebook 的 Recoil React 庫
    🔥 本周熱門100 行代碼實現 Facebook 的 Recoil React 庫 — 這個想法很大膽。作者並非為了 100% 複製 Recoil 的功能,而是為了挑戰自己的編程技術。來看看他怎麼做的吧。
  • 如何設計 React 代碼結構?
    在本文中,我只會介紹在設計React代碼結構方面的個人喜好,以及其中的原因。希望你能從中借鑑一二,或者至少可以從不同的角度理解這個問題。拼圖首先,我想簡單地介紹一下我是哪種程式設計師。在寫代碼時,我會需要構建的東西看成基本的組成部分,就像拼圖一樣。
  • 《精通react/vue組件設計》之實現一個健壯的警告提示(Alert)組件
    選手來說,如果沒用typescript,建議大家都用PropTypes, 它是react內置的類型檢測工具,我們可以直接在項目中導入. vue有自帶的屬性檢測方式,這裡就不一一介紹了.基於react實現一個Alert組件2.1.
  • 寫React Hooks前需要注意什麼?
    如若有發現hooks相關lint導致的warning,不要全局autofix除了hooks外,正常的lint基本不會改變代碼邏輯,只是調整編寫規範。但是hooks的lint規則不同,exhaustive-deps 的變化會導致代碼邏輯發生變化,這極容易引發線上問題,所以對於hooks的waning,請不要做全局autofix操作。
  • 【實戰總結篇】寫React Hooks前必讀
    如若有發現hooks相關lint導致的warning,不要全局autofix除了hooks外,正常的lint基本不會改變代碼邏輯,只是調整編寫規範。但是hooks的lint規則不同,exhaustive-deps 的變化會導致代碼邏輯發生變化,這極容易引發線上問題,所以對於hooks的waning,請不要做全局autofix操作。
  • React.js 2016最佳實踐
    下個版本的react-router將會對代碼分隔做更多支持。  對於react-router的未來規劃,可以去看博文Ryan Florence: Welcome to Future of Web Application Delivery。
  • React 16.8發布:hooks終於來了!
    相反,可以在一些新組件中嘗試使用 hooks,並讓我們知道你的想法。使用 hooks 的代碼仍然可以與使用類的現有代碼並存。是的!如果你願意,應該可以在大部分新代碼中使用 hooks。在 hooks 還處於 alpha 狀態的時候,React 社區就已經使用 hooks 為動畫、表單、訂閱、與其他庫集成等創建了很多有趣的示例。我們也感到很興奮,因為 hooks 讓代碼重用變得更加容易,可以幫助你以更簡單的方式開發組件並為用戶帶來更出色的用戶體驗。
  • 6個React Hook最佳實踐技巧
    它使函數組件能夠以新的方式編寫、重用和共享 React 代碼。在這篇文章中,我將分享 6 個關於 React Hooks 的技巧。你可以把它當作一份指南,在將 Hooks 實現到組件中時可以拿來參考。這條規則看起來是句廢話,但無論是新手還是經驗豐富的 React 開發人員,都常常會忘記遵循 React Hooks 的規則。
  • React Hooks使用小結
    社區,論壇裡各位大佬大多認為,更加的可抽象,邏輯可復用,代碼精簡,避免寫各種生命周期,這裡我就react在官方文檔提到的hooks出現的動機(emmm...不就是好處麼)與大家的使用感受做一下簡單的總結:公共邏輯的復用。
  • React.js 2016 最佳實踐
    如果你在瀏覽器中使用React.js,你將會接觸到這個點,並為其選擇一個庫。我們選擇的是出自優秀rackt社區的react-router,這個社區總是能為React.js愛好者們帶來高質量的資源。要使用react-router需要查看它的文檔,但更重要的是:如果你使用Flux/Redux,我們推薦你將路由state與store或全局state保持同步。
  • 十大最受歡迎的 React Native 應用開發編輯器
    而在隨後從事 React Native 開發工作過程中,對相應的編輯器做了一些探索和研究,本文總結了一些非常適合移動應用開發的編輯器和 IDE。1.它是免費和開源的,支持調試、嵌入式 Git 控制項、語法高亮、智能代碼補全、代碼段和代碼重構。擴展ReactNative Tools - 此擴展為React Native 項目提供了開發環境。你可以調試代碼,從命令終端快速運行 react-native 命令,並使用 IntelliSense 瀏覽 React Native API 的對象、函數和參數。5.
  • 【重學React】動手實現一個react-redux
    我們知道,react 中高階組件可以實現邏輯的復用。文中所用到的 Counter 代碼在 https://github.com/YvetteLau/Blog 中的 myreact-redux/counter 中,建議先 clone 代碼,當然啦,如果覺得本文不錯的話,給個star鼓勵。
  • 精通React/Vue系列之帶你實現一個功能強大的通知提醒框
    該組件在諸如Antd或者elementUI等第三方組件庫中也都會出現,主要用來為用戶提供系統通知信息的.我們在調用它時並不像其他組件一樣,通過引入組件標籤來調用。我們在全局使用的配置方法是xNotification.config(config), 在通知框實例中我們使用xNotification.pop(config)。
  • 面試題:React中setState是異步還是同步?
    在學習react的過程中幾乎所有學習材料都會反覆強調一點setState是異步的,來看一下react官網對於setState的說明。
  • 30 分鐘精通 React 新特性——React Hooks
    你在還在為組件中的this指向而暈頭轉向嗎?——既然Class都丟掉了,哪裡還有this?你的人生第一次不再需要面對this。這樣看來,說React Hooks是今年最勁爆的新特性真的毫不誇張。如果你也對react感興趣,或者正在使用react進行項目開發,答應我,請一定抽出至少30分鐘的時間來閱讀本文好嗎?
  • 【前端技術】react渲染 - 流程概述
    實際上jsx 是來源於一個前端框架 react。在react中除了我們了解的jsx,那麼jsx在react的渲染過程是哪個環節生效,以及渲染過程經歷了哪些步驟。本文會基於這些點進行概述。1.本文的react.render樹狀圖.xmind,此為作者查看/調試react的渲染源碼時做的結構筆記。
  • React SSR 同構入門與原理
    安裝包:npm i redux react-redux redux-thunk --save複製代碼redux 庫;react-redux 是 react 與 redux 的橋梁;redux 都添加完畢後,最後我們在組件中使用 redux 的方式獲取數據,改造 Home 組件:import React from"react";import { Link } from"react-router-dom";import { connect } from"react-redux
  • React Status 中文周刊 #26 - Aleph:基於 Deno 的 React 框架
    以及使用諸如 AWS CDK 之類的自動化工具,你可以在 React 應用中實現和部署兩種服務端渲染方案。/Cassidy Williams6 件 React 開發者不做會後悔的事 — 沒有什麼比可以從他人的錯誤中吸取教訓更好的事情了,作者坦率的承認了之前的不足以及表明了如何改進。