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呢:
- 可以通過props從父組件中獲取的變量不應(yīng)該做為組件State尉共。
- 這個變量如果在組件的整個生命周期中都保持不變就不應(yīng)該作為組件State。
- 通過其他狀態(tài)(State)或者屬性(Props)計(jì)算得到的變量不應(yīng)該作為組件State弃锐。
- 沒有在組件的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)類型可以分為三種待诅。
- 數(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等费尽。
- 普通對象
對象也是可變類型赠群,修改對象類型的狀態(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
- 無狀態(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等組合方式)
- 純組件(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ā)生改變而組件沒有重新渲染的問題。
- 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)比較錯誤。