当我在开发 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 最适合的地方是应用于大型项目,在小型项目、简单页面中的价值并非很大,毕竟一个页面若是自己就一点简单的内容展现通常也不须要多少内存。而大型项目就不同的,一个页面可能有几十个组件,这时候每一个组件的性能问题都应该重视起来,等到页面崩溃的时候再去找问题那就真是费时又费力,还不必定短期内能解决。