reactjs中props和state最佳實踐

reactjs中props和state最佳實踐

翻譯文章:ReactJS Props vs State Best Practices

props & state的誤用產生的bug,花費了我們大量的修復時間。在一個app中如果包含有成百不可見的危險代碼,對我們的應用將是非常危險和頭痛的事情。 
這篇文章中,你將明白這些不可預知的行爲產生的原因,這樣在編程中出錯將變得非常困難。這也可以用來解決我們其他JS編程中遇到的類似的問題。

在我開始學習reactjs時,第一個困惑我的事情是在使用props傳遞數據給組件、在組件的state中怎樣讀取數據源和store中提供的數據。這一方面,我低估了問題的難度。

一個簡單的例子

這一節中我們將明白它們(props & state)爲什麼這麼容易混淆。 
下面這段代碼是reactjs langding page中的第一個例子修改而來,其中的HelloMessage組件,在這裏我們使用Counter來代替。

/* Snippet 1 */
var Counter = React.createClass({
  render: function() {
    return <span>Count is {this.props.count}</span>;
  }
});

React.render(<Counter count={5} />, mountNode);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

非常容易,對不對?但是,我們現在使用state來做相同的事情。

/* Snippet 2 */
var items = ['item1', 'item2', 'item3'];

var Counter = React.createClass({
  getInitialState: function() {
    return {count: items.length};
  },

  render: function() {
    return <span>Count is {this.state.count}</span>;
  }
});

React.render(<Counter />, mountNode);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

哪一個是惡魔?一旦我們程序變大,哪一個將是不可見的錯誤?實際上,兩種用法都有可能正確,也有可能錯誤,這取決於它們使用時的上下文環境。接下來,我將嘗試使用最好的方法來解釋這是爲什麼。

我相信這些困惑的出現都是因爲理解過於簡單造成的。我們不能責備reactjs上面的例子不夠好,這些例子確實是一個很好的入門實例。但是沒有任何上下文環境,這樣在我們使用的時候就會遇到困難。第一件比較明顯的事情就是要告訴我們這個Counter組件的目的。我們是要用它來展示一個帖子的數量呢?還是要顯示類似於facebook導航條上的消息數?

TweetList Component

這裏我們將建立一個帖子列表組件(TweetList)和一個相對應的帖子計數器組件(Counter),來說明我們怎樣及什麼時候來使用props & state。以此說明當Counter展示一個帖子數量時該怎麼使用。當我們傳入1時,顯示的是one,以此類推。首先我們嘗試在沒有props & state的幫助下來建立組件。

/* Snippet 3 */
var _tweet = {
  author: 'John',
  content: 'My first ReactJS tweet',
  impressions: 5
};

var Counter = React.createClass({
  render: function() {
    var wordsDataSet = ['zero', 'one', 'two', 'three', 'four', 'five'];
    var word = wordsDataSet[_tweet.impressions];

    return (
      <span>Impressions: {word}</span>
    );
  }
});

var TweetItem = React.createClass({
  render: function() {
    return (
    <div>
      <div>@{_tweet.author} says:</div>
      <div>{_tweet.content}</div>
      <Counter />
    </div>
    );
  }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

這一小段代碼非常易於理解,當然你也可能發現了一下可怕的缺陷。最糟糕的是,這個Counter組件只能用於一個帖子,而不能複用。爲了改變這一特徵,我們來看看我們需要爲Counter組件定義哪些屬性: 
1)不能將count定義死。(它應該是可以被用於tweet impressions, retweets, number of followers等等) 
2)我們可以不用關心是怎樣獲取的數據。(它可以來自於JS對象、HTML屬性、一個ajax回調等等) 
3)它應該是很容易被任何層級的複雜組件調用。

Flux框架要求我們,組件層次中的最外層的那個組件(controller-view 組件)應該處理一個state,然後通過props傳遞給它的子組件們。這樣無論何時何地要改變view將變得容易,無論何時只要controller-view的state發生變化,它都將觸發它的render函數,子組件們將會獲取新的props,然後逐級影響並更新其他組件。通過這些特性以及上面我們總結的Counter組件屬性,Counter組件不應定義任何state,而是隻能依靠props。所以我們修改了我們的代碼:

/* Snippet 4 */
// ...
var Counter = React.createClass({
  render: function() {
    var wordsDataSet = ['zero', 'one', 'two', 'three', 'four', 'five'];
    var word = wordsDataSet[this.props.count];

    return (
      <span>{word}</span>
    );
  }
});

