React 展示组件与容器组件(英译)

作者:kurtshen

译自react-js-presentational-container-components,by Krasimir Tsonev.

当我们开始使用 React 时,我们很快会开始遇到疑惑。在哪里放置数据,组件间变化如何通信或如何管理状态?问题的答案往往是与场景相关,也有时候只是跟平常使用 react 库来做的练习与实验有关。 然而,有一种广泛使用并有助于组织基于React的应用模式 —— 将组件拆分为展示(presentational)组件和(container)容器组件。

本文是 React 模式系列的一部分。检出这个仓库来了解在使用React开发应用时使用的更多技术。

让我们从一个简单的例子开始,说明问题,然后将组件拆分为容器和展示组件。 我们将使用一个 clock 组件。 它接受一个Date对象作为prop,并显示实时变化的时间。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = { time: this.props.time };
    this._update = this._updateTime.bind(this);
  }
  render() {
    var time = this._formatTime(this.state.time);
    return (
      <h1>{ time.hours } : { time.minutes } : { time.seconds }</h1>
    );
  }
  componentDidMount() {
    this._interval = setInterval(this._update, 1000);
  }
  componentWillUnmount() {
    clearInterval(this._interval);
  }
  _formatTime(time) {
    var [ hours, minutes, seconds ] = [
      time.getHours(),
      time.getMinutes(),
      time.getSeconds()
    ].map(num => num < 10 ? '0' + num : num);

    return { hours, minutes, seconds };
  }
  _updateTime() {
    this.setState({ time: new Date(this.state.time.getTime() + 1000) });
  }
};

