反应组件是否应该知道其父组件的状态
Should a react component have the knowledge of the state of its parent component
所以我写了这个 table 行组件,它带有复选框作为其他 table 组件使用的第一列。以下是组件的简化版本
Table行:
import React, { Component, PropTypes } from 'react'
import { Checkbox } from 'react-bootstrap'
export default class TableRow extends Component {
constructor(props) {
super(props)
this.handleClickCheckbox = this.handleClickCheckbox.bind(this)
}
handleClickCheckbox() {
this.props.onClickCheckbox(this.props.entity.id)
}
render() {
const { checked, children } = this.props
return (
<tr>
<Checkbox
checked={checked}
onChange={this.handleClickCheckbox}
> </Checkbox>
{children}
</tr>
}
}
TableRow.propTypes = {
entity: PropTypes.shape({
id: PropTypes.number
}),
checked: PropTypes.bool,
onClickCheckbox: PropTypes.func
}
Table:
import React, { Component, PropTypes } from 'react'
import { Table } from 'react-bootstrap'
import TableRow from './TableRow'
class SampleTable extends Component {
constructor(props) {
super(props)
this.state = {
checkedEntitiesIds: []
}
this.handleChangeChecked = this.handleChangeChecked.bind(this)
}
handleChangeChecked(id) {
const { checkedEntitiesIds } = this.state
const indexOfEntity = checkedEntitiesIds.indexOf(id)
// uncheck it if it's already checked, vice versa
this.setState({
checkedEntitiesIds : indexOfEntity > -1 ?
checkedEntitiesIds.filter((id, index) => index != indexOfEntity) :
[...checkedEntitiesIds, id]
})
}
render() {
const { entities } = this.props // can come from either parent component or subcribe to redux state
return (
<Table>
<thead>
<tr>
<th>placeholder for checkbox column</th>
<th>dummy header 1</th>
<th>dummy header 2</th>
</tr>
</thead>
<tbody>
{entities.map(entity =>
<TableRow
key={entity.id}
entity={entity}
checked={this.state.checkedIds.indexOf(action.id) > -1}
onClickCheckbox={this.handleChangeChecked}
>
<td>dummy table cell 1</td>
<td>dummy table cell 2</td>
</TableRow>
)}
</tbody>
</Table>
)
}
}
我将复选框设置为受控输入,因为选中的道具的值可能来自数据库。每个 table 行都有一个可以唯一标识该行的实体道具。我在 table 组件中维护 checkedEntitiesIds 状态,而不是在 TableRow 组件中维护检查状态,因为我觉得它为我提供了最大的灵活性。
我的一位同事不喜欢他必须在每个 table 组件中维护 checkedEntitiesIds 状态以及 handleChangeChecked 函数这一事实。他提出了将 checkedEntitiesIds 状态添加到 TableRow 并将 handleChangeChecked 的函数体移动到 TableRow 的建议。它的工作方式是每次复选框获得 clicked/changed 时,Table 行中的 checkedEntitiesIds 状态将首先更新,然后传递回父级 table,因此父组件的 checkedEntitiesIds 状态得到也更新了这样他就不需要一遍又一遍地编写 check/uncheck 逻辑。
有几件事困扰着我做出这样的改变:
- 让 TableRow 知道 checkedEntitiesIds,这应该是 Table 的状态,这太违反直觉了。
- 重复的真实来源。
- Table行将有一个状态。如果我在这里错了,请纠正我。我的理解是那些低阶哑组件应该很少有自己的状态。
- 在某些扫描仪中,复选框单击事件不一定会立即导致状态更改。例如,当单击复选框时,首先提示确认模式,并仅在选择YES时才更新状态。在 TableRow 中使用状态不提供这种灵活性。
我几乎可以肯定他的提议有些反模式。我只是不确定我放置组件的方式是否有意义以及我列出的那些点是否有效。我在这里是否缺少任何重要的东西来说服他?有什么方法可以在可重用性方面改进我的代码。任何见解将不胜感激。
您提出的所有观点似乎都是合理的。 Smart vs Dumb Component 模式似乎是每个人都在采用的模式(并且有充分的理由,如果没有这种明确的区分,代码会变得非常混乱)。
您同事的建议从根本上是错误的另一件事是,数据从 child 向上流动到 parent。 React 的核心思想之一是单向数据流。传播到 parent 的 child 中的更改应该 通过存储 (或任何数据层)以维持此单向 属性。
让 child 通知 parent 更新,它增加了一层复杂性(如果它触发 child 的更新会怎样,这会返回到 parent 并永远循环?)React 一开始就试图摆脱这个问题(正是这个问题让人们怀疑 Angular 应用程序的可扩展性,使它们在更大的范围内变得过于复杂)。
所以我写了这个 table 行组件,它带有复选框作为其他 table 组件使用的第一列。以下是组件的简化版本
Table行:
import React, { Component, PropTypes } from 'react'
import { Checkbox } from 'react-bootstrap'
export default class TableRow extends Component {
constructor(props) {
super(props)
this.handleClickCheckbox = this.handleClickCheckbox.bind(this)
}
handleClickCheckbox() {
this.props.onClickCheckbox(this.props.entity.id)
}
render() {
const { checked, children } = this.props
return (
<tr>
<Checkbox
checked={checked}
onChange={this.handleClickCheckbox}
> </Checkbox>
{children}
</tr>
}
}
TableRow.propTypes = {
entity: PropTypes.shape({
id: PropTypes.number
}),
checked: PropTypes.bool,
onClickCheckbox: PropTypes.func
}
Table:
import React, { Component, PropTypes } from 'react'
import { Table } from 'react-bootstrap'
import TableRow from './TableRow'
class SampleTable extends Component {
constructor(props) {
super(props)
this.state = {
checkedEntitiesIds: []
}
this.handleChangeChecked = this.handleChangeChecked.bind(this)
}
handleChangeChecked(id) {
const { checkedEntitiesIds } = this.state
const indexOfEntity = checkedEntitiesIds.indexOf(id)
// uncheck it if it's already checked, vice versa
this.setState({
checkedEntitiesIds : indexOfEntity > -1 ?
checkedEntitiesIds.filter((id, index) => index != indexOfEntity) :
[...checkedEntitiesIds, id]
})
}
render() {
const { entities } = this.props // can come from either parent component or subcribe to redux state
return (
<Table>
<thead>
<tr>
<th>placeholder for checkbox column</th>
<th>dummy header 1</th>
<th>dummy header 2</th>
</tr>
</thead>
<tbody>
{entities.map(entity =>
<TableRow
key={entity.id}
entity={entity}
checked={this.state.checkedIds.indexOf(action.id) > -1}
onClickCheckbox={this.handleChangeChecked}
>
<td>dummy table cell 1</td>
<td>dummy table cell 2</td>
</TableRow>
)}
</tbody>
</Table>
)
}
}
我将复选框设置为受控输入,因为选中的道具的值可能来自数据库。每个 table 行都有一个可以唯一标识该行的实体道具。我在 table 组件中维护 checkedEntitiesIds 状态,而不是在 TableRow 组件中维护检查状态,因为我觉得它为我提供了最大的灵活性。
我的一位同事不喜欢他必须在每个 table 组件中维护 checkedEntitiesIds 状态以及 handleChangeChecked 函数这一事实。他提出了将 checkedEntitiesIds 状态添加到 TableRow 并将 handleChangeChecked 的函数体移动到 TableRow 的建议。它的工作方式是每次复选框获得 clicked/changed 时,Table 行中的 checkedEntitiesIds 状态将首先更新,然后传递回父级 table,因此父组件的 checkedEntitiesIds 状态得到也更新了这样他就不需要一遍又一遍地编写 check/uncheck 逻辑。
有几件事困扰着我做出这样的改变:
- 让 TableRow 知道 checkedEntitiesIds,这应该是 Table 的状态,这太违反直觉了。
- 重复的真实来源。
- Table行将有一个状态。如果我在这里错了,请纠正我。我的理解是那些低阶哑组件应该很少有自己的状态。
- 在某些扫描仪中,复选框单击事件不一定会立即导致状态更改。例如,当单击复选框时,首先提示确认模式,并仅在选择YES时才更新状态。在 TableRow 中使用状态不提供这种灵活性。
我几乎可以肯定他的提议有些反模式。我只是不确定我放置组件的方式是否有意义以及我列出的那些点是否有效。我在这里是否缺少任何重要的东西来说服他?有什么方法可以在可重用性方面改进我的代码。任何见解将不胜感激。
您提出的所有观点似乎都是合理的。 Smart vs Dumb Component 模式似乎是每个人都在采用的模式(并且有充分的理由,如果没有这种明确的区分,代码会变得非常混乱)。
您同事的建议从根本上是错误的另一件事是,数据从 child 向上流动到 parent。 React 的核心思想之一是单向数据流。传播到 parent 的 child 中的更改应该 通过存储 (或任何数据层)以维持此单向 属性。
让 child 通知 parent 更新,它增加了一层复杂性(如果它触发 child 的更新会怎样,这会返回到 parent 并永远循环?)React 一开始就试图摆脱这个问题(正是这个问题让人们怀疑 Angular 应用程序的可扩展性,使它们在更大的范围内变得过于复杂)。