问题描述
我正在尝试在React和Redux中实现容器组件 ,我不确定应该对生命周期方法负责什么;容器或演示组件.有人可能会争辩说,生命周期方法在控制DOM更新时是呈现性的,但是从这一方面来说,它们是否也具有行为特征?
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?
此外,到目前为止,我已经看到的所有容器组件实现都利用 react-redux
绑定,就像我自己的绑定一样.即使我将关注点清楚地分开,对于行为组件,是否应该从React.Component
继承?
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?
例如,我正在使用的应用程序具有Tab
表示组件,并带有shouldComponentUpdate
方法:
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.
推荐答案
数据应控制在尽可能靠近树的根的位置.这样做可以提供一些简单的优化,因为您只传递了需要的东西.
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.
这将涉及到您控制某些生命周期组件的位置.正如mgmcdermott所提到的,许多生命周期组件确实取决于您在做什么,但是最好的情况是拥有最简单,最笨拙的组件.
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.
在我的大多数项目中,在我的react目录中,我有components/
和views/
.我始终希望视图应尽可能多地执行繁琐的工作.话虽这么说,我已经构建了许多使用生命周期方法的组件,例如componentDidMount
,componentWillMount
,componentWillUnmount
,但是我通常会尝试隔离视图中的更新,因为其中一项工作是在我的意见,就是控制数据流.这意味着componentShouldUpdate
会住在这里.就我个人而言,我认为componentShouldUpdate
纯粹是线下优化,并且仅在重新渲染期间遇到较大性能问题的情况下使用它.
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.
我不太确定我能理解您的继承自React.Component
"问题.如果您要询问是否使用纯函数,es6 class
或React.createClass
,我不知道有一个标准规则,但是保持一致是很好的.
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.
要解决您是在处理行为还是表示,行为是单击,而重画则是表示.您的行为可能很好地存在于Tab
组件中,该组件在Tabs
视图中进行了重新绘制. Tabs
视图从redux传递您的方法,将当前活动的选项卡设置为单独的Tab
组件,然后可以通过redux发送选项卡切换的行为,以便您可以进行演示componentShouldUpdate
.这有道理吗?
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?
因此,容器中的mapToDispatch
方法将具有设置活动选项卡的功能,我们称其为activateTab(idx)
,该选项卡使用基于0的选项卡索引.您的容器将其传递给您控制的包含组件,即views/Tabs
,并将该方法传递给components/Tab
. components/Tab
将具有一个onClick
方法,该方法正在侦听您的DOM元素之一,然后调用this.props.activateTab(myIndex)
(您也可以将绑定版本的ActivateTab传递到components/Tab
中,因此不必知道它是自己的索引),这会触发redux,然后将您的数据传回views/Tabs
,它可以根据redux中的数据处理componentShouldUpdate
.
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 Since this was marked as accepted, I'll blow out my code example into something usable to the average person.
顺便说一句,我不会写太多的redux,因为这可能与应用程序密切相关,但是我假设您的状态是activeTabIdx
脱离父级.
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.
这篇关于应在容器组件或表示组件中实现React生命周期方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!