问题
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