在更新树状结构中的嵌套组件时遇到一些麻烦。我创建了以下最小示例来说明问题:Codesandbox.io
出于完整性考虑,这是要嵌套的组件:
class Node extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: props.selected
};
this.toggleSelected = this.toggleSelected.bind(this);
}
toggleSelected() {
this.setState({
selected: !this.state.selected
});
}
render() {
return (
<>
<div onClick={this.toggleSelected}>
Selected: {this.state.selected ? "T" : "F"}, Depth: {this.props.depth}
</div>
{this.props.depth < 5 && (
<Node selected={this.state.selected} depth={this.props.depth + 1} />
)}
</>
);
}
}
在单击时,树中的节点应该是可选的,我也希望(递归)在所有子级中切换选定状态。我以为可以通过将this.state.selected
通过道具传递给一个或多个孩子来做到这一点,不幸的是,这似乎不起作用。
子级被重新渲染,但是使用其旧状态(可理解的是,因为它们没有通过构造函数重新初始化)。处理该问题的正确方法是什么?
我还尝试将key
道具传递给节点,以帮助做出反应以区分元素,但无济于事。
编辑:以下是一些预期行为的示例:
考虑这棵树:
[ ] Foo
[ ] Foo A
[ ] Foo A1
[ ] Foo A2
[ ] Foo B
[ ] Foo B1
[ ] Foo B2
检查“ Foo”节点时的预期结果:
[x] Foo
[x] Foo A
[x] Foo A1
[x] Foo A2
[x] Foo B
[x] Foo B1
[x] Foo B2
检查“ Foo A”节点时的预期结果:
[ ] Foo
[x] Foo A
[x] Foo A1
[x] Foo A2
[ ] Foo B
[ ] Foo B1
[ ] Foo B2
任何朝着正确方向的提示/提示都会受到赞赏。
答案 0 :(得分:2)
您应像这样使用getDerivedStateFromProps
:
constructor(props) {
super(props);
this.state = {
selected: props.selected,
propsSelected: props.selected
};
this.toggleSelected = this.toggleSelected.bind(this);
}
static getDerivedStateFromProps(props, state) {
if (props.selected !== state.propsSelected)
return {
selected: props.selected,
propsSelected: props.selected
};
}
我们始终将prevProp存储在状态中。每当我们在状态中存储的道具和来自父级的道具发生变化时,我们都会更新受父级和组件本身控制的状态部分(selected
),并且还将道具保留在这一点,有待日后差异化。
通常,可以由父级及其自身控制的组件将涉及这种逻辑。在大多数react组件库中找到的输入组件就是一个理想的例子。
答案 1 :(得分:0)
免责声明:我没有在OP的问题中修复代码,而是向您演示如何在React应用程序中渲染树并管理其状态。
创建由无状态Node
组件组成的数据驱动树,并将状态管理留给根组件会更容易。
每个节点收到selectedId
并将其与自己的id
进行比较,以了解是否应将他们(及其子代)设置为活动状态。
他们还收到setSelected
回调,以通知父母他们被点击了。
受到来自父级的数据(它们的唯一ID,选定状态等)的驱动,它变得更加通用,并留出了很多地方来用新功能扩充树。它也解耦,更易于测试等。
// Stateless node
const Node = ({ depth, setSelected, selected, id, nodes, selectedId }) => {
const isSelected = selected || selectedId === id;
return (
<React.Fragment>
{id && (
<div style={{ marginLeft: `${depth * 15}px` }}>
<input
type="checkbox"
onChange={() => setSelected(id)}
checked={isSelected}
/>
{id}
</div>
)}
{depth < 5 &&
nodes.map(nodeProps => (
<Node
key={nodeProps.id}
{...nodeProps}
selected={isSelected}
selectedId={selectedId}
setSelected={setSelected}
depth={depth + 1}
/>
))}
</React.Fragment>
);
};
Node.defaultProps = {
selected: false,
nodes: [],
depth: 0
};
// Parent keeps the selected Id
class App extends React.Component {
state = {
selected: null
};
setSelected = id => {
this.setState(({ selected }) => ({
selected: selected !== id ? id : null
}));
};
render() {
return (
<div className="App">
<Node
nodes={this.props.tree}
selectedId={this.state.selected}
setSelected={this.setSelected}
/>
</div>
);
}
}
const treeData = [
{
id: "Foo",
nodes: [
{
id: "Foo A",
nodes: [{ id: "Foo A1" }, { id: "Foo A2" }]
},
{
id: "Foo B",
nodes: [{ id: "Foo B1" }, { id: "Foo B2" }]
}
]
}
];
ReactDOM.render(
<App tree={treeData} />,
document.getElementById("root")
);
input {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
这与重复的目标How to update parent's state in React?相同,但带有一个个性化的示例以帮助理解当前用例与背后概念之间的关系。