深入理解React 組件狀態(tài)(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)計算而來的中間狀態(tài)祭埂。

組件中用到的一個變量是不是應(yīng)該作為組件State,可以通過下面的4條依據(jù)進行判斷:

  1. 這個變量是否是通過Props從父組件中獲缺ァ蛆橡?如果是,那么它不是一個狀態(tài)掘譬。
  2. 這個變量是否在組件的整個生命周期中都保持不變泰演?如果是,那么它不是一個狀態(tài)葱轩。
  3. 這個變量是否可以通過其他狀態(tài)(State)或者屬性(Props)計算得到睦焕?如果是,那么它不是一個狀態(tài)靴拱。
  4. 這個變量是否在組件的render方法中使用垃喊?如果不是,那么它不是一個狀態(tài)袜炕。這種情況下本谜,這個變量更適合定義為組件的一個普通屬性,例如組件中用到的定時器妇蛀,就應(yīng)該直接定義為this.timer耕突,而不是this.state.timer笤成。

請務(wù)必牢記,并不是組件中用到的所有變量都是組件的狀態(tài)眷茁!當存在多個組件共同依賴一個狀態(tài)時炕泳,一般的做法是狀態(tài)上移,將這個狀態(tài)放到這幾個組件的公共父組件中上祈。

二. State 與 Props 區(qū)別

除了State, 組件的Props也是和組件的UI有關(guān)的圃验。他們之間的主要區(qū)別是:State是可變的描馅,是組件內(nèi)部維護的一組用于反映組件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)放入一個隊列中简珠,React會優(yōu)化真正的執(zhí)行時機阶界,并且React會出于性能原因,可能會將多次setState的狀態(tài)修改合并成一次狀態(tài)修改聋庵。所以不要依賴當前的State膘融,計算下個State。當真正執(zhí)行狀態(tài)修改時祭玉,依賴的this.state并不能保證是最新的State托启,因為React會把多次State的修改合并成一次,這時攘宙,this.state將還是這幾次State修改前的State。另外需要注意的事拐迁,同樣不能依賴當前的Props計算下個狀態(tài)蹭劈,因為Props一般也是從父組件的State中獲取,依然無法確定在組件狀態(tài)更新時的值线召。

舉個例子铺韧,對于一個電商類應(yīng)用,在我們的購物車中缓淹,當我們點擊一次購買數(shù)量按鈕哈打,購買的數(shù)量就會加1塔逃,如果我們連續(xù)點擊了兩次按鈕,就會連續(xù)調(diào)用兩次this.setState({quantity: this.state.quantity + 1})料仗,在React合并多次修改為一次的情況下湾盗,相當于等價執(zhí)行了如下代碼:

Object.assign(
  previousState,
  {quantity: this.state.quantity + 1},
  {quantity: this.state.quantity + 1}
)

于是乎,后面的操作覆蓋掉了前面的操作立轧,最終購買的數(shù)量只增加了1個格粪。

如果你真的有這樣的需求,可以使用另一個接收一個函數(shù)作為參數(shù)的setState氛改,這個函數(shù)有兩個參數(shù)帐萎,第一個是當前最新狀態(tài)(本次組件狀態(tài)修改后的狀態(tài))的前一個狀態(tài)preState(本次組件狀態(tài)修改前的狀態(tài)),第二個參數(shù)是當前最新的屬性props胜卤。如下所示:

// 正確
this.setState((preState, props) => ({
  counter: preState.quantity + 1; 
}))

3. State 的更新是一個淺合并(Shallow Merge)的過程疆导。

當調(diào)用setState修改組件狀態(tài)時,只需要傳入發(fā)生改變的State葛躏,而不是組件完整的State澈段,因為組件State的更新是一個淺合并(Shallow Merge)的過程。例如紫新,一個組件的狀態(tài)為:

this.state = {
  title : 'React',
  content : 'React is an wonderful JS library!'
}

當只需要修改狀態(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當作是不可變對象囤耳,一方面是如果直接修改this.state,組件并不會重新render偶芍;另一方面State中包含的所有狀態(tài)都應(yīng)該是不可變對象充择。當State中的某個狀態(tài)發(fā)生變化,我們應(yīng)該重新創(chuàng)建這個狀態(tài)對象匪蟀,而不是直接修改原來的狀態(tài)椎麦。那么,當狀態(tài)發(fā)生變化時材彪,如何創(chuàng)建新的狀態(tài)呢观挎?根據(jù)狀態(tài)的類型,可以分成三種情況:

1. 狀態(tài)的類型是不可變類型(數(shù)字段化,字符串嘁捷,布爾值,null显熏, undefined)

這種情況最簡單雄嚣,因為狀態(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,當向books中增加一本書時港谊,使用數(shù)組的concat方法或ES6的數(shù)組擴展語法(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'];
}))