var TweetItem = React.createClass({
  getInitialState: function() {
    return {tweet: _tweet};
  },

  render: function() {
    return (
      <div>
        <div>@{_tweet.author} says:</div>
        <div>{_tweet.content}</div>
        Impressions:
        <Counter count={_tweet.impressions} />
      </div>
    );
  }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

現在我們的Counter變得更好了。當TweetItem是最外層組件(controller-view)時,那麼它就應該有一個state。但是我們一直還有一個問題沒有解決,TweetItem不太可能單獨使用,我們也要將它放在tweet列表中,所以它也不應該擁有state。接下來,讓我們來修改TweetItem,並且創建TweetList組件。

/* Snippet 5 */
// ...

var TweetItem = React.createClass({
  render: function() {
    return (
      <div>
        <div>@{this.props.tweet.author} says:</div>
        <div>{this.props.tweet.content}</div>
        Impressions:
        <Counter count={this.props.tweet.impressions} />
      </div>
    );
  }
});

var TweetList = React.createClass({
  getInitialState: function() {
    return {tweets: []};
  },

  componentDidMount: function() {
    var self = this;
    $.get('/latest-tweets.json', function(_tweets) {
      self.setState({tweets: _tweets});
    });
  },

  render: function() {
    var listItems = this.state.tweets.map(function(tweet, index) {
      return (
        <li key={index}>
          <TweetItem tweet={tweet} />
        </li>
      );
    });

    return (
      <ul>
        {listItems}
      </ul>
    );
  }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

現在代碼看起來更好了。最外層組件TweetList擁有了該應用的state。在componentDidMount函數中我們通過jQuery ajax來獲取最後一個tweet,並且更新了其state。

提示:儘管講ajax代碼寫在TweetList組件中沒有任何問題,但是最好的做法還是將其邏輯分離。比如寫在stores中。這樣看起來更簡潔明瞭。

UnreadMessagesCount Component

還記得我開始時候說過,不同版本的Counter組件的正確還是錯誤主要取決於其使用它的上下文環境嗎?之前我們創建了一個只依賴於props的Counter組件,接下來,我們將創建一個依賴於state的Counter組件。 
想象一下我們使用messages來代替tweets來工作,而且我們應該有一個MessageList組件。我們想讓它最大限度的像TweetList組件一樣渲染,只不過沒有impressions數量。我們來一起定義這個組件的特徵: 
1)我們只關心unread messages的數量。在整個APP中我們只存儲了一個數字。 
2)只要unread messages一創建,它將自動更新。 
3)無論在哪裏它應該都可以被獨立調用。類似於Facebook頁眉中可以被使用。即我們的MessageList和Counter組件需要保持彼此分離。 
我們的HTML文件裏的代碼應該看起來像這樣:

<body>
  <header>
      <span id="counter-mount-node"></span>
  </header>
  <main>
      <span id="message-list-mount-node"></span>
  </main>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

你是否已經看出來了,Counter組件自己就是一個controller-view。而且我們也不需要對它進行重用,它的代碼簡單明瞭。Bat-Signal規定我們只能使用state來實現。

var UnreadMessagesCounter = React.createClass({
  getInitialState: function() {
    return {count: 0};
  },

  componentDidMount: function() {
    messageStore.addChangeListener(this.onMessagesChanged);
  },

  onMessagesChanged: function() {
      var count = messageStore.getUnreadMessagesCount();
      this.setState({count: count});
  },

  render: function() {
    return (
      <span>You have {this.state.count} unread messages</span>
    );
  }
});

var mountNode = document.getElementById("counter-mount-node");
React.render(<UnreadMessagesCounter />, mountNode);

$(document).ready(function() {
    messageStore.loadMessagesAndEmitChange();
    setInterval(messageStore.loadMessagesAndEmitChange, 5000);
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

希望這些代碼比較容易理解。這個組件非常的直接。我們沒有使用邏輯代碼,因爲還沒有messageStore。我們來看看這個相應的store是怎樣的: 
1)它應該只有一個通道來檢索信息列表; 
2)讓組件訂閱任何更新時事件,因此它們可以得到最後一個list。這裏可以通過註冊一個addChangeListener的回調方法來加以實現; 
3)loadMessagesAndEmitChange方法在頁面加載後,每個5秒被調用一次。

如果MessageList可以通過messageStore來監聽其變化。我們就不用像TweetList那樣通過MessageList組件代碼來獲取更新。這裏我們省略了store代碼。

結論

到目前爲止,我們學會了關於reactjs的使用方法。我會讓它更有趣的展示一個Twitter克隆在後面的階段,或者是更令人興奮的東西,我會做出決定在一天或兩天。即將到來的文章將介紹其他主題,將最後的基礎教程。如果你正在尋找問題運行片段在評論中讓我知道,我很樂意幫助你。


轉載於:前端小野



發佈了10 篇原創文章 · 獲贊 9 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章