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);

非常容易,对不对?但是,我们现在使用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);

哪一个是恶魔?一旦我们程序变大,哪一个将是不可见的错误?实际上,两种用法都有可能正确,也有可能错误,这取决于它们使用时的上下文环境。接下来,我将尝试使用最好的方法来解释这是为什么。

我相信这些困惑的出现都是因为理解过于简单造成的。我们不能责备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>
    );
  }
});

这一小段代码非常易于理解,当然你也可能发现了一下可怕的缺陷。最糟糕的是,这个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>
    );
  }
});

现在我们的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>
    );
  }
});

现在代码看起来更好了。最外层组件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>

你是否已经看出来了,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);
});

希望这些代码比较容易理解。这个组件非常的直接。我们没有使用逻辑代码,因为还没有messageStore。我们来看看这个相应的store是怎样的:
1)它应该只有一个通道来检索信息列表;
2)让组件订阅任何更新时事件,因此它们可以得到最后一个list。这里可以通过注册一个addChangeListener的回调方法来加以实现;
3)loadMessagesAndEmitChange方法在页面加载后,每个5秒被调用一次。

如果MessageList可以通过messageStore来监听其变化。我们就不用像TweetList那样通过MessageList组件代码来获取更新。这里我们省略了store代码。

结论

到目前为止,我们学会了关于reactjs的使用方法。我会让它更有趣的展示一个Twitter克隆在后面的阶段,或者是更令人兴奋的东西,我会做出决定在一天或两天。即将到来的文章将介绍其他主题,将最后的基础教程。如果你正在寻找问题运行片段在评论中让我知道,我很乐意帮助你。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值