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

我与影子孤独终老i 提交于 2019-12-12 12:56:28

问题


I'm attempting to implement container components in React and Redux, and I'm unsure of what should take responsibility for lifecycle methods; containers or presentational components. One could argue that the lifecycle methods are presentational as they control DOM updates, but in that respect, aren't they also behavioural?

Furthermore, all of the implementations of container components that I've seen thus far utilise the react-redux bindings, as do my own. Even if I keep the concerns clearly separated, is it appropriate to inherit from React.Component in the case of a behaviour component?

For example, the app on which I'm working has a Tab presentational component, with a shouldComponentUpdate method:

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

    [...]
}

On the one hand, this seems like a presentational concern as it controls when component should re-render. On the other hand, however, this is a means of handling when the user clicks a new tab, updating the application's state via an action, thus I'd class this as behavioural.


回答1:


Data should be controlled as close to the root of the tree as possible. Doing this provides some simple optimizations, being that you're only passing what you need.

This will bubble down to where you are controlling some lifecycle components. As mgmcdermott mentioned, a lot of lifecycle components really depend on what you're doing, but the best case scenario is to have the simplest, dumbest components.

In most of my projects, in my react directory, I have components/ and views/. It is always my preference that a view should do as much of the grunt work as possible. That being said, there a a number of components that I've built that use lifecycle methods like componentDidMount, componentWillMount, componentWillUnmount, but I typically try and isolate updates in my views, since one of their jobs, in my opinion, is controlling data flow. That means componentShouldUpdate would live there. Personally, I think componentShouldUpdate is purely end-of-the-line optimization, though, and I only use it in cases where I'm having large performance issues during a re-render.

I'm not super sure I understand your "inherit from React.Component" question. If you're asking whether or not to use pure functions, es6 class, or React.createClass, I don't know that there is a standard rule, but it is good to be consistent.

To address whether or not you are dealing with a behaviour or presentation, behaviour is the click, but re-drawing is presentation. Your behaviour might be well off to exist in your Tab component, where the re-draw in your Tabs view. Tabs view passes your method from redux to set the currently active tab into your individual Tab components, and can then send the behaviour of tab switching through redux so you can do your presentation componentShouldUpdate. Does that make sense?

So your mapToDispatch method in your container will have a function to set your active tab, let's call it activateTab(idx), which takes a 0-based index of the tab. Your container passes that to the containing component that you control, which is views/Tabs, and it passes that method along to components/Tab. components/Tab will have an onClick method which is listening on one of your DOM elements, which then calls this.props.activateTab(myIndex) (you could also pass a bound version of activateTab into components/Tab so it does not have to be aware of it's own index), which triggers redux, then passes back your data into views/Tabs which can handle a componentShouldUpdate based on the data from redux.

Expanded Edit: Since this was marked as accepted, I'll blow out my code example into something usable to the average person.

As a quick aside, I'm not going to write much redux, as this can be very app dependent, but I'm assuming that you have a state with activeTabIdx hanging off the parent.

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>
     )
   }

That will mostly do the right thing. Keep in mind that this doesn't handle/care about tab content, and as a result, you may want to structure your view differently.




回答2:


I think that this is a matter of opinion. I personally like to keep my presentational components as dumb as possible. This allows me to also write most of my presentational components as stateless functions, which are being optimized more and more in React updates. This means that if I can help it, I will prevent any presentational component from having an internal state.

In the case of your example, I don't believe that it is a presentational concern because componentShouldUpdate is a pure function of the props, which should be passed whenever this component is used. Even though this component updates the application's state, I believe that because it has no internal state, it is not necessarily behavioral.

Again, I don't think there is really a right or wrong way of doing things here. It reminds me of the discussion about whether or not Redux should handle all application state. I think if you keep the idea of making presentational components as dumb (reusable) as possible, you can figure out the correct place to put lifecycle methods in any case.




回答3:


Your question is not very correct.

Simple act of using a lifecycle method doesn't define the component as a presentational or a container component.

Lifecycle methods are exactly that — the hooks for your convenience where you can do pretty much anything you want.

A container component typically does some setup that connects itself to your application's data flow in those lifecycle methods. That's what makes it a container component, not the bare fact that it uses some lifecycle methods.

Presentational components are typically dumb and stateless, therefore they typically don't need those lifecycle methods hooked into. This doesn't mean that it's always the case. A presentational component may be stateful (although this is often undesired) and a stateless component may make use of the lifecycle methods, but in a totally different fashion than a container component would. It might add some event listeners to the document or adjust the cursor position of an input in a totally stateless way.

And perhaps you're mixing up container components and stateful components. Those are different things, and while container components are stateful, stateful components don't necessarily acts as container components.



来源:https://stackoverflow.com/questions/37141980/should-react-lifecycle-methods-be-implemented-in-container-components-or-present

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!