ReactJS組件state的最佳實踐

當我們寫React應用的時候,知道在組件中何時使用state何時不使用state,是非常重要的。在這篇文章中,我將回顧我所認為的使用state的最佳實踐:

  1. 如果component沒有自己的數據,那么其他數據便不應該影響它的state。
  2. 用于描述組件的state盡可能簡單。
  3. 運算和條件判斷移動到render函數。

這些規則如果有特殊情況,應該在適當的時候違反。不過如果你能夠一直都遵循它們的話,你會發現你的component更容易解耦,測試更容易寫,而且整個應用的bug也很少。下面讓我們仔細看看這些規則:

1. 如果component沒有自己的數據,那么其他數據便不應該影響它的state

第一,可能是最重要的一點,component的state不應該依賴于props傳遞。當然props可能向子組件傳遞state,例如,在一個普通的input組件中,為了禁用input的文字輸入,我可能選擇一個disabled的prop。但是當我說'state'的時候,我是明確的指component的state屬性。所以,當state開始依賴于它的props的時候,你可能會發現這是一段不好的代碼。看看以下代碼片段:

    import React from 'react';

    class UserWidget extends React.Component {

    // BAD: 通過props接收到的值設置this.state.fullName
        constructor (props) {
            this.state = {
                fullName: `${props.firstName} ${props.lastName}`
            };
        }

        render () {
            var fullName = this.state.fullName;
            var picture = this.props.picture;

            return (
                <div>
                    <img src={picture} />
                    <h2>{fullName}</h2>
                </div>
            );
        }
    }

以上代碼有什么問題?一開始可能不是很明顯,但是假如firstName或者lastName改變了,UserWidget組件的視圖將不會改變。構造函數在組件初始化渲染執行之后只會調用一次,因此fullName的值永遠是第一次渲染時候的值。React新手可能經常會犯這樣的錯誤,因為setState是更新組件視圖的最簡單且最明顯的方式。

你應該問問你自己,該組件是否擁有這些數據,內部的firstName和lastName創建了嗎?如果沒有,那么state不應該依賴便不應該依賴于這些數據。那么最好的避免這個問題的方式是什么呢?在render函數里面計算fullName的值。

    render () {
        var fullName = `${this.props.firstName} ${this.props.lastName}`;
        // ...
    }

把fullName移動到render函數里面之后,我們將不用再關心fullName的值是否更新了。當props改變的時候,React會運行一個鉤子函數--componentWillReceiveProps,然而我還是會考慮這種反模式,因為它不需要增加項目的復雜性。

當然,如果你在組件初始化之后不關心props,那么這條規則將不會適用。

當使用React.createClass代替extends React.Component時候,則用getInitialState代替constructor。
有時候,"state"將需要設置一些值,在flux模式中,可能是根控制器組件監聽不同的stores。

2. 用于描述組件的state盡可能簡單

你應該盡可能的簡單的去描述一個組件的狀態。在很多種情況下,這意味著用布爾值是更好的方式。

思考下面的例子,我們有一些組件,它們在state里面的class屬性是基于clicked和hovered事件改變的。(不管你信不信,我看到過很多這樣的例子)

    import React from 'react';
    var cx = React.addons.classSet;

    class ArbitraryWidget extends React.Component {

        constructor() {
            this.state = {
                classes: []
            };
        }

        // BAD: 當鼠標滑過的時候,把'hover'push到this.state.classes
        handleMouseOver() {
            var classes = this.state.classes;
            classes.push('hover');

            this.setState({ classes: classes });
        }

        // BAD: 當鼠標離開的時候,從this.state.classes移除'hover'
        handleMouseOut() {
            var classes = this.state.classes;
            var index = classes.indexOf('hover');
            classes.splice(index, 1);

            this.setState({ classes: classes });
        }

        // BAD: 被點擊的時候,在this.state.classes切換'active'
        handleClick() {
            var classes = this.state.classes;
            var index = classes.indexOf('active');

            if (index != -1) {
                classes.splice(index, 1);
            } else {
                classes.push('active');
            }

            this.setState({ classes: classes });
        }

        render() {
            var classes = this.state.classes;

            return ( 
                <div className={cx(classes)}
                    onClick={this.handleClick.bind(this)}
                    onMouseOver={this.handleMouseOver.bind(this)}
                    onMouseOut={this.handleMouseOut.bind(this)}
                />
            )
        }
    }

