react/react-native性能优化

5,835 阅读6分钟

写在前面

本文适合新手入门,如果是react老玩家可以break或者查漏补缺。


这里就说一些常见的新手误区以及优化方法:

为何需要优化

笔者一直觉的性能优化是一个累积的过程,贯穿在你所写的每一行代码中。不注意优化平常或许不会有什么大的问题,但是谁也不知道哪一句会变成压死骆驼的那最后一根稻草,造成世界爆炸💥。


下面是正文,希望能对你有所帮助。

react优化重点

react性能优化的核心:

减少render次数!推迟或者分摊render

原因是react绝大部分的开销都产生在render时期 , 在render过程中会有大量的对象复制现象 , 同时会产生许多碎对象(react element) , 用于与上个对象进行对比 , 并且在每一次render中产生。

针对这一点特性 , 总结了一下几点常用的优化方法:

优化实例

  1. 根据props初始化组件

例:

    class Page extends Component (props) {
        state = {
            a: 1
        }
        componentDidMount() {
            this.setState({
                a: this.props.a
            })
        }
    }

很明显的错误是在生命周期内二次修改组件,虽然符合了修改状态的时机(componentDidMount内修改状态),但是应该想想是否有多余的二次渲染嫌疑,应该直接在constructor内初始化(也更符合“初始化”的字面定义)。

优化:

class Page extends Component (props) {
        constructor(props) {
            super(props)
            this.state = {
                a: props.a
            }
        }
    }
  1. 继承PureComponent

例:

    class Page extends Component (props) {
        constructor(props) {
            super(props)
            this.state = {
                a: props.a
            }
        }
    }

在有props传入同时没有使用shouldComponentUpdate的情况下使用PureComponent可以有效减少render次数,其本质上是使用了组件的shouldComponentUpdate(newProps, newState)生命周期,在render之前对比props、 state(浅对比),若无变化则不更新。

优化:

class Page extends PureComponent (props) {
        constructor(props) {
            super(props)
            this.state = {
                a: props.a
            }
        }
    }
  1. 使用shouldComponentUpdate

例:

    class Page extends Component (props) {
        constructor(props) {
            super(props)
            const { a, b } = props.data;
            this.state = {
                a,
                b
            }
        }
        render() {
            const { a } = this.props.data;
            return <div>{a}</div>
        }
    }

在整个render中只需要a的情况下未使用shouldComponentUpdate,而组件的的更新通常会传入一个新的对象,如果a值未变,这就会造成无意义的rerender

优化:

    class Page extends Component (props) {
        constructor(props) {
            super(props)
            const { a, b } = props.data;
            this.state = {
                a,
                b
            }
        }
        shouldComponentUpdate(newProps, newState) {
            if (newProps.data.a !== this.props.data.a) {
                return true;
            }
            return false;
        }
        render() {
            const { a } = this.props.data;
            return <div>{a}</div>
        }
    }
  1. 复杂页面合理拆分组件

例:

    class Page extends PureComponent (props) {
        constructor(props) {
            super(props)
            this.state = {
                a: 1,
                ...
            }
        }
        render() {
            const { a } = this.props.data;
            return <div>
                {...}
            </div>
        }
    }

react的diff比对是以组件为单位进行的,page也是一个大组件,所有的数据都在一个页面,任何一个状态的变化会引起整个页面的刷新。合理地拆分组件, 并且结合PureComponent定义组件, 可以减少页面无变化部分的render次数,同时diff比对的粒度更细。

优化:

    class Page extends PureComponent (props) {
        constructor(props) {
            super(props)
            this.state = {
                a: 1,
                b: 2
            }
        }
        render() {
            const { a } = this.props.data;
            return <div>
                <Component1 a={a} />
                <Component2 b={b} />
                ...
            </div>
        }
    }
  1. componentDidMount周期调用接口并在返回之后setState( 经评论区的朋友提醒,因为componentWillMount react16.3已经标记为不安全的生命周期了,在17的时候会下掉,所以不再推荐这一条,感谢@Flasco 的提醒)

例:

    class Page extends PureComponent (props) {
        constructor(props) {
            this.state = {
                a: 1
            }
        }
        componentDidMount() {
            this.getData()
        }
        getData async() {
            const result = await API.getData();
            this.setState({
                a: result.a
            })
        }
    }

react确实强调不能在componentWillMount中修改状态,但是这里要考虑到的是,调用接口是异步操作,web端所有的异步操作都会在同步操作跑完之后再执行,所以在有接口调用或者其他异步操作的情况下,可以在componentWillMount中调用接口,并将状态修改写在回调中,可以加快响应时间。

优化:

    class Page extends PureComponent (props) {
        constructor(props) {
            this.state = {
                a: 1
            }
        }
        componentWillMount() {
            this.getData()
        }
        getData async() {
            const result = await API.getData();
            this.setState({
                a: result.a
            })
        }
    }
  1. jsx中不要定义函数

