对于 setState() 相信伙伴们都用过,它是 React 官方推荐用来更新组件 state 的 API,可是对于 setState() 你真的了解吗?且待我慢慢详聊一番。html
语法1: setState(updater[, callback])
react
updater:函数类型,返回一个更新后的 state 中的状态对象,它会和 state 进行浅合并。性能优化
callback: 可选,回调函数。babel
语法2: setState(stateChange[, callback])
app
setState: 对象类型,会将传入的对象浅层合并到新的 state 中。dom
callback:可选,回调函数。异步
对于这两种形式,不一样的是第一个参数选择问题,能够选择一个函数返回一个新的state对象,亦能够直接选择一个对象应用于状态更新,那么啥时候选择函数类型的参数,何时选择对象类型的呢?这里能够总结两句话:函数
当前状态更新无需依赖以前的state状态时,选择对象类型参数性能
当前更新状态依赖以前的状态时,选择函数类型参数测试
example:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>setState详解</title> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="app"></div> <script type="text/babel"> class A extends React.Component { state = { count: 0 } update1 = () => { this.setState({count: this.state.count+1}) } update2 = () => { this.setState(state => ({ count: state.count+1 })) } update3 = () => { this.setState({ count: 8 }) } render () { return ( <div> <h1>{this.state.count}</h1> <button onClick={this.update1} style={{marginRight: 15}}>测试1</button><button style={{marginRight: 15}} onClick={this.update2}>测试2</button><button onClick={this.update3}>测试3</button> </div> ) } } ReactDOM.render( <A/>, document.getElementById('app') ) </script> </body> </html>
这个例子中,咱们经过点击按钮测试1或测试2来改变组件 A 的 count 状态值,由于每次修改状态都是在原先基础上加 1, 因此在setState 中适合选择函数类型参数,即 update2 写法推荐。
点击 测试3 按钮会直接将count 值修改成 固定值 8,这无需依赖上一次count状态值,因此在setState 中适合选择对象类型参数,即 update3 写法推荐。
咱们知道setState() 会触发组件render() 函数,从新渲染组件将更新后的内容显示在视图上,那么在 setState() 以后咱们立马就能获取到最新的state值吗?
这里涉及到一个 setState() 是异步更新仍是同步更新的问题?
结论:
在React相关的回调函数中setState() 是异步更新
不在React 相关的回调中setState() 是同步更新
React 相关的回调包括:组件的生命周期钩子,React 组件事件监听回调。
React不相关的回调包括常见的:setTimeout(), Promise()等。
咱们仍是能够拿以前的按钮点击实例来测试。
example:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>setState详解</title> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="app"></div> <script type="text/babel"> class A extends React.Component { state = { count: 0 } update1 = () => { this.setState({count: this.state.count+1}) console.log(this.state.count) } update2 = () => { setTimeout(() => { this.setState(state => ({ count: state.count+1 })) console.log(this.state.count) }) } update3 = () => { Promise.resolve().then(value => { this.setState({ count: 8 }) console.log(this.state.count) }) } componentWillMount () { this.setState(state => ({ count: state.count+1 })) console.log(this.state.count) } render () { console.log('render()', this.state.count) return ( <div> <h1>{this.state.count}</h1> <button onClick={this.update1} style={{marginRight: 15}}>测试1</button><button style={{marginRight: 15}} onClick={this.update2}>测试2</button><button onClick={this.update3}>测试3</button> </div> ) } } ReactDOM.render( <A/>, document.getElementById('app') ) </script> </body> </html>
咱们在 React 事件监听回调 update1 和 组件生命周期 componentWillMount() 钩子里面分别在setState()以后打印最新的 state 值,发现打印出来的仍是修改以前的state,可是页面已经更新为最新状态,看图:
采用一样的方法咱们能够观察在 update2 的setTimeout() 和 update3 的 Promise() 回调中,setState() 后打印的是最新的state值,并且这个打印会在setState() 触发组件从新render() 以后。通过测试,刚好验证了咱们的结论是正确的,在React 相关的回调中setState()是异步更新状态,在不相关的回调中 setState() 是同步更新状态。
这个问题实际上是针对当setState() 异步更新状态以后,怎么立马获取到最新的状态值,也就是上面例子咱们说的在update1() 和componentWillMount()中怎么打印出最新的state值。
答案其实很是简单,也就是咱们说到的setState()传参的第二个callback() 参数。setState() 的第二个回调会在更新状态以后,组件从新render() 以后调用,也就是这里面咱们能够获取到最新的状态值。
代码:
... update1 = () => { this.setState({count: this.state.count+1}, () => { console.log(this.state.count) }) } componentWillMount () { this.setState(state => ({ count: state.count+1 }), () => { console.log(this.state.count) }) }
这样,咱们一样能够在update1 和 componentWillMount() 中 打印出最新的state值。
这里咱们讨论的前提固然是setState() 异步更新状态时候,由于同步更新,咱们调用几回 setState(),就会触发几回 render钩子,固然也会实时分别打印出更新后的状态值。
结论:
这里分两种状况讨论:
当setState() 传对象类型参数,React会合并重复屡次的调用setState(),触发一次render。
当setState() 传函数类型参数,React会依次屡次的调用setState(),触发一次render。
能够看到,咱们屡次重复调用setState(),无论是传参是何种类型。React都只会调用一次 render,从新渲染组件。
咱们能够一样以按钮点击实例来测试咱们结论。
example:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>setState详解</title> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <div id="app"></div> <script type="text/babel"> class A extends React.Component { state = { count: 0 } update1 = () => { // this.setState({count: this.state.count+1}, () => { // console.log(this.state.count) // }) // this.setState({count: this.state.count+1}, () => { // console.log(this.state.count) // }) // this.setState({count: this.state.count+1}, () => { // console.log(this.state.count) // }) this.setState((state) => ({ count: state.count+1 }), () => { console.log(this.state.count) }) this.setState((state) => ({ count: state.count+1 }), () => { console.log(this.state.count) }) this.setState((state) => ({ count: state.count+1 }), () => { console.log(this.state.count) }) } update2 = () => { setTimeout(() => { this.setState(state => ({ count: state.count+1 })) console.log(this.state.count) this.setState(state => ({ count: state.count+1 })) console.log(this.state.count) this.setState(state => ({ count: state.count+1 })) console.log(this.state.count) }) } update3 = () => { Promise.resolve().then(value => { this.setState({ count: 8 }) console.log(this.state.count) }) } componentWillMount () { this.setState(state => ({ count: state.count+1 })) console.log(this.state.count) } render () { console.log('render()', this.state.count) return ( <div> <h1>{this.state.count}</h1> <button onClick={this.update1} style={{marginRight: 15}}>测试1</button><button style={{marginRight: 15}} onClick={this.update2}>测试2</button><button onClick={this.update3}>测试3</button> </div> ) } } ReactDOM.render( <A/>, document.getElementById('app') ) </script> </body> </html>
当点击测试按钮2,由于setState() 是同步更新状态,能够发现组件进行了屡次render调用,分别依次打印出更新后的状态值,这个很简单。
咱们点击测试按钮1,分别对传给setState()参数不一样进行了测试,发现当传参是对象类型时候,React会合并重复setState()调用,也就是只更新一次state状态,传函数类型参数时候,则分别进行了计算更新。
不管以哪一种方式传参重复调用 setState() ,React 都只会进行一次render 调用,这也是性能优化的一部分,防止屡次重复渲染带来的性能问题。
其实官网推荐咱们使用setState()时候,第一个参数传函数类型参数,由于函数参数中接收的 state 和 props 都保证为最新。