這個組件可以運行,但是我持保留意見。它現在的state是一個存著字符串類型的數組,this.state.classes = ['active', 'hover'],不僅代碼的可讀性很差,而且改變起來特別麻煩。假如有其他組件依賴于我的這個class的數組,那么查看這個數組是否包含hover肯定比查看hover的布爾值是什么的難度要大。我們需要重構這段代碼,用布爾值代表組件是否應該有這些class,例如isHovering === true意味著我是否應該使用hover這個class。

    import React from 'react';
    var cx = React.addons.classSet;

    class ArbitraryWidget extends React.Component {

        constructor() {
            this.state = {
                isHovering: false,
                isActive: false
            };
        }

        // GOOD: 當鼠標滑過的時候,this.state.isHovering設置為true
        handleMouseOver() {
            this.setState({ isHovering: true });
        }

        // GOOD: 當鼠標離開的時候,this.state.isHovering設置為false
        handleMouseOut() {
            this.setState({ isHovering: false });
        }

        // GOOD: 被點擊的時候,改變this.state.active
        handleClick() {
            var active = !this.state.isActive;
            this.setState({ isActive: active });
        }

        render() {
            // use the classSet addon to concat an array of class names together
            var classes = cx([
                this.state.isHovering && 'hover',
                this.state.isActive && 'active'
            ]);

            return ( 
                <div className={cx(classes)}
                    onClick={this.handleClick.bind(this)}
                    onMouseOver={this.handleMouseOver.bind(this)}
                    onMouseOut={this.handleMouseOut.bind(this)}
                />
            );
        }
    }

為了使用這些state的布爾值,我們必須在render函數里面計算class數組。但是,我們增強了代碼的可讀性,this.state.isHovering遠比this.state.classes.indexOf('hover') != -1更能代表組件實際的狀態。這個組件更容易擴展和測試,因為我們不需要考慮數組的構建。

我想再說一遍,你應該始終以用最簡單的方式表示state為目標。這并不一定意味著你只能存儲布爾值,有可能是深層嵌套的對象,也可能是數字、字符串或者函數。
想象一下作為其他人,試圖觀察組件返回的一個class的數組的狀態,這個數組對于你是否有用呢?當然沒有。相比之下布爾值isActive是更為可行的。我希望你明白我的意思。

3. 運算和條件判移動到render函數

在前面的兩條規則中,這一條其實已經提到了。然而,它仍然是值得注意的。盡可能的在render函數中進行最后一步運算。雖然這樣也許會略慢于其他方法,但它能確保最少的重定向組件,在輕微的性能提升之前,我們應該更注重代碼的可讀性和擴展性。

我需要連接prop中的firstName和lastName?把它移動到render函數。我的組件需要使用哪個class?在render函數中做決定。如果我的todo列表沒有任何項目,我應該顯示在text框中顯示一個placeholder?在render函數中做決定。我需要格式化電話號碼?在render函數中做決定。我該如何呈現出子組件?在render函數中做決定。我今天要吃午飯嗎?在render函數中做決定。

當然,你不要把所有代碼都放在一個函數里面。相反,最好把它們分割成合適的helper函數(用一個好的名字),關鍵是你用render函數做太多的事情的話,應該減少它的復雜性。你可以用一個前綴來表示helper函數。例如:

    // GOOD: Helper function to render fullName
    renderFullName () {
        return `${this.props.firstName} ${this.props.lastName}`;
    }

    render () {
        var fullName = this.renderFullName();
        // ...
    }

CPU密集運算

因為我建議你把所有的東西都推遲到render函數中,它會導致CPU密集運算也會推遲。為了避免重復復雜的渲染,考慮memoization的功能。

不要把變量存儲到component實例上

不要像下面這樣做:

    class ArbitraryWidget extends React.Component {

        constructor () {
            this.derp = 'something';
        }

        handleClick () {
            this.derp = 'somethingElse';
        }

        render () {
            var something = this.derp;
        }
    }

這是非常不好的,不僅是因為你沒有遵守用this.state存儲值的約定,而且this.derp改變的時候,不會自動觸發render。

原文地址
Best Practices for Component State in React.js

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容