组件划分

组件划分

容器型组件和展示型组件

最简单的组件划分方式,就是将其二分,可以称为容器型组件和展示型组件,但是也有其他名字,比如臃肿型组件和简单型组件,智能型组件和傻瓜型组件,有状态组件和纯组件,封装型组件和元组件等等。它们不完全相同,但是在核心观点上是相似的。

容器型组件与展示型组件对比

这种方式的优势在于:

  • 关注点分离。通过用这种方式开发组件,你可以更好的理解你的 app 和 UI。
  • 更好的复用性。你可以在不同的数据源中使用相同的展示型组件,也可以把它们放进不同容器型组件中更进一步的进行复用。
  • 展示型组件是你的 App 必不可少的"调色板",你可以把它们放在一个独立的页面中,让设计师随意拖拽它们的变量而不改变应用的逻辑。在这个页面上进行页面快照回归测试。
  • 这种方法逼你去把用于布局的组件抽出来,例如 Sidebar,Page,ContextMenu。然后通过子组件的方式引入而不是在各个容器型组件中复制粘贴已有的样式和布局。

为了与之前的概念做比较,这是一些相关但不同的二分法:

  • 有状态和无状态:有些组件使用 React 的 setState()方法,有些不用。容器型组件往往是有状态的而展示型组件往往是无状态的,这并不是一条铁律。展示型组件也可以是有状态的,容器型组件也可以是无状态的

  • 类和函数:从 React0.14 开始,组件既可以声明为类也可以声明为函数。函数式组件可以定义的更简单但是也缺少一些只能在类组件中使用的特写。有些限制可能未来会消除,但是在当下仍然是存在的。由于函数式组件更加易于理解,所以我建议你尽量的使用它。除非你需要 state,生命周期函数,或者性能优化,这些特性只有在类组件中才可以使用。

  • 纯和非纯:如果一个组件在输入相同 props 的情况下总是输出相同的结果,那我们称这个组件为 pure component。pure component 既可以声明为类组件也可以声明为函数式组件,即可以是有状态的也可以是无状态的。另一个重要的方面是,pure component 不依赖 props 和 state 的深层比对,所以可以在 shouldComponentUpdate 方法中进行浅比较优化性能,但是在未来可能有很多变化。

展示型组件

  • 关心数据的展示方式。
  • 内部可能包含展示型组件和容器型组件,并且通常存在其他 DOM 元素及其样式。
  • 允许通过 this.props.children 控制组件。
  • 不依赖 app 中的其它文件,像 Flux 的 actions 或 stores。
  • 不关心数据是如何加载和变化的。
  • 仅通过 props 接收数据和回调函数。
  • 几乎不用组件内的 state(如果用到的话,也仅仅是维护 UI 状态而不是数据状态)。
  • 除非需要用到 state,生命周期函数或性能优化,通常写成函数式组件。
  • 例如:Page,Sidebar,Story,UserInfo,List。
const Footer = props => {
  return (
    <div>
      <ul>
        <li>Footer Information</li>
      </ul>
    </div>
  );
};

容器型组件

其中容器组件往往会包含内部状态,其具备以下特点:

  • 关心数据的运作方式。
  • 内部可能包含展示型组件和容器型组件,但是通常没有任何用于自身的 DOM 元素,除了一些用于包裹元素的 div 标签,并且不存在样式。
  • 为展示型组件和容器型组件提供数据和操作数据的方法。
  • 调用 Flux actions 并以回调函数的方式给展示型组件提供 actions。
  • 通常是有状态的,并且作为数据源存在。
  • 通常由高阶函数生成例如 React Redux 的 connect(),Realy 的 createContainer,或者 Flux Utils 的 Container.create(),而不是手写的。
  • 例如:UserPage,FollowersSidebar,StoryContainer,FollowedUserList。
class SmartComponent01 extends Component {
  manageSomeData() {
    /* ... */
  }
  makeSomeCalculations() {
    /* ... */
  }
  handleSomeEvent = event => {
    /* ... */
  };

  render() {
    return (
      <div>
          <DumpComponent01 data={/*...*/} />
          <DumpComponent02 func={/*...*/} /> {" "}
      </div>
    );
  }
}

案例分析:TodoList 的拆分

在使用 React 中,你是否会出现过一个文件的代码很多,既存在应用数据的读取和处理,又存在数据的显示,而且每个组件还不能复用。首先我们来看一个容器组件和展示组件一起的例子吧。

class TodoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: []
    };
    this.fetchData = this.fetchData.bind(this);
  }
  componentDidMount() {
    this.fetchData();
  }
  fetchData() {
    fetch("/api/todos").then(data => {
      this.setState({
        todos: data
      });
    });
  }
  render() {
    const { todos } = this.state;
    return (
      <div>
        <ul>
          {todos.map((item, index) => {
            return <li key={item.id}>{item.name}</li>;
          })}
        </ul>
      </div>
    );
  }
}

大家可以看到这个例子是没有办法复用的,因为数据的请求和数据的展示都在一个组件进行,要实现组件的复用,我们就需要将展示组件和容器组件分离出来。具体代码如下:

// 展示组件
class TodoList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const { todos } = this.props;
    return (
      <div>
        <ul>
          {todos.map((item, index) => {
            return <li key={item.id}>{item.name}</li>;
          })}
        </ul>
      </div>
    );
  }
}
// 容器组件
class TodoListContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: []
    };
    this.fetchData = this.fetchData.bind(this);
  }
  componentDidMount() {
    this.fetchData();
  }
  fetchData() {
    fetch("/api/todos").then(data => {
      this.setState({
        todos: data
      });
    });
  }
  render() {
    return (
      <div>
        <TodoList todos={this.state.todos} />
      </div>
    );
  }
}
上一页