派生状态的数据源的不确定性会导致各种问题,那如果每份数据有且只被一个component管理应该就能避免这些问题了。这种思路有两种实现,一种是数据完全由父组件管理,一种是数据完全由组件自己管理。下面分别讨论:
完全受控组件(fully controlled component)
组件的数据完全来自于父组件,组件自己将不需要管理state。我们新建一个完全受控版的UserInput:
class FullyControlledUserInput extends React.Component { render() { const { user, onConfirm, onChange } = this.props; return ( <div> <input value={user.name || ''} onChange={onChange} /> <button onClick={() => { onConfirm(user) }}>确定</button> </div> ) } }
App中调用FullyControlledUserInput的方法如下:
... <FullyControlledUserInput user={targetUser} onChange={(e) => { this.setState({ targetUser: { id: targetUser.id, name: e.target.value } }); }} onConfirm={this.onConfirm} /> ...
现在FullyControlledUserInput中的所有的数据都来源于父组件,由此解决数据冲突和被篡改的问题。
完全不受控组件(fully uncontrolled component)
组件的数据完全由自己管理,因此componentWillReceiveProps中的代码都可以移除,但保留传入props来设置state初始值:
class FullyUncontrolledUserInput extends React.Component { state = { user: this.props.user } onChange = (e) => { this.setState({ user: { ...this.state.user, name: e.target.value } }); } render() { const { user } = this.state; const { onConfirm } = this.props; return ( <div> <input value={user.name || ''} onChange={this.onChange} /> <button onClick={() => { onConfirm(user) }}>确定</button> </div> ) } }
当传入的props发生改变时,我们可以通过传入一个不一样的key来重新创建一个component的实例来实现页面的更新。App中调用FullyUncontrolledUserInput的方法如下::
<FullyUncontrolledUserInput user={targetUser} onConfirm={this.onConfirm} key={targetUser.id} />
大部分情况下,这是更好的解决方案。或许有人会觉得这样性能会受影响,其实性能并不会变慢多少,而且如果组件的更新逻辑过于复杂的话,还不如重新创建一个新的组件来的快。
在父组件中调用子组件的方法设置state
如果某些情况下没有合适的属性作为key,那么可以传入一个随机数或者自增的数字作为key,或者我们可以在组件中定义一个设置state的方法并通过ref暴露给父组件使用,比如我们可以在UserInput中添加:
setNewUserState = (newUser) => { this.setState({ user: newUser }); }
在App中通过ref调用这个方法:
... <UserInput user={targetUser} onConfirm={this.onConfirm} ref='userInput' /> <ul> { users.map(u => ( <li key={u.id}> {u.name} <button onClick={() => { this.setState({ targetUser: u }); this.refs.userInput.setNewUserState(u); }}> 编辑 </button> </li> )) } </ul> <button onClick={() => { this.setState({ targetUser: {} }); this.refs.userInput.setNewUserState({}); }}> 新建 </button> ...
这个方法不推荐使用,除非实在没法了。。