當從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);
}))

當從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),因為這些方法都是在原數(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 使用對象擴展語法(object spread properties

// 方法一:將state先賦值給另外的變量湖笨,然后使用對象擴展語法創(chuàng)建新對象
var owner = this.state.owner;
this.setState({
  owner: {...owner, name: 'Jason'};
})

// 方法二:使用preState、對象擴展語法創(chuàng)建新對象
this.setState(preState => ({
  owner: {...preState.owner, name: 'Jason'};
}))

總結(jié)一下蹦骑,創(chuàng)建新的狀態(tài)對象的關(guān)鍵是慈省,避免使用會直接修改原對象的方法,而是使用可以返回一個新對象的方法眠菇。當然边败,也可以使用一些Immutable的JS庫,如Immutable.js捎废,實現(xiàn)類似的效果放闺。

那么,為什么React推薦組件的狀態(tài)是不可變對象呢缕坎?一方面是因為不可變對象方便管理和調(diào)試,了解更多可參考這里篡悟;另一方面是出于性能考慮谜叹,當對象組件狀態(tài)都是不可變對象時匾寝,我們在組件的shouldComponentUpdate方法中,僅需要比較狀態(tài)的引用就可以判斷狀態(tài)是否真的改變荷腊,從而避免不必要的render調(diào)用艳悔。當我們使用React 提供的PureComponent時,更是要保證組件狀態(tài)是不可變對象女仰,否則在組件的shouldComponentUpdate方法中猜年,狀態(tài)比較就可能出現(xiàn)錯誤,因為PureComponent執(zhí)行的是淺比較(比較對象的引用)疾忍。

作者:艾特老干部
鏈接:http://www.reibang.com/p/c6257cbef1b1
來源:簡書
著作權(quán)歸作者所有乔外。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處一罩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杨幼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子聂渊,更是在濱河造成了極大的恐慌差购,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汉嗽,死亡現(xiàn)場離奇詭異欲逃,居然都是意外死亡,警方通過查閱死者的電腦和手機饼暑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門稳析,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撵孤,你說我怎么就攤上這事迈着。” “怎么了邪码?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵裕菠,是天一觀的道長。 經(jīng)常有香客問我闭专,道長奴潘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任影钉,我火速辦了婚禮画髓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘平委。我一直安慰自己奈虾,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肉微,像睡著了一般匾鸥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碉纳,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天勿负,我揣著相機與錄音,去河邊找鬼劳曹。 笑死奴愉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的铁孵。 我是一名探鬼主播锭硼,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼库菲!你這毒婦竟也來了账忘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熙宇,失蹤者是張志新(化名)和其女友劉穎鳖擒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烫止,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡蒋荚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了馆蠕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片期升。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖互躬,靈堂內(nèi)的尸體忽然破棺而出播赁,到底是詐尸還是另有隱情,我是刑警寧澤吼渡,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布容为,位于F島的核電站,受9級特大地震影響寺酪,放射性物質(zhì)發(fā)生泄漏坎背。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一寄雀、第九天 我趴在偏房一處隱蔽的房頂上張望得滤。 院中可真熱鬧,春花似錦盒犹、人聲如沸懂更。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沮协。三九已至坛猪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間皂股,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工命黔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呜呐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓悍募,卻偏偏與公主長得像蘑辑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坠宴,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 說在前面 關(guān)于 react 的總結(jié)過去半年就一直碎碎念著要搞起來洋魂,各(wo)種(tai)原(lan)因(le)。心...
    陳嘻嘻啊閱讀 6,875評論 7 41
  • 一. 定義State State必須能代表一個組件UI呈現(xiàn)的完整狀態(tài)集喜鼓,即組件的任何UI改變副砍,都可以從State的...
    一只dororo閱讀 240評論 0 1
  • HTML模版 之后出現(xiàn)的React代碼嵌套入模版中。 1. Hello world 這段代碼將一個一級標題插入到指...
    ryanho84閱讀 6,240評論 0 9
  • 把兒子交給父母庄岖,我倆手拉手去約會~ 傻乎乎在門口合影~ 眼花繚亂視野豁翎,連腳步聲都輕了~ 風景哪里都很好~ 莫名的期...
    lucy大人閱讀 609評論 11 49
  • 1 高中的時候,我在作文里寫道隅忿,我有時候樂觀心剥,有時多愁善感,跟朋友在一起也會比較大大咧咧背桐,有時卻也會敏感脆弱优烧,我認...
    yimy201314閱讀 187評論 0 0