React 生命周期方法应该在容器组件还是展示组件中实现?

Should React lifecycle methods be implemented in container components or presentation components?

我正在尝试在 React 和 Redux 中实现 container components,但我不确定生命周期方法应该由什么负责;容器或展示组件。有人可能会争辩说生命周期方法是表象的,因为它们控制 DOM 更新,但在这方面,它们不也是行为的吗?

此外,我迄今为止看到的所有容器组件的实现都使用了 react-redux 绑定,我自己也是如此。即使我将关注点清楚地分开,在行为组件的情况下从 React.Component 继承是否合适?

例如,我正在开发的应用程序有一个 Tab 展示组件,带有 shouldComponentUpdate 方法:

class Tabs extends Component {
    shouldComponentUpdate(nextProps) {
        const { activeTab } = this.props;
        return activeTab !== nextProps.activeTab;
    }

    [...]
}

一方面,这似乎是一个表象问题,因为它控制组件何时应该重新呈现。然而,另一方面,当用户单击新选项卡时,这是一种处理方式,通过操作更新应用程序的状态,因此我 class 这是行为。

我认为这是一个见仁见智的问题。我个人喜欢让我的展示组件尽可能地愚蠢。这让我也可以将我的大部分展示组件编写为 stateless functions,这些组件在 React 更新中得到了越来越多的优化。这意味着如果我可以帮助它,我将阻止任何表示组件具有内部状态。

就您的示例而言,我认为这不是表象问题,因为 componentShouldUpdate 是 props 的纯函数,无论何时使用此组件都应传递它。即使这个组件更新了应用程序的状态,我相信因为它没有内部状态,所以它不一定是行为的。

同样,我认为这里的做事方式没有对错之分。这让我想起了关于whether or not Redux should handle all application state的讨论。我认为,如果您保持使表示组件尽可能愚蠢(可重用)的想法,那么在任何情况下您都可以找到放置生命周期方法的正确位置。

数据应该尽可能靠近树的根控制。这样做提供了一些简单的优化,因为您只传递了您需要的东西。

这将冒泡到您控制某些生命周期组件的位置。正如 mgmcdermott 提到的,许多生命周期组件确实取决于您在做什么,但最好的情况是拥有最简单、最笨的组件。

在我的大部分项目中,在我的 react 目录中,我有 components/views/。我始终倾向于视图应该尽可能多地完成繁重的工作。话虽如此,我构建的许多组件都使用生命周期方法,例如 componentDidMountcomponentWillMountcomponentWillUnmount,但我通常会尝试在我的视图中隔离更新,因为一个在我看来,他们的大部分工作是控制数据流。这意味着 componentShouldUpdate 会住在那里。不过,就我个人而言,我认为 componentShouldUpdate 纯粹是行尾优化,我只在重新渲染期间遇到严重性能问题的情况下才使用它。

我不太确定我理解你的 "inherit from React.Component" 问题。如果你问是否使用纯函数,es6 class,或React.createClass,我不知道有没有标准规则,但保持一致是好的。

要解决您是否正在处理行为或演示,行为是点击,但重新绘制是演示。你的行为可能很好地存在于你的 Tab 组件中,在你的 Tabs 视图中重新绘制。 Tabs 视图从 redux 传递你的方法来将当前活动的选项卡设置到你的个人 Tab 组件中,然后可以通过 redux 发送选项卡切换的行为,这样你就可以进行演示 componentShouldUpdate。这有意义吗?

因此容器中的 mapToDispatch 方法将具有设置活动选项卡的功能,我们称之为 activateTab(idx),它采用基于 0 的选项卡索引。您的容器将其传递给您控制的包含组件,即 views/Tabs,并将该方法传递给 components/Tabcomponents/Tab 将有一个 onClick 方法,该方法正在侦听您的 DOM 元素之一,然后调用 this.props.activateTab(myIndex) (您也可以将 activateTab 的绑定版本传递到 components/Tab 所以它不必知道它自己的索引),它会触发 redux,然后将你的数据传回 views/Tabs,它可以根据来自 redux 的数据处理 componentShouldUpdate

扩展编辑:由于这被标记为已接受,我将把我的代码示例扩展成普通人可以使用的东西。

顺便说一句,我不打算写太多 redux,因为这可能非常依赖于应用程序,但我假设你有一个 activeTabIdx 挂起父级的状态。

containers/TabExample.jsx

import { connect } from 'react-redux'
import Tabs from 'views/Tabs.js'

const mapStateToProps = function (state) {
  return {
    activeTabIdx: state.activeTabIdx
    // And whatever else you have...
  }
}

const mapDispatchToProps = function (dispatch) {
  return {
    activateTab: function (idx) {
      dispatch({
        action: 'ACTIVATE_TAB_IDX',
        idx: idx
      }) // You probably want this in a separate actions/tabs.js file...
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Tabs)

views/Tabs.js

import React, { createClass } from 'react'
import Tab from 'components/Tab.js'

const { number, func } = React.PropTypes

// Alternatively, you can use es6 classes...
export default createClass({
  propTypes: {
    activeTabIdx: number.isRequired,
    activateTab: func.isRequired
  },

  render () {
    const { activeTabIdx } = this.props
    const tabs = ['Tab 1', 'Tab 2', 'Tab 3']

    return (
      <div className='view__tabs'>
        <ol className='tabs'>
          {this.renderTabLinks(tabs, activeTabIdx)}
        </ol>
      </div>
    )
  },

  renderTabLinks (tabs, activeTabIdx) {
    return tabs.map((tab, idx) => {
      return (
        <Tab
          onClick={this.props.activateTabIdx.bind(this, idx)}
          isActive={idx === activeTabIdx}
        >
          {tab}
        </Tab>
      )
    })
  }
})

components/Tab.js

import React, { createClass } from 'react'

const { func, bool } = React.PropTypes

// Alternatively, you can use es6 classes...
export default createClass({
  propTypes: {
    children: node.isRequired,
    onClick: func.isRequired,
    isActive: bool.isRequired
  },

  handleClick (e) {
    const { isActive, onClick } = this.props

    e.preventDefault()

    if (!isActive) {
      onClick()
    }
  },

  render () {
    const { children, isActive } = this.props

    const tabClass = isActive
      ? 'tabs__items tabs__items--active'
      : 'tabs__items'

    return (
      <li className={tabClass}>
        <a className='tabs__item-link' onClick={this.handleClick}>
          {children}
        </a>
      </li>
     )
   }

这将主要做正确的事情。请记住,这与选项卡内容无关 handle/care,因此,您可能希望以不同方式构建视图。

你的问题不太正确。

使用生命周期方法的简单操作不会将组件定义为 展示性容器 组件。

生命周期方法就是这样 — 为您提供方便的挂钩,您可以在其中做几乎任何您想做的事情。

容器组件通常会进行一些设置,将其自身连接到那些生命周期方法中的应用程序数据流。这就是使它成为容器组件的原因,而不是它使用 一些 生命周期方法这一事实。

展示组件 通常 愚蠢且无状态,因此它们通常不需要挂钩那些生命周期方法。这并不意味着情况总是如此。展示组件可能是有状态的(尽管这通常是不希望的),而无状态组件可能会使用生命周期方法,但使用的方式与容器组件完全不同。它可能会向文档添加一些事件侦听器或以完全无状态的方式调整输入的光标位置。

也许您混淆了 container 组件和 stateful 组件。这些是不同的东西,虽然容器组件是有状态的,但有状态组件不一定充当容器组件。