• 展示型组件和容器型组件
    • 问题
    • 提取出容器型组件
    • 展示型组件
    • 好处
    • 结语

    展示型组件和容器型组件

    万事开头难。React 也不例外,作为初学者,我们也有一大堆问题。我应该将数据放在何处?如何进行变化通知?如何管理状态?这些问题的答案往往与上下文有关,而有时取决于 React 的实战经验。但是,有一种广泛使用的模式,有助于组织基于 React 的应用,那便是将组件分为展示型组件和容器型组件。

    我们先从一个简单示例开始,首先说明此示例的问题,然后将组件分成容器型组件和展示型组件。示例中使用的是 Clock 组件,它接收 Date 对象作为属性并显示实时时间。

    1. class Clock extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = { time: this.props.time };
    5. this._update = this._updateTime.bind(this);
    6. }
    7. render() {
    8. const time = this._formatTime(this.state.time);
    9. return (
    10. <h1>
    11. { time.hours } : { time.minutes } : { time.seconds }
    12. </h1>
    13. );
    14. }
    15. componentDidMount() {
    16. this._interval = setInterval(this._update, 1000);
    17. }
    18. componentWillUnmount() {
    19. clearInterval(this._interval);
    20. }
    21. _formatTime(time) {
    22. var [ hours, minutes, seconds ] = [
    23. time.getHours(),
    24. time.getMinutes(),
    25. time.getSeconds()
    26. ].map(num => num < 10 ? '0' + num : num);
    27. return { hours, minutes, seconds };
    28. }
    29. _updateTime() {
    30. this.setState({
    31. time: new Date(this.state.time.getTime() + 1000)
    32. });
    33. }
    34. };
    35. ReactDOM.render(<Clock time={ new Date() }/>, ...);

    在组件的构造函数中,我们初始化了组件的状态,这里只保存了当前时间。通过使用 setInterval ,我们每秒更新一次状态,然后组件会重新渲染。要想看起来像个真正的时钟,我们还使用了两个辅助函数: _formatTime_updateTime_formatTime 用来提取时分秒并确保它们是两位数的形式。_updateTime 用来将 time 对象设置为当前时间加一秒。

    问题

    这个组件中做了好几件事,它似乎承担了太多的职责。

    • 它通过自身来修改状态。在组件中更改时间可能不是一个好主意,因为只有 Clock 组件知道当前时间。如果系统中的其他部分也需要此数据,那么将很难进行共享。
    • _formatTime 实际上做了两件事,它从时间对象中提取出所需信息,并确保这些值永远以两位数字的形式进行展示。这没什么问题,但如果提取操作不是函数的一部分那就更好了,因为函数绑定了 time 对象的类型。即此函数既要知道数据结构,同时又要对数据进行可视化处理。

    提取出容器型组件

    容器型组件知道数据及其结构,以及数据的来源。它们知道是如何运转的,或所谓的业务逻辑。它们接收信息并对其进行处理,以方便展示型组件使用。通常,我们使用 高阶组件 来创建容器型组件,因为它们为我们的自定义逻辑提供了缓冲区。

    下面是 ClockContainer 的代码:

    1. // Clock/index.js
    2. import Clock from './Clock.jsx'; // <-- 展示型组件
    3. export default class ClockContainer extends React.Component {
    4. constructor(props) {
    5. super(props);
    6. this.state = { time: props.time };
    7. this._update = this._updateTime.bind(this);
    8. }
    9. render() {
    10. return <Clock { ...this._extract(this.state.time) }/>;
    11. }
    12. componentDidMount() {
    13. this._interval = setInterval(this._update, 1000);
    14. }
    15. componentWillUnmount() {
    16. clearInterval(this._interval);
    17. }
    18. _extract(time) {
    19. return {
    20. hours: time.getHours(),
    21. minutes: time.getMinutes(),
    22. seconds: time.getSeconds()
    23. };
    24. }
    25. _updateTime() {
    26. this.setState({
    27. time: new Date(this.state.time.getTime() + 1000)
    28. });
    29. }
    30. };

    它接收 time (date 对象) 属性,使用 setInterval 循环并了解数据 (getHoursgetMinutesgetSeconds) 的详情。最后渲染展示型组件并传入时分秒三个数字。这里没有任何展示相关的内容。只有业务逻辑

    展示型组件

    展示型组件只涉及组件的外在展现形式。它们会有附加的 HTML 标记来使得页面更加漂亮。这种组件没有任何绑定及依赖。通常都是实现成 无状态组件,它们没有内部状态。

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

    1. // Clock/Clock.jsx
    2. export default function Clock(props) {
    3. var [ hours, minutes, seconds ] = [
    4. props.hours,
    5. props.minutes,
    6. props.seconds
    7. ].map(num => num < 10 ? '0' + num : num);
    8. return <h1>{ hours } : { minutes } : { seconds }</h1>;
    9. };

    好处

    将组件分成容器型组件和展示型组件可以增加组件的可复用性。不改变时间或不使用 JavaScript Date 对象的应用中,都可以使用 Clock 函数/组件。原因是它相当单纯,不需要对所需数据的详情有任何了解。

    容器型组件封装了逻辑,它们可以搭配不同的展示型组件使用,因为它们不参与任何展示相关的工作。我们上面所采用的方法是一个很好的示例,它阐明了容器型组件是如何不关心展示部分的内容的。我们可以很容易地从数字时钟切换到模拟时钟,唯一的变化就是替换 render 方法中的 <Clock> 组件。

    测试也将变得更容易,因为组件承担的职责更少。容器型组件不关心 UI 。展示型组件只是纯粹地负责展示,它可以很好地预测出渲染后的 HTML 标记。

    结语

    容器型和展示型的并非是新概念,但是它真的非常适合 React 。它使得应用具有更好的结构,易于管理和扩展。