React組件的State

React的核心思想是組件化的思想诅妹,應(yīng)用由組件搭建而成郎汪, 而組件中最重要的概念是State(狀態(tài))森瘪。

正確定義State

React把組件看成一個狀態(tài)機(jī)。通過與用戶的交互吵血,實(shí)現(xiàn)不同狀態(tài)谎替,然后渲染UI,讓用戶界面和數(shù)據(jù)保持一致蹋辅。組件的任何UI改變钱贯,都可以從State的變化中反映出來;State中的所有狀態(tài)都用于反映UI的變化侦另,不應(yīng)有多余狀態(tài)秩命。

那么什么樣的變量應(yīng)該做為組件的State呢:

  1. 可以通過props從父組件中獲取的變量不應(yīng)該做為組件State尉共。
  2. 這個變量如果在組件的整個生命周期中都保持不變就不應(yīng)該作為組件State。
  3. 通過其他狀態(tài)(State)或者屬性(Props)計(jì)算得到的變量不應(yīng)該作為組件State弃锐。
  4. 沒有在組件的render方法中使用的變量不用于UI的渲染袄友,那么這個變量不應(yīng)該作為組件的State 。這種情況下霹菊,這個變量更適合定義為組件的一個普通屬性剧蚣。

React中的immutability

React官方建議把State當(dāng)做是不可變對象,State中包含的所有狀態(tài)都應(yīng)該是不可變對象旋廷,當(dāng)State中的某個狀態(tài)發(fā)生變化鸠按,我們應(yīng)該重新創(chuàng)建這個狀態(tài)對象,而不是直接修改原來的狀態(tài)饶碘。State根據(jù)狀態(tài)類型可以分為三種待诅。

  1. 數(shù)字,字符串熊镣,布爾值,null募书,undefined這五種不可變類型绪囱。

因?yàn)槠浔旧砭褪遣豢勺兊模绻薷臓顟B(tài)的話莹捡,直接賦新值就可以鬼吵,例如:

this.setState({
  num: 1,
  string: 'hello',
  ready: true
});

2、數(shù)組類型

js中數(shù)組類型為可變類型篮赢。假如有一個state是數(shù)組類型齿椅,例如students。修改students的狀態(tài)應(yīng)該保證不會修改原來的狀態(tài)启泣,
例如新增一個數(shù)組元素涣脚,應(yīng)使用數(shù)組的concat方法或ES6的數(shù)組擴(kuò)展語法。

  let students = this.state.students;
  this.setState({
    students: students.concat(['xiaoming'])
  });
  
  //或者
  this.setState(preState => ({
    students: [ ...preState.books, 'xiaogang']
  });

從數(shù)組中截取部分作為新狀態(tài)時寥茫,應(yīng)使用slice方法;當(dāng)從數(shù)組中過濾部分元素后遣蚀,作為新狀態(tài)時,使用filter方法纱耻。不應(yīng)該使用push芭梯、pop、shift弄喘、unshift玖喘、splice等方法修改數(shù)組類型的狀態(tài),因?yàn)檫@些方法都是在原數(shù)組的基礎(chǔ)上修改的蘑志。應(yīng)當(dāng)使用不會修改原數(shù)組而返回一個新數(shù)組的方法累奈,例如concat贬派、slice、filter等费尽。

  1. 普通對象

對象也是可變類型赠群,修改對象類型的狀態(tài)時,應(yīng)該保證不會修改原來的狀態(tài)旱幼〔槊瑁可以使用ES6的Object.assign方法或者對象擴(kuò)展語法。

//Object.assign方法
this.setState(preState => ({
  school: Object.assign({}, preState.school, {classNum: 10})
}));

//對象擴(kuò)展語法
let school = this.state.school;
this.setState({
  school: { ...school, { classNum: 10 } }
})

不同方式創(chuàng)建的組件中的State

  1. 無狀態(tài)組件(Stateless Functional Component)

