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

IT技术 reactjs redux flux
2021-05-17 20:36:51

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

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

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

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

    [...]
}

一方面,这似乎是一个展示问题,因为它控制着组件何时应该重新渲染。然而,另一方面,这是一种处理用户单击新选项卡、通过操作更新应用程序状态的方法,因此我将其归类为行为。

3个回答

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

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

在我的大多数项目中,在我的 react 目录中,我有components/views/. 视图应该尽可能多地完成繁重的工作,这始终是我的偏好。话虽如此,我已经构建了许多使用生命周期方法(如componentDidMountcomponentWillMount、 )的组件componentWillUnmount,但我通常尝试在我的视图中隔离更新,因为在我看来,它们的工作之一是控制数据流。这意味着componentShouldUpdate会住在那里。就我个人而言,我认为componentShouldUpdate这纯粹是最终的优化,而且我只在重新渲染期间遇到较大性能问题的情况下才使用它。

我不太确定我理解你的“继承自React.Component”问题。如果你问要不要使用纯函数,es6 class,或者React.createClass,我不知道有标准规则,但保持一致是好的。

要解决您是否正在处理行为或演示,行为是点击,但重新绘制是演示。您的行为可能很好地存在于您的Tab组件中,在您的Tabs视图中重新绘制Tabsview 从 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,它可以处理一个componentShouldUpdate基于从终极版的数据。

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

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

容器/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)

视图/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>
      )
    })
  }
})

组件/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>
     )
   }

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

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

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

再说一次,我不认为这里真的有正确或错误的做事方式。这让我想起了关于Redux 是否应该处理所有应用程序状态的讨论我认为,如果您保持使展示组件尽可能愚蠢(可重用)的想法,那么您可以找出在任何情况下放置生命周期方法的正确位置。

你的问题不是很正确。

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

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

一个容器组件 通常做一些设置本身连接到您的应用程序的数据,这些生命周期方法流动。这就是使它成为容器组件的原因,而不是因为它使用了一些生命周期方法。

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

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