1. 基本原理
1.1 render()函数
一般来说, 要尽可能少地在 render 函数中做操作. 如果非要做一些复杂操作或者计算, 也许你可以考虑使用一个 memoized https://en.wikipedia.org/wiki/Memoization 函数以便于缓存那些重复的结果. 可以看看 Lodash.memoize https://lodash.com/docs#memoize , 这是一个开箱即用的记忆函数.
反过来讲, 避免在组件的 state 上存储一些容易计算的值也很重要. 举个例子, 如果 props 同时包含 firstName 和 lastName, 没必要在 state 上存一个 fullName, 因为它可以很容易通过提供的 props 来获取. 如果一个值可以通过简单的字符串拼接或基本的算数运算从 props 派生出来, 那么没理由将这些值包含在组件的 state 上.
1.2 Prop 和 state
重要的是要记住, 只要 props(或 state)的值不等于之前的值, React 就会触发重新渲染. 如果 props 或者 state 包含一个对象或者数组, 嵌套值中的改变会触发重新渲染. 考虑到这一点, 你需要注意在每次渲染的生命周期中, 创建一个新的 props 或者 state 都可能无意中导致了性能下降.(注: 对象或者数组只要引用不变, 是不会触发 rerender 的)
举几个现在我在开发中遇到的几个问题:
例子: 函数绑定的问题
- /*
- 给 prop 传入一个行内绑定的函数 (包括 ES6 箭头函数) 实质上是在每次父组件 render 时传入一个新的函数.
- */
- render() {
- return (
- <div>
- <a onClick={() => this.onRender()}>显示</a>
- <a onClick={() => this.onRender.bind(this)}>显示</a>
- </div>
- );
- }
- /*
- 应该在构造函数中处理函数绑定并且将已经绑定好的函数作为 prop 的值
- */
- constructor( props ) {
- this.doSomething = this.doSomething.bind( this );
- //or
- this.doSomething = (...args) => this.doSomething(...args);
- }
- render() {
- return (
- <div>
- <a onClick={ this.onRender }>显示</a>
- </div>
- );
- }
例子: 对象或数组字面量
- /*
- 对象或者数组字面量在功能上来看是调用了 Object.create() 和 new Array(). 这意味如果给 prop 传递了对象字面量或者数组字面量. 每次 render 时 React 会将他们作为一个新的值. 这在处理 行内样式时通常是有问题的.
- */
- /* 遇到的写法 */
- // 每次渲染时都会为 style 新建一个对象字面量
- render() {
- return <div style={ { backgroundColor: 'red' } }/>
- }
- /* 建议写法 */
- // 在组件外声明
- const style = { backgroundColor: 'red' };
- render() {
- return <div style={ style }/>
- }
例子 : 注意兜底值字面量
- /*
- 有时我们会在 render 函数中创建一个兜底的值来避免 undefined 报错. 在这些情况下, 最好在组件外创建一个兜底的常量而不是创建一个新的字面量.
- /*
- /* 遇到的写法 */
- render() {
- let thingys = [];
- // 如果 this.props.thingys 没有被定义, 一个新的数组字面量会被创建
- if( this.props.thingys ) {
- thingys = this.props.thingys;
- }
- return <ThingyHandler thingys={ thingys }/>
- }
- /* 遇到的写法 */
- render() {
- // 这在功能上和前一个例子一样
- return <ThingyHandler thingys={ this.props.thingys || [] }/>
- }
- /* 建议的写法 */
- // 在组件外部声明
- const NO_THINGYS = [];
- render() {
- return <ThingyHandler thingys={ this.props.thingys || NO_THINGYS }/>
- }
1.3 尽可能的保持 Props(和 State)简单和精简
理想情况下, 传递给组件的 props 应该是它直接需要的. 为了将值传给子组件而将一个大的, 复杂的对象或者很多独立的 props 传递给一个组件会导致很多不必要的组件渲染(并且会增加开发复杂性).
我们使用 Redux 作为状态容器, 所以在我们看来, 最理想的是方案在组件层次结构的每一个层级中使用 https://www.npmjs.com/package/react-redux 的 connect() 函数直接从 store 上获取数据. connect 函数的性能很好, 并且使用它的开销也非常小.
这里已经优化了.
1.4 组件方法
由于组件方法是为组件的每个实例创建的, 如果可能的话, 使用 helper/util 模块的纯函数或者静态类方法. 尤其在渲染大量组件的应用中会有明显的区别.
2. 组件是否重新渲染
视图的变化是邪恶的
2.1 shouldComponentUpdate()
React 有一个生命周期函数 shouldComponentUpdate(). 这个方法可以根据当前的和下一次的 props 和 state 来通知这个 React 组件是否应该被重新渲染.
然而使用这个方法有一个问题, 开发者必须考虑到需要触发重新渲染的每一种情况. 这会导致逻辑复杂, 一般来说, 会非常痛苦. 如果非常需要, 你可以使用一个自定义的 shouldComponentUpdate()
方法, 但是很多情况下有更好的选择.
2.2 React.PureComponent
React 从 v15 开始会包含一个 PureComponent 类, 它可以被用来构建组件. React.PureComponent 声明了它自己的 shouldComponentUpdate() 方法, 它自动对当前的和下一次的 props 和 state 做一次浅对比. 有关浅对比的更多信息, 请参考这个 Stack Overflow:
在大多数情况下, React.PureComponent 是比 React.Component 更好的选择. 在创建新组件时, 首先尝试将其构建为纯组件, 只有组件的功能需要时才使用 React.Component. 更多信息, 请查阅相关文档 React.PureComponent.
2.3 怎么使用 PureComponent
PureComponent 节约了我们的时间, 避免了多余的代码. 那么, 掌握如何正确使用它是非常重要的, 否则如果使用不当, 它就无法发挥作用. 因为 PureComponent 仅仅是浅比较(shadow comparison), 所以改变组件内部的 props 或者 state , 它将不会发挥作用. 所以建议按上面建议的几种写法来开发. 这样如果用上面建议的写法来写的话就有两个好处:
一是避免组件每次渲染都做重复的事
二是避免在使用 PureComponent 时损失 PureComponent 的优点
注意:
使用了 pureComponent 之后, 只是浅比较, 这样会导致改变组件内部的 props 之后不会更新视图, 所以 pureComponent 建议用在纯 UI 类的组件里.
正常情况下, 迁移的方式非常简单, 就像改变组件继承的基类, 从
class MyComponent extends Component {...}
到
class MyComponent extends PureComponent {...}
在 PureComponent 出现之前, 通常是自己实现一个这样的组件:
- import React,{ Component }from 'react'
- import shallowEqual from 'react-pure-render/shallowEqual'
- export default class PureComponent extends Component{
- shouldComponentUpdate(nextProps, nextState) {
- return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
- }
- }
当然 react 性能优化不仅仅有上面提到的几点, 还可以从 webpack, 使用 immutable.JS 上优化
tuiguang.PNG
来源: http://www.jianshu.com/p/d6c34eb87bca