例:

    class Page extends PureComponent (props) {
        render() {
            return (
                <div onClick={() => {
                    ...
                }}/>
            )
        }
    }

render方法中定义的函数会在每次组件更新中重新定义,每次定义又都会重新申请一块内存,造成更多的内存占用,触发js的垃圾回收也会增大开销,严重影响性能。应该将函数存在实例上,持久化方法和内存,在render中绑定或使用。

优化:

    class Page extends PureComponent (props) {
        onClick = () => {
            ...
        }
        render() {
            return (
                <div onClick={this.onClick}/>
            )
        }
    }
  1. jsx中不要绑定this

例:

    class Page extends PureComponent (props) {
        onClick() {
            ...
        }
        render() {
            return (
                <div onClick={this.onClick.bind(this)}/>
            )
        }
    }

虽然实例中定义存储了函数,但是bind方法却会返回一个新的函数,同样加大了内存占用和垃圾回收的开销。可以将函数直接定义为箭头函数,或者在constructor中使用bind改this指向。

优化:

    class Page extends PureComponent (props) {
        constructor(props) {
            super(props)
            this.state = {
                ...
            }
            this.onBindClick = this.onBindClick.bind(this)
        }
        onClick = () => {
            ...
        }
        onBindClick() {
            ...
        }
        render() {
            return (
                <div onClick={this.onClick}>
                    <div onClick={this.onBindClick}/>
                </div>
            )
        }
    }
  1. 合理使用ref

例:

    const SLIDER_WEIGHT = 200
    class Page extends PureComponent (props) {
        constructor(props) {
            super(props)
            this.state = {
                left: 0
            }
        }
        componentDidMount() {
            this.initSwiper()
        }
        initSwiper = () => {
            this.intervalId = setInterval(() => {
                this.setState((state) => ({
                    left: left + SLIDER_WEIGHT
                })))
            }, 2000)
        }
        render() {
            const { left } = this.state
            return (
                <div>
                    <div style={{left: left + 'px'}}>
                        ...
                    </div>
                </div>
            )
        }
    }

假设这里要实现一个轮播图,为了实现轮播效果在循环定时器中频繁修改state,每次更新组件状态,会造成组件的频繁渲染。这时候可以使用ref修改dom样式而不需要触发组件更新。

优化:

例:

    const SLIDER_WEIGHT = 200
    class Page extends PureComponent (props) {
        left = 0
        componentDidMount() {
            this.initSwiper()
        }
        initSwiper = () => {
            this.intervalId = setInterval(() => {
                this.left += SLIDER_WEIGHT
                this.refs.swiper.style.left = this.left + 'px'
            }, 2000)
        }
        render() {
            const { left } = this.state
            return (
                <div>
                    <div ref="swiper">
                        ...
                    </div>
                </div>
            )
        }
    }
  1. 使用Fragment

例:

    () => <div>
        <div>content1</div>
        <div>content2</div>
    </div>

当你写一个单独的组件时,如果发现最外层元素实质上是多余的,但是子元素又有多个标签并列不能直接暴露时,你应该使用Fragment,一个空的jsx标签。这样在渲染的时候会少一个标签,并且使我们的代码看起来更加简洁。

优化:

例:

    () => <>
        <div>content1</div>
        <div>content2</div>
    </>

react-native优化点

上文中几条优化方法同样适用于react-native,因为它们有着同样的抽象层,但是react-native有一些独特的优化技巧,提供给即将需要写native的同学😏

  1. 使用 Animated动画,将一部分的功能放到原生上去执行。可以理解为css3的动画,底层优化与更简单的实现使我快乐。

  2. 考虑能否使用更优的组件:listView、Flatlist ... 和属性:pagesize、removeClippedSubviews... ,同样是底层优化带来的便利。

  3. 使用Interactionmanager将一些耗时较长的工作安排到所有的交互或者动画完成之后再进行可以分摊开销(react-native的js与原生代码通信带来的性能瓶颈),Interactionmanager.runAfterInteractions()中回调。


以上就是我总结的几个react/react-native性能优化点, 若你有更多方案,请于下方留言,我会及时补充,最后祝大家写代码永无bug,性能永远最优。

//
//                            _ooOoo_
//                           o8888888o
//                           88" . "88
//                           (| -_- |)
//                           O\  =  /O
//                        ____/`---'\____
//                      .'  \\|     |//  `.
//                     /  \\|||  :  |||//  \
//                    /  _||||| -:- |||||-  \
//                    |   | \\\  -  /// |   |
//                    | \_|  ''\---/''  |   |
//                    \  .-\__  `-`  ___/-. /
//                  ___`. .'  /--.--\  `. . __
//               ."" '<  `.___\_<|>_/___.'  >'"".
//              | | :  `- \`.;`\ _ /`;.`/ - ` : | |
//              \  \ `-.   \_ __\ /__ _/   .-` /  /
//         ======`-.____`-.___\_____/___.-`____.-'======
//                            `=---='
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//                      佛祖保佑       永无BUG

-- The end