React中的原子SetState更新
SetState
以误导新人相信状态更新是同步的而臭名昭著。 虽然 setState
通过 协调算法 在计算状态方面非常快,但它仍然是一个异步操作,如果使用不当,可能会使管理 React 组件中的复杂状态变得非常具有挑战性。 在本文中,了解 setState's
鲜为人知的功能签名,它将保证 原子状态更新
SetState Classic™
如果你已经使用 React 一段时间了,你可能遇到过这样的情况:
/* The initial value of this.state.count = 0 */ // multiple calls this.setState(count: this.state.count + 1); this.setState(count: this.state.count + 1); console.log(this.state.count); // 1 // for-loop for (let i = 0; i < 10; i++) { this.setState({count: this.state.count + 1}); } console.log(this.state.count); // 1 // if-statement this.setState({count: this.state.count + 1}); if (this.state.count === 0) { console.log(this.state.count); // 0 }
SetState
给 React 社区的新手带来了很多困惑。 使用关键字“react setstate”在 StackOverflow 上快速搜索显示 仍然模糊 关于 setState
是异步还是同步。 Eric Elliot 甚至建议 一起避免 setState 并让 Redux 处理所有状态更新。
这种不满在一定程度上是有道理的。 在 React 官方文档中,这种传递 setState
对象字面量的经典方式很容易引入难以修复的“竞争条件”错误。 当您的组件有很多 state
需要管理时,这个问题会更加复杂。
功能集状态
还有另一种使用 setState
的方法,该方法在文档中没有非常突出地宣传。
this.setState((prevState) => { return {count: prevState.count + 1}; }) this.setState((prevState) => { return {count: prevState.count + 1}; }) this.setState((prevState) => { return {count: prevState.count + 1}; }) this.setState((prevState) => { console.log(prevState.count); // 3 return {count: prevState.count + 1}; })
如果你以前没有看过这个,你会认为我在编造这个是正确的,但我向你保证 它是真实的 。 通过传入一个函数(而不是熟悉的对象字面量),它将获取当前状态树作为第一个参数,并有机会在将控制权传递给连续的 setState
调用之前执行自己的任意计算。 换句话说,这些函数保证接收原子状态更新。
“使用函数多次调用 setState 是安全的。 更新将被排队,然后按照它们被调用的顺序执行。” – 丹·阿布拉莫夫
这种使用方式setState
为我们提供了可预测且可靠的方式来操纵 React 中的状态。 虽然它更冗长,但如果您使用大型状态树和/或复杂的逻辑来更新状态,它就变得不可或缺。
让我们探索一个真实世界的例子,以便我们可以比较这两种方法。
构建密码验证器
想象一下,您正在构建一个表单来让用户重置他们的密码。 我们需要保存几个状态; 其中大部分用于验证密码强度。
class PasswordForm extends Component { constructor(props) { super(props); this.state = { password: '', hasEnoughChars: false, hasUpperAndLowercaseChars: false, hasSpecialChars: false, isPasswordValid: false }; } render() { return ( <div> /*input*/ <input onChange={this.handleInput} type="password" value={this.state.password}/> /*visual prompts*/ <div> <span style={bgColor(this.state.hasEnoughChars)}> Minimum 8 characters </span> <span style={bgColor(this.state.hasUpperAndLowercaseChars)}> Include 1 uppercase and lowercase letter </span> <span style={bgColor(this.state.hasSpecialChars)}> Minimum 1 special character </span> </div> /*button*/ <button disabled={!this.state.isPasswordValid}> Submit </button> </div> ); } bgColor(condition) { return {backgroundColor: condition ? 'green' : 'red'}; } // Object literal & Callback handleInput(e) { this.setState({ password: e.target.value, hasEnoughChars: e.target.value.length >= 8, hasUpperAndLowercaseChars: /[a-z]/.test(e.target.value) && /[A-Z]/.test(e.target.value), hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(e.target.value) }, () => { if (this.state.hasEnoughChars && this.state.hasUpperAndLowercaseChars && this.state.hasSpecialChars) { this.setState({isPasswordValid: true}); } else { this.setState({isPasswordValid: false}); } }); } // Functions handleInput(e) { this.setState({password: e.target.value}); this.setState((prevState) => ({ hasEnoughChars: prevState.password.length >= 8, hasUpperAndLowercaseChars: /[a-z]/.test(prevState.password) && /[A-Z]/.test(prevState.password), hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(prevState.password) })); this.setState((prevState) => ({ isPasswordValid: prevState.hasEnoughChars && prevState.hasUpperAndLowercaseChars && prevState.hasSpecialChars })); } }
使用回调让我们进入通过嵌套函数管理事物顺序的心态。 虽然现在看起来还不错,但如果我们需要在 this.setState({isPasswordValid})
之后添加回调,事情很快就会变得丑陋。
使用功能,每个setState
都是为一个关注领域量身定制的。 出于这个原因,我们不需要在这些函数定义中“硬编码”状态更新的顺序,因为转换状态所需的所有相关信息都在第一个参数中提供:prevState
。
包起来
通过对 setState
使用此函数签名,您可以直接使用最新的状态树。 这种可预测性使我们能够更有效地推理问题,并自信地构建状态丰富的 React 组件。
试试这个模式,看看你是否喜欢它。 这种使用 setState
的方式有可能成为事实上的。 在 tweet 中,Dan Abramov 建议“未来 React 很可能最终会更直接地支持这种模式。”
👉查看密码表格的CodePen