我今晚一直在做一些关于使用道具设置组件初始状态的主题的研究,而且我遇到过双方争论的人。因此,我的问题有两个部分。
1)我正在做的是反模式吗?从我所知道的不是 - 根据this article如果是这样,它究竟出了什么问题呢?
2)我是否有另一种方法可以在不使用道具来设置状态的情况下重写这个逻辑?
父组件:
class App extends Component {
constructor(props){
super(props);
this.state ={
todos: []
}
}
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/todos')
.then(response => {
this.setState({ todos: response.data })
});
}
render() {
if(!this.state.todos){
return <div>Loading...</div>
}
return (
<div className="container">
<div className="row">
{
this.state.todos.map((todo, i) => {
return (
<Todo todo={todo} key={i}/>
)
})
}
</div>
</div>
);
}
}
子组件
class Todo extends Component{
constructor(props) {
super(props);
var { title, completed, userId } = this.props.todo;
this.state = { title, completed, userId }
}
changeCompletion = () => {
this.setState({completed: !this.state.completed})
}
render() {
return(
<div className="col-md-4 col-sm-6">
<div className={"card card-inverse text-center " + (this.state.completed ? 'card-success' : 'card-danger')}>
<div className="card-block">
<blockquote className="card-blockquote">
<p>{ this.state.title }</p>
</blockquote>
<button onClick={this.changeCompletion} className={"btn btn-sm " + (this.state.completed ? 'btn-danger' : 'btn-success')}>{ this.state.completed ? 'incomplete' : 'complete'} </button>
</div>
</div>
</div>
)
}
}
答案 0 :(得分:3)
<强> 1。我正在做的是反模式吗? - 可能,是
问题是您的子组件的构造函数只在多个渲染中运行一次。如果在子组件中使用setState()
重新渲染,则构造函数将不会再次运行,因此道具和状态不同步,这是文章提到的“多个事实来源”问题。
如果重新渲染父组件(通过状态更改,道具更改或this.forceUpdate()
),则子组件的构造函数将不会重新执行。也就是说,React会在内部提高性能,您可以使用console.log()
来调查组件的生命周期。
通常,如果我碰到你的情况,我会添加一个名为ComponentWillReceiveProps(nextProps)
的生命周期方法,并使用nextProps
重新启动状态。虽然它不是完美的解决方案,因为其他开发人员可能会修改组件中的道具,我们又回到了“多个事实来源”的问题。您需要告诉或教育项目中的其他开发人员,他们不允许直接或间接地修改组件中的道具,但即使如此,他们有时也会忘记。但至少添加生命周期方法确实改善了这种情况。
<强> 2。有没有其他方法可以重写这个逻辑而不使用道具设置状态?
您可以尝试这种方法:基本上,您在子组件的props中公开回调,并在父组件中执行它以触发父组件的重新呈现,而父组件又触发重新呈现所有子组件,这是我们想要什么。
请记住,当React重新渲染所有子组件时,它不会销毁然后重新创建所有子组件,因此它们的constructor
将不会重新执行。只会执行他们的生命周期方法ComponentWillReceiveProps(nextProps)
。
父组件:
<Todo todo={todo} key={i} onChangeCompletionCallback={() => {
let clonedState = Object.assign({}, this.state);
clonedState.todos[i].completed = !clonedState.todos[i].completed;
this.setState(clonedState);
}}/>
子组件
changeCompletion = () => {
this.props.onChangeCompletionCallback();
}
一个后续问题,在这个特定的例子中,因为有 正在渲染200个待办事项,我只是改变了完成状态 一次一个,这是一个手动配置的实例 shouldComponentUpdate()函数可能是1的差异 渲染与200?渲染所有200个待机本身是不是很糟糕?
让我们首先澄清render
一词。 React中有两种render
:
render
:慢 render
:(设计为)快速 现在,如果你真正的DOM render
200 todos,那肯定是坏事。但是,如果你虚拟DOM render
200 todos,这取决于。但大多数情况下,它会很快。
当您通过render
,props change
或state change
this.forceUpdate()
组件时,您正在执行虚拟DOM render
。之后,
render
。它只是
真正的DOM renders
需要什么。所以我不认为在这种情况下手动配置shouldComponentUpdate(...)
生命周期方法会有所帮助。看起来你保存了199个虚拟DOM renders
,但实际上你在浪费你的努力。
this.setState(...)
是异步的。这意味着无论何时调用this.setState(...)
,虚拟DOM render
都不会立即发生。 React将尝试在一个大的虚拟DOM renders
中批量处理多个Virtual DOM render
。因此,即使您发出199个虚拟DOM renders
,反应也足够聪明,可以将它们一起批处理,因此只会发生1个虚拟DOM render
。不必在shouldComponentUpdate(...)
中手动执行此操作。
最后,我的解释在理论上(基于文档)。如果您认真对待性能优化问题,则可能需要进行调查以获得更可靠的信息(事实和数字)。但至少,我的理论解释可以给你一些好的开始,我希望。