當我在開發 React 項目時,常常會想,要是有一個工具能實時告知我組件是否有性能問題就行了,這樣就能在開發的時候就儘可能避免組件過大時的性能問題,以及下降潛在的頁面崩潰機率。
而後我就在網上找到了這個工具:@welldone-software/why-did-you-render
,它能在我開發 react 組件的時候及時提醒我當前寫的組件是否有沒必要要的重複渲染問題,在開發的時候就避免掉部分性能問題。react
why did you render 應當在開發環境
裏使用。
爲避免麻煩,如下why did you render 都簡稱 why render
。ios
官網地址: https://github.com/welldone-s...
文檔說明: https://medium.com/welldone-s...
安裝 npm 包:git
npm install @welldone-software/why-did-you-render --save
開發環境裏啓用(全局啓用,部分組件啓用請看官方文檔):github
import React from 'react'; if (process.env.WHY_RENDER) { const whyDidYouRender = require('@welldone-software/why-did-you-render'); whyDidYouRender(React, { trackAllPureComponents: true, }); }
官網裏是判斷 process.env.NODE_ENV === 'development'
,不過我以爲應該和原有的開發,環境區分開發,新建一個 script
,否則容易給同一個項目裏其餘開發同窗形成困擾:npm
package.json
新建一個 scripts
json
"scripts": { "wr": "npm run dev --WHY_RENDER" }
而後咱們就可使用 npm run wr
開發了。緩存
當咱們啓用了 why render 插件開發的時候,若是組件裏有沒必要要的re-render問題時,控制檯裏會有相關的信息提示(不論是頁面加載的時候仍是交互的時候均可能會有提示):函數
上圖裏有兩種可改進的地方,一個是 props 的 onchange 事件這塊致使 RedioGroup 和 Checkbox re-render了;另外一個是 state 的值並未改變也致使 re-render。
根據這些提示咱們能夠針對性優化,一個一個的解決,直至全部的提示都清除。工具
下面我對各種提示及相應的解決辦法歸類了一下:性能
父組件給子組件傳的是表達式、函數、組件時觸發 re-render提示問題。
<ClassDemo regEx={/something/} fn={function something(){}} date={new Date('6/29/2011 4:52:48 PM UTC')} reactElement={<div>hi!</div>} />
上面是官方文檔(?)裏的一個例子,當經過表達式等方式而不是變量的方式把 props 傳遞給子組件的時候,控制檯裏會有 re-render 提示:
解決辦法,只須要把表達式等賦給變量再使用變量傳遞 props 就行:
const reg = /something/; const something = function f(){}; const date = new Date('6/29/2011 4:52:48 PM UTC'); const elem = () => { return { <div>hi!</div> } } <ClassDemo regEx={reg} fn={something} date={date} reactElement={elem} />
不過我歷來都是經過建立變量來傳遞props的,直接把表達式傳過去也太low了。
這個最多見的例子是在接口請求裏,好比查詢數據,當參數未改變,再次查詢返回的數據未變時,可能會致使數據的渲染組件的re-render,這在ant-design 的 table 組件裏很常見。
解決辦法:
一、更改state前先判斷,有變化才更改state;
二、使用React.memo 或者 shouldComponentUpdate;
hooks組件裏,若是事件處理器 F props 裏依賴 state,在state 變化時F props 會傳入新的事件方法。
在下圖的真實場景裏,全部的ant-design表單元素value是用的一個 state 對象,事件方法也是同一個,若是不處理,隨便一個表單元素的值更改都會致使全部的表單元素 re-render,而後就有了不少個 re-render 提示。
要讓事件處理器不變,有兩個解決方式,不過也是大同小異:
// 通用事件處理函數 export function useEventCallback(fn, dependencies) { const ref = useRef(() => { throw new Error('Cannot call an event handler while rendering.'); }); useEffect(() => { ref.current = fn; }, [fn, ...dependencies]); return useCallback((...args) => { const fn = ref.current; return fn(...args); }, [ref]); } // 使用 const [state, setState] = useState({ name: '', type: '', }); // 事件處理器 onChange const onChange = useEventCallback((value) => { // setState or do otherthing }, [state]);
// 一個簡單的ref 自定義 hooks export function useRefProps(props) { const ref = useRef(props); // 每次渲染更新props useEffect(() => { ref.current = props; }); return ref; } // 使用 const [state, setState] = useState({ name: '', type: '', }); const stateRef = useRefProps({ state, [...others] }); // 事件處理器 onChange const onChange = useCallback((value) => { const { state, ...others } = stateRef.current; // setState or do otherthing }, []);
兩種方式的原理相似,都是把變化數據/函數使用Ref做爲中轉,使用 useCallback 緩存結果。不過明顯第一種易用性更高。
使用這兩個方法獲得的處理事件處理器即使做爲 props 傳給子組件也不會變化的。
有些 ant-design 的表單組件會觸發re-render提示。好比表單裏支持 Option 子組件的組件,例如 Select組件,使用 Option 組件就會有 re-render 提示。
解決辦法:Option 組件使用 options props 替代,這塊ant-design 應該是有優化的。
其餘的組件有re-render提示均可先在 ant-design上查找是否有最新的使用方式。
// 會致使 re-render 的使用方式 <Select onChange={handleChange}> <Option value="jack">Jack</Option> <Option value="lucy">Lucy</Option> <Option value="disabled" disabled></Option> </Select> // 不會致使 re-render 的使用方式 const options = [ { label: 'Jack', value: 'jack', }, { label: 'Lucy', value: 'lucy', } ] <Select onChange={handleChange} options={options}> </Select>
若是咱們的項目沒有引入第三方庫,那咱們是能夠優化掉全部的 re-render 提示的。但實際開發中這種狀況基本上不會出現的,因此這就致使了一個問題:當第三方組件致使了 re-render 提示時,咱們極可能由於沒法更改第三方庫而致使 re-render 提供沒法消除掉。
因此沒必要執着於消除全部的 re-render 提示,控制 re-render 數量在一個可接受的範圍內,好比加載完或交互完後提示在三個內,也不失爲一個合理的優化結果。
why render 最適合的地方是應用於大型項目,在小型項目、簡單頁面中的價值並非很大,畢竟一個頁面若是自己就一點簡單的內容展現通常也不須要多少內存。而大型項目就不同的,一個頁面可能有幾十個組件,這時候每一個組件的性能問題都應該重視起來,等到頁面崩潰的時候再去找問題那就真是費時又費力,還不必定短期內能解決。