這種組件自身沒有狀態(tài)柏卤,不需要管理state狀態(tài)冬三,所有數(shù)據(jù)都是從props傳入。

const Teacher = ({
  name,
  age
}) => {
  return (
    <div>Teacher {name} is {age} years old.</div>
  )
}

相同的輸入(props)必然有相同的輸出缘缚,因此這種組件可以寫成無副作用的純函數(shù)勾笆,且適合函數(shù)式編程(函數(shù)的compose,curring等組合方式)

  1. 純組件(PureComponent)

我們知道桥滨,當(dāng)組件的props或者state發(fā)生變化的時候React會對組件當(dāng)前的Props和State分別與nextProps和nextState進(jìn)行比較窝爪,當(dāng)發(fā)現(xiàn)變化時,就會對當(dāng)前組件以及子組件進(jìn)行重新渲染齐媒,否則就不渲染蒲每。有時候我們會使用shouldComponentUpdate來避免不必要的渲染。當(dāng)然有時候這種簡單的判斷喻括,顯得有些多余和樣板化邀杏,于是react就提供了PureComponent來自動幫我們完成這件事,簡化了我們的代碼唬血,提高了性能望蜡。例如:

class CounterButton extends React.pureComponent {
  constructor(props) {
    super(props)
    this.state = { count: 1 };
  }
  render() {
    return (
      <button 
        color={this.props.color}
        onClick={() => this.setState(state = > ({count: state.count + 1}))}
      >
       Count: {this.state.coount}
      </button>
    )
  }
}

在上例中,雖然沒有添加shouldComponentUpdate代碼拷恨,但是react自動完成了props和state的比較脖律,當(dāng)props和state沒有發(fā)生變化時不會對組件重新渲染。但是PureComponent的自動為我們添加的shouldComponentUpate函數(shù)腕侄,只是對props和state進(jìn)行淺比較(shadowcomparison)状您,當(dāng)props或者state本身是嵌套對象或數(shù)組等時,淺比較并不能得到預(yù)期的結(jié)果兜挨,這會導(dǎo)致實(shí)際的props和state發(fā)生了變化膏孟,但組件卻沒有更新的問題。

淺比較:比較 Object.keys(state | props) 的長度是否一致拌汇,每一個 key 是否兩者都有柒桑,并且是否是一個引用,也就是只比較了第一層的值噪舀,確實(shí)很淺魁淳,所以深層的嵌套數(shù)據(jù)是對比不出來的飘诗。

例如

class Ul extends PureComponent {
  constructor(props) {
    super(props)
    this.state = { 
      items: [1, 2, 3] 
    };
  }
  handleClick = () => {
    let { items } = this.state;
    items.push(4);
    this.setState({ items });
  }
  render() {
    return (
    <div>
      <ul>
        {this.state.items.map(i => <li key={i}>{i}</li>)}
      </ul>
      <button onClick={this.handleClick}>add</button>
    </div>)
  }
}

會發(fā)現(xiàn),無論怎么點(diǎn) add 按鈕界逛, li 都不會變多昆稿,因?yàn)?pop方法是在原數(shù)組上進(jìn)行的修改,items的preState與nextState 用的是一個引用息拜, shallowEqual 的結(jié)果為 true 溉潭。改正:

handleClick = () => {
  let { items } = this.state;
  this.setState({ items: items.concat([4]) });
}

這樣每次改變都會產(chǎn)生一個新的數(shù)組,items的preState與nextState 用的是不同引用少欺, shallowEqual 的結(jié)果為 false喳瓣,也就可以 render 了。
在PureComponent中要避免可變對象作為props和state赞别,你可以考慮使用Immutable.js來創(chuàng)建不可變對象畏陕,Immutable Data就是一旦創(chuàng)建,就不能再被更改的數(shù)據(jù)仿滔。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象惠毁。從而避免出現(xiàn)props和state發(fā)生改變而組件沒有重新渲染的問題。

  1. Component

