本文译自Container Components

React的许多设计模式都给我的代码带来了深远的影响,而容器组件模式就是其中之一。

在React.js大会上,Jason Bonta介绍了他们在Facebook是如何构建高性能组件的,其中包含了这段关于容器组件的精髓.

其思想很简单:

容器负责获取数据,然后渲染到相应的子组件上。完毕。

“相应”的意思是同名的组件,例如:

StockWidgetContainer => StockWidget
TagCloudContainer => TagCloud
PartyPooperListContainer => PartyPooperList

明白了吧。

为什么要用容器组件?

假设你有个组件负责显示评论。如果没有容器组件,那就得把所有代码都写到一起:

// CommentList.js

class CommentList extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }

  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  
  render() {
    return <ul> {this.state.comments.map(renderComment)} </ul>;
  }
  
  renderComment({body, author}) {
    return <li>{body}{author}</li>;
  }
}

该组件要负责获取数据,还得负责显示数据。这样其实没什么错误,但却丧失了React的几点好处。

可复用性

CommentList只能在环境完全一致的时候才能复用。

数据结构

组件应该声明它所需要的数据,PropTypes就是用来干这个的。

但是,尽管上面的组件知道自己需要什么数据结构,但却没办法声明这种需求。因此,服务器端的json结构发生变化,该组件就会悄无声息地停止工作。

用容器重新写一遍

首先,将所有获取数据的操作都写进容器组件

// CommentListContainer.js

class CommentListContainer extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}

然后改一下CommentList,接受comments属性。

// CommentList.js

class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }
  
  render() { 
    return <ul> {this.props.comments.map(renderComment)} </ul>;
  }
 
  renderComment({body, author}) {
    return <li>{body}{author}</li>;
  }
}

这样做有什么好处?

实际上好处多多:

  • 数据获取渲染操作完全分离
  • CommentList组件可复用
  • CommentList可以设置PropTypes,这样出错时会报告错误

我很喜欢容器组件。它深深地影响了我的React代码,让我的组件更易读。试试看,并且推荐看一下Jason的演讲。很赞的!