對於 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 都保證爲最新。