與PureComponent不同的是崎页,Component需要開發(fā)者顯示定義shouldComponentUpdate且定制性更強(qiáng)鞠绰。對于一些無論怎么修改都不應(yīng)該讓組件重新渲染的props就不必在shouldComponentUpdate中進(jìn)行比較。同PureComponent一樣Component中的state也應(yīng)該是不可變對象实昨。

使用Object.assign或者concat等方法避免修改原來的對象或數(shù)組是通過將屬性/元素從一個對象/數(shù)組復(fù)制到另一個來工作。對于大型對象/數(shù)組來說盐固,這樣的操作比較慢荒给。且對于嵌套對象或數(shù)組可能還需要深拷貝才能保證state的不可變性。當(dāng)應(yīng)用復(fù)雜后刁卜,props和state也變得復(fù)雜和龐大志电,通過淺拷貝和深拷貝就會影響性能和造成內(nèi)存的浪費(fèi)。且對象和數(shù)組默認(rèn)是可變的蛔趴,沒有什么可以確保state是不可變對象挑辆,你必須時刻記住要使用這些方法。

使用immutable.js可以很好的解決這些問題孝情。Immutable.js 的基本原則是對于不變的對象返回相同的引用鱼蝉,而對于變化的對象,返回新的引用箫荡。同時為了避免 deepCopy 把所有節(jié)點(diǎn)都復(fù)制一遍帶來的性能損耗魁亦,Immutable 使用了 Structural Sharing(結(jié)構(gòu)共享),即如果對象樹中一個節(jié)點(diǎn)發(fā)生變化羔挡,只修改這個節(jié)點(diǎn)和受它影響的父節(jié)點(diǎn)洁奈,其它節(jié)點(diǎn)則進(jìn)行共享间唉。(關(guān)于immutab.js可以看這篇文章或者immutable.js)

總結(jié)

正確的定義state不僅便于狀態(tài)的管理與調(diào)試,而且在復(fù)雜應(yīng)用中保持state的簡潔利术,在組件更新時能減少比較次數(shù)呈野,提高性能。保證state的不可變性不僅保證數(shù)據(jù)更容易追蹤印叁、推導(dǎo)被冒,而且能避免組件更新時shouldComponent出現(xiàn)狀態(tài)比較錯誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喉钢,一起剝皮案震驚了整個濱河市姆打,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肠虽,老刑警劉巖幔戏,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異税课,居然都是意外死亡闲延,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門韩玩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垒玲,“玉大人,你說我怎么就攤上這事找颓『嫌” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵击狮,是天一觀的道長佛析。 經(jīng)常有香客問我,道長彪蓬,這世上最難降的妖魔是什么寸莫? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮档冬,結(jié)果婚禮上膘茎,老公的妹妹穿的比我還像新娘。我一直安慰自己酷誓,他們只是感情好披坏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盐数,像睡著了一般刮萌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上娘扩,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天着茸,我揣著相機(jī)與錄音壮锻,去河邊找鬼。 笑死涮阔,一個胖子當(dāng)著我的面吹牛猜绣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敬特,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼掰邢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伟阔?” 一聲冷哼從身側(cè)響起辣之,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皱炉,沒想到半個月后怀估,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡合搅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年多搀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灾部。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡康铭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赌髓,到底是詐尸還是另有隱情从藤,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布锁蠕,位于F島的核電站夷野,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匿沛。R本人自食惡果不足惜扫责,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一榛鼎、第九天 我趴在偏房一處隱蔽的房頂上張望逃呼。 院中可真熱鬧,春花似錦者娱、人聲如沸抡笼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽推姻。三九已至,卻和暖如春框沟,著一層夾襖步出監(jiān)牢的瞬間藏古,已是汗流浹背增炭。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拧晕,地道東北人隙姿。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像厂捞,于是被迫代替她去往敵國和親输玷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359