ReactDOM.render(<Clock time={ new Date() }/>, ...);
```

在组件的构造函数中,我们将传递的time对象存储到内部状态。 通过使用setInterval,我们每秒更新状态,组件被重新渲染。 为了使它看起来像一个真正的时钟,我们使用两个辅助方法 —— _formatTime_updateTime_formatTime方法是提取小时,分钟和秒,并确保他们遵循两位数格式。_updateTime以一秒为度量来改变当前的time对象。

问题

在我们的组件这里有几件事情会发生。看起来这个组件有太多的职责。

  • 它自己改变状态。 更改组件内部的时间可能不是一个好主意,因为只有clock知道当前的值。 如果系统的另一部分依赖于此数据,则很难共用它。
  • _formatTime实际上是做两件事 —— 它从日期对象中提取所需的信息,并确保这些值始终为两位数。 这看起来没问题,但如果提取的方法不是这个组件的一部分,这将是很好的。因为Clock绑定了time对象的类型(作为一个prop)。 也就是说它需要知道关于数据形态的细节。

解决思路

那么,让我们将组件分为两个部分 - 容器和展示组件。

容器

容器知道数据,知道数据的形态以及数据从何而来。 他们知道事务如何运作的细节或者说所谓的业务逻辑。 它们接收信息并对其进行格式化,以便由展示组件简单地使用。 通常我们使用高阶组件(higher-order components)来创建容器。 它们的render方法仅包含展示组件。 在flux架构(flux architecture)的上下文中,这是绑定了stores的变化和调用action的创建者的。

下面是我们的ClockContainer

// Clock/index.js
import Clock from './Clock.jsx'; // <-- 展示组件

export default class ClockContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { time: props.time };
    this._update = this._updateTime.bind(this);
  }
  render() {
    return <Clock { ...this._extract(this.state.time) }/>;
  }
  componentDidMount() {
    this._interval = setInterval(this._update, 1000);
  }
  componentWillUnmount() {
    clearInterval(this._interval);
  }
  _extract(time) {
    return {
      hours: time.getHours(),
      minutes: time.getMinutes(),
      seconds: time.getSeconds()
    };
  }
  _updateTime() {
    this.setState({ time: new Date(this.state.time.getTime() + 1000) });
  }
};

它仍然接受time(日期对象),执行setInterval循环并了解有关数据(getHoursgetMinutesgetSeconds)的详细信息。 最终渲染到展示组件并传递小时,分钟和秒三个数字。

展示组件

展示组件是与展示的东西样子相关的。 他们有着让页面变得漂亮所需的额外的修饰。这样的组件不绑定任何东西,并且没有依赖性。 通常被实现为无状态功能组件(stateless functional components),也就是说它们没有内部状态。

在我们的例子中,展示组件只包含两位数字的检查并返回<h1>标签:

// Clock/Clock.jsx
export default function Clock(props) {
  var [ hours, minutes, seconds ] = [
    props.hours,
    props.minutes,
    props.seconds
  ].map(num => num < 10 ? '0' + num : num);

  return <h1>{ hours } : { minutes } : { seconds }</h1>;
};

好处

将组件拆分为容器和展示组件增加了组件的可重用性。 我们的Clock函数/组件可能存在于不改变时间或不使用JavaScript的Date对象的应用程序中。 这是因为它是漂亮的傀儡。 没有关于数据的细节,只有它的初始形态和它来自哪里。

关于容器的好处是它们封装逻辑并且可以将数据注入到不同的渲染器中。 通常,导出容器的代码不直接导出一个类,而是一个函数。 例如,不是使用

import Clock from './Clock.jsx';

export default class ClockContainer extends React.Component {
  render() {
    return <Clock />;
  }
}

而是我们可以导出一个接受展示组件的函数:

export default function(Component) {
  return class Container extends React.Component {
    render() {
      return <Component />;
    }
  }
}

使用这种技术我们的容器是真正灵活的渲染其结果。 如果我们要从数字时钟的展示样式转换到模拟时钟的展示样式,这将是非常有用的。

因为我们对于我们的组件必须考虑更少,使得测试也会变得容易。 容器不关心UI东西,并且通常触发逻辑的动作由回调控制。展示组件只是呈现传入的props,并且如果某处被点击/填充(数据),他们的单元测试或多或少地会检查正确的回调是否被调用。

其他资源

旁注

没有什么是一成不变的。现实组件有时有内部状态。容器可能有额外增加的部分。这里描述的概念没有严格的规则,怎么去做取决于具体的场景。

原文链接:http://ivweb.io/topic/583f21cd270eedfd10a0f5e7

相关阅读推荐

React + Redux 组件化方案,by Adamhe. @ivweb

前端开发框架简介:angular和react

容器健康检查详解

原文链接:

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏QQ会员技术团队的专栏

一起脱去小程序的外套和内衣:微信小程序架构解析

微信小程序的公测掀起了学习小程序开发的浪潮,天生跨平台,即用即走、媲美原生体验、完善的文档、高效的开发框架,小程序给开发者带来了很多惊喜。通过这篇文章和大家一起...

5.1K3
来自专栏前端架构与工程

Nodejs建站笔记-注册登录流程的简单实现

1. 使用Backbone实现前端hash路由 登录注册页面如下: ? 初步设想将注册和登录作为两个不同的url实现,但登录和注册功能的差距只有form表单...

21910
来自专栏一“技”之长

iOS生成标准的注释工具——VVDocumenter

        在程序开发中,我们免不了要写许多注释,方便帮别人也方便我们自己以后检查我们的代码。然而,写注释是一件十分浪费我们时间与精力的事,要写符合文档格式...

643
来自专栏九彩拼盘的叨叨叨

node.js 第三方模块

1233
来自专栏24K纯开源

VS编译链接时错误(Error Link2005)的解决方法

      近期参与的项目中使用了公司另外一个同事提供的一个静态库文件。该静态库文件集成了CUDA, OpenCL两个库,用于做图形加速计算,提高视频解码拼接速...

2009
来自专栏SpringBoot 核心技术

想知道分享海报图片的生成方式吗?

源码地址:https://gitee.com/hengboy/html-covert-image

272
来自专栏ytkah

微信小程序开发教程!博卡君第二弹【微信小程序项目结构以及配置】

前面我们转了博卡君通宵吐血赶稿的微信小程序开发教程,当时只更新了两章,现在接着发布第三章:微信小程序项目结构以及配置,第四章:微信小程序首页面开发,以下是微信小...

2674
来自专栏web前端教室

WEB前端架构(二)

继续想到哪说哪,, 继上一期说,定好了MVC结构之后 就准备先搞些组件出来,首先就是抽个input出来。。 目前有登录页和用户地址栏页, input至少有二...

1836
来自专栏Python爬虫与算法进阶

【Python爬虫实战】——爬取今日头条美女图片

笔者是头条的深度使用者,经常用头条完成“看片”大业。若不信的话可以试试在头条搜索街拍,返回的都是一道道靓丽的风景线。 ? 想把图片存下来,该怎么办呢?我们可以用...

1.1K9
来自专栏CSDN技术头条

【独家】饿了么前端团队快应用背后研发实践

饿了么是一年前开始参与内测尝试开发快应用的,看着快应用平台一步一步的走过来,发展的越来越好。目前来说快应用开发条件已经比较完善,本次分享,为大家介绍前端开发人员...

1423

扫码关注云+社区