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 条评论
登录 后参与评论

相关文章

来自专栏杨建荣的学习笔记

关于闪回区溢出导致的数据hang(r11笔记第12天)

对于Oracle数据库的闪回区的设置,之前和一个同事和讨论过,总体来说有一些不同的意见。 首先这个闪回区是一个逻辑的概念,闪回区的大小不会严格依赖于磁盘空间的情...

34213
来自专栏偏前端工程师的驿站

Thinking in React Implemented by Reagent

973
来自专栏铭毅天下

干货 |《从Lucene到Elasticsearch全文检索实战》拆解实践

1、题记 2018年3月初,萌生了一个想法:对Elasticsearch相关的技术书籍做拆解阅读,该想法源自非计算机领域红火已久的【樊登读书会】、得到的每天听本...

1.3K6
来自专栏Danny的专栏

【软考路上】——用例图之include和extend

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

703
来自专栏Pythonista

牛掰的python与unix

  加载subprocess模块仅仅是将可以使用的代码文件加载进来。也可以创建自己的模块或文件,拱以后重复使用,这与加载subprocess模块的方法相同。IP...

562
来自专栏Golang语言社区

一个简单的游戏服务器框架_游戏开发

最近一段时间不是很忙,就写了一个自己的游戏服务器框架雏形,很多地方还不够完善,但是基本上也算是能够跑起来了。我先从上层结构说起,一直到实现细节吧,想起什么就写...

6606
来自专栏GopherCoder

『Go 语言学习专栏』-- 第十一期

1273
来自专栏张宁的专栏

【腾讯云的1001种玩法】Ubuntu 14.04 Spark单机环境搭建与初步学习

最近毕设需要学习Spark操作,预先学习了一波。也撰写个文章供各位讨论分享。安装与配置大数据这个领域是热火朝天,而Apache Spark则是一个炙手可热大数据...

6320
来自专栏FreeBuf

初窥卡巴斯基ARK读取MBR

LONG LONG LONG AGO就发现通过Hook磁盘端口驱动程序中的IRP_MJ_SCSI派遣函数方式过不了KB了,最近又遇到这个问题就想借此机会分析一下...

846
来自专栏逸鹏说道

前后端分离了,然后呢?

  前言   前后端分离已经是业界所共识的一种开发/部署模式了。所谓的前后端分离,并不是传统行业中的按部门划分,一部分人纯做前端(HTML/CSS/JavaSc...

2797

扫码关注云+社区