react/react-native效能優化

ZoenLeo發表於2018-12-14

寫在前面

本文適合新手入門,如果是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>
            )
        }
    }
複製程式碼

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

相關文章