[转] 深入理解React 组件状态(State)
React 的核心思想是組件化的思想,應(yīng)用由組件搭建而成,而組件中最重要的概念是State(狀態(tài)),State是一個組件的UI數(shù)據(jù)模型,是組件渲染時的數(shù)據(jù)依據(jù)。
一. 如何定義State
定義一個合適的State,是正確創(chuàng)建組件的第一步。State必須能代表一個組件UI呈現(xiàn)的完整狀態(tài)集,即組件的任何UI改變,都可以從State的變化中反映出來;同時,State還必須是代表一個組件UI呈現(xiàn)的最小狀態(tài)集,即State中的所有狀態(tài)都是用于反映組件UI的變化,沒有任何多余的狀態(tài),也不需要通過其他狀態(tài)計(jì)算而來的中間狀態(tài)。
組件中用到的一個變量是不是應(yīng)該作為組件State,可以通過下面的4條依據(jù)進(jìn)行判斷:
請務(wù)必牢記,并不是組件中用到的所有變量都是組件的狀態(tài)!當(dāng)存在多個組件共同依賴一個狀態(tài)時,一般的做法是狀態(tài)上移,將這個狀態(tài)放到這幾個組件的公共父組件中。
二. State 與 Props 區(qū)別
除了State, 組件的Props也是和組件的UI有關(guān)的。他們之間的主要區(qū)別是:State是可變的,是組件內(nèi)部維護(hù)的一組用于反映組件UI變化的狀態(tài)集合;而Props對于使用它的組件來說,是只讀的,要想修改Props,只能通過該組件的父組件修改。在組件狀態(tài)上移的場景中,父組件正是通過子組件的Props, 傳遞給子組件其所需要的狀態(tài)。
三. 如何正確修改State
1.不能直接修改State。
直接修改state,組件并不會重新重發(fā)render。例如:
// 錯誤 this.state.title = 'React';正確的修改方式是使用setState():
// 正確 this.setState({title: 'React'});2. State 的更新是異步的。
調(diào)用setState,組件的state并不會立即改變,setState只是把要修改的狀態(tài)放入一個隊(duì)列中,React會優(yōu)化真正的執(zhí)行時機(jī),并且React會出于性能原因,可能會將多次setState的狀態(tài)修改合并成一次狀態(tài)修改。所以不要依賴當(dāng)前的State,計(jì)算下個State。當(dāng)真正執(zhí)行狀態(tài)修改時,依賴的this.state并不能保證是最新的State,因?yàn)镽eact會把多次State的修改合并成一次,這時,this.state將還是這幾次State修改前的State。另外需要注意的事,同樣不能依賴當(dāng)前的Props計(jì)算下個狀態(tài),因?yàn)镻rops一般也是從父組件的State中獲取,依然無法確定在組件狀態(tài)更新時的值。
舉個例子,對于一個電商類應(yīng)用,在我們的購物車中,當(dāng)我們點(diǎn)擊一次購買數(shù)量按鈕,購買的數(shù)量就會加1,如果我們連續(xù)點(diǎn)擊了兩次按鈕,就會連續(xù)調(diào)用兩次this.setState({quantity: this.state.quantity + 1}),在React合并多次修改為一次的情況下,相當(dāng)于等價(jià)執(zhí)行了如下代碼:
Object.assign(previousState,{quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1} )于是乎,后面的操作覆蓋掉了前面的操作,最終購買的數(shù)量只增加了1個。
如果你真的有這樣的需求,可以使用另一個接收一個函數(shù)作為參數(shù)的setState,這個函數(shù)有兩個參數(shù),第一個是當(dāng)前最新狀態(tài)(本次組件狀態(tài)修改后的狀態(tài))的前一個狀態(tài)preState(本次組件狀態(tài)修改前的狀態(tài)),第二個參數(shù)是當(dāng)前最新的屬性props。如下所示:
// 正確 this.setState((preState, props) => ({ counter: preState.quantity + 1; }))3. State 的更新是一個淺合并(Shallow Merge)的過程。
當(dāng)調(diào)用setState修改組件狀態(tài)時,只需要傳入發(fā)生改變的State,而不是組件完整的State,因?yàn)榻M件State的更新是一個淺合并(Shallow Merge)的過程。例如,一個組件的狀態(tài)為:
this.state = {title : 'React',content : 'React is an wonderful JS library!' }當(dāng)只需要修改狀態(tài)title時,只需要將修改后的title傳給setState:
this.setState({title: 'Reactjs'});React會合并新的title到原來的組件狀態(tài)中,同時保留原有的狀態(tài)content,合并后的State為:
{title : 'Reactjs',content : 'React is an wonderful JS library!' }四. State與Immutable
React官方建議把State當(dāng)作是不可變對象,一方面是如果直接修改this.state,組件并不會重新render;另一方面State中包含的所有狀態(tài)都應(yīng)該是不可變對象。當(dāng)State中的某個狀態(tài)發(fā)生變化,我們應(yīng)該重新創(chuàng)建這個狀態(tài)對象,而不是直接修改原來的狀態(tài)。那么,當(dāng)狀態(tài)發(fā)生變化時,如何創(chuàng)建新的狀態(tài)呢?根據(jù)狀態(tài)的類型,可以分成三種情況:
1. 狀態(tài)的類型是不可變類型(數(shù)字,字符串,布爾值,null, undefined)
這種情況最簡單,因?yàn)闋顟B(tài)是不可變類型,直接給要修改的狀態(tài)賦一個新值即可。如要修改count(數(shù)字類型)、title(字符串類型)、success(布爾類型)三個狀態(tài):
this.setState({count: 1, title: 'Redux', success: true })2. 狀態(tài)的類型是數(shù)組
如有一個數(shù)組類型的狀態(tài)books,當(dāng)向books中增加一本書時,使用數(shù)組的concat方法或ES6的數(shù)組擴(kuò)展語法(spread syntax):
// 方法一:將state先賦值給另外的變量,然后使用concat創(chuàng)建新數(shù)組 var books = this.state.books; this.setState({ books: books.concat(['React Guide']); }) // 方法二:使用preState、concat創(chuàng)建新數(shù)組 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; }))當(dāng)從books中截取部分元素作為新狀態(tài)時,使用數(shù)組的slice方法:
// 方法一:將state先賦值給另外的變量,然后使用slice創(chuàng)建新數(shù)組 var books = this.state.books; this.setState({ books: books.slice(1,3); }) // 方法二:使用preState、slice創(chuàng)建新數(shù)組 this.setState(preState => ({ books: preState.books.slice(1,3); }))當(dāng)從books中過濾部分元素后,作為新狀態(tài)時,使用數(shù)組的filter方法:
// 方法一:將state先賦值給另外的變量,然后使用filter創(chuàng)建新數(shù)組 var books = this.state.books; this.setState({ books: books.filter(item => { return item != 'React'; }); }) // 方法二:使用preState、filter創(chuàng)建新數(shù)組 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }); }))注意不要使用push、pop、shift、unshift、splice等方法修改數(shù)組類型的狀態(tài),因?yàn)檫@些方法都是在原數(shù)組的基礎(chǔ)上修改,而concat、slice、filter會返回一個新的數(shù)組。
3. 狀態(tài)的類型是普通對象(不包含字符串、數(shù)組)
3.1 使用ES6 的Object.assgin方法
// 方法一:將state先賦值給另外的變量,然后使用Object.assign創(chuàng)建新對象 var owner = this.state.owner; this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}); }) // 方法二:使用preState、Object.assign創(chuàng)建新對象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); }))3.2 使用對象擴(kuò)展語法(object spread properties)
// 方法一:將state先賦值給另外的變量,然后使用對象擴(kuò)展語法創(chuàng)建新對象 var owner = this.state.owner; this.setState({ owner: {...owner, name: 'Jason'}; }) // 方法二:使用preState、對象擴(kuò)展語法創(chuàng)建新對象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; }))總結(jié)一下,創(chuàng)建新的狀態(tài)對象的關(guān)鍵是,避免使用會直接修改原對象的方法,而是使用可以返回一個新對象的方法。當(dāng)然,也可以使用一些Immutable的JS庫,如Immutable.js,實(shí)現(xiàn)類似的效果。
那么,為什么React推薦組件的狀態(tài)是不可變對象呢?一方面是因?yàn)椴豢勺儗ο蠓奖愎芾砗驼{(diào)試,了解更多可參考這里;另一方面是出于性能考慮,當(dāng)對象組件狀態(tài)都是不可變對象時,我們在組件的shouldComponentUpdate方法中,僅需要比較狀態(tài)的引用就可以判斷狀態(tài)是否真的改變,從而避免不必要的render調(diào)用。當(dāng)我們使用React 提供的PureComponent時,更是要保證組件狀態(tài)是不可變對象,否則在組件的shouldComponentUpdate方法中,狀態(tài)比較就可能出現(xiàn)錯誤,因?yàn)镻ureComponent執(zhí)行的是淺比較(比較對象的引用)。
作者:蒼山沭河
鏈接:https://www.jianshu.com/p/c6257cbef1b1
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
轉(zhuǎn)載于:https://www.cnblogs.com/chris-oil/p/8215756.html
總結(jié)
以上是生活随笔為你收集整理的[转] 深入理解React 组件状态(State)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第七章软件项目资源管理
- 下一篇: yarn安装依赖包报错 error A