十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
假設(shè) React 組件有這樣一個(gè)狀態(tài):

創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供日喀則網(wǎng)站建設(shè)、日喀則做網(wǎng)站、日喀則網(wǎng)站設(shè)計(jì)、日喀則網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、日喀則企業(yè)網(wǎng)站模板建站服務(wù),10年日喀則做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
this.state = {
a: {
b: 1
}
}我們這樣修改了它的狀態(tài):
this.state.a.b = 2;
this.setState(this.state);
你覺(jué)得組件會(huì)重新渲染么?
我們先在 class 組件里試一下:
import { Component } from 'react';
class Dong extends Component {
constructor() {
super();
this.state = {
a: {
b: 1
}
}
}
componentDidMount() {
setTimeout(() => {
this.state.a.b = 2;
this.setState(this.state);
}, 2000);
}
render() {
return {this.state.a.b}
}
}
export default Dong;渲染 state.a.b 的值,兩秒以后修改 state。
你發(fā)現(xiàn)它重新渲染了,因?yàn)槠胀ǖ?class 組件只要 setState 就會(huì)渲染。
但很多情況下我們需要做性能優(yōu)化,只有 props 和 state 變了才需要渲染,這時(shí)候會(huì)繼承 PureComponent:
但這時(shí)候你就會(huì)發(fā)現(xiàn)組件不再重新渲染了。
說(shuō)明這種情況下不能這樣寫 setState:
先不著急探究原因,我們?cè)僭?function 組件里試一下:
import { useEffect, useState } from 'react';
function Dong() {
const [state, setState] = useState({
a: {
b: 1
}
});
useEffect(() => {
setTimeout(() => {
state.a.b = 2;
setState(state)
}, 2000);
}, [])
return {state.a.b}
}
export default Dong;這時(shí)候你覺(jué)得組件會(huì)重新渲染么?
結(jié)果是也不會(huì)重新渲染。
這說(shuō)明 React 內(nèi)部肯定對(duì) function 組件還有繼承 PureComponent 的 class 組件做了相應(yīng)的處理。
那 React 都做了什么處理呢?
我們從源碼看一下:
首先是繼承 PureComponent 的 class 組件:
你會(huì)發(fā)現(xiàn) React 在更新 class 組件的時(shí)候,會(huì)判斷如果是 PureComponent,那么會(huì)淺比較 props 和 state,如果變了才會(huì)渲染。
怎么淺比較的呢?
你會(huì)發(fā)現(xiàn)它先對(duì)比了兩個(gè)值是否相等,如果不相等的話,再取出 key 來(lái),對(duì)比每個(gè) key 的值是否相等。
所以說(shuō),我們 setState 的時(shí)候傳入 this.state 就不行了,第一個(gè)判斷都過(guò)不去。
而且就算創(chuàng)建了新對(duì)象,如果每個(gè) key 的值沒(méi)有變,那依然也是不會(huì)渲染的。
這就是 React 對(duì) PureComponent 做的優(yōu)化處理。
再來(lái)看下 function 組件的,React 是怎么對(duì)它做的處理呢?
你會(huì)看到調(diào)用 useState 的 setXxx 時(shí),React 會(huì)判斷上次的 state 和這次的 state,如果一樣,那就不會(huì)渲染,直接 return 了。
這是為什么 function 組件里 setState 上次的 state 不行的原因。
這兩種情況還是有區(qū)別的,PureComponent 的處理里如果 state 變了,還會(huì)依次對(duì)比每個(gè) key 的值,如果有某個(gè)值變了才會(huì)去渲染,但 function 組件里只對(duì)比了 state。
我們測(cè)試一下:
用上圖的方式 setState,整個(gè) state 變了,但是 key 對(duì)應(yīng)的值沒(méi)有變。
在 PureComponent 的 class 組件里,按照我們的分析應(yīng)該不會(huì)再渲染,只會(huì)打印一次 render:
確實(shí)是這樣,雖然 state 對(duì)象變了,但是 key 的值沒(méi)變,不會(huì)重新渲染。
然后在 function 組件里試一下:
你會(huì)發(fā)現(xiàn)它打印了兩次 render:
綜上,我們可以總結(jié)一下:
普通的 class 組件,setState 就會(huì)重新渲染
繼承 PureComponent 的 class 組件,setState 時(shí)會(huì)對(duì)比 state 本身變沒(méi)變,還會(huì)對(duì)比 state 的每個(gè) key 的值變沒(méi)變,變了才會(huì)重新渲染
function 組件在用 useState 的 setXxx 時(shí),會(huì)對(duì)比 state 本身變沒(méi)變,變了就會(huì)重新渲染
為什么 function 組件里只對(duì)比了 state 沒(méi)有對(duì)比每個(gè) key 的值也很容易理解,因?yàn)楸緛?lái)每個(gè) state就是用 useState 單獨(dú)聲明的了,不像 class 組件的 state 都在一起。
知道了這個(gè)結(jié)論,我們也就知道了 setState 該怎么寫了:
class 組件要這么寫:
state 的每個(gè)要修改的 key 的值,如果是個(gè)對(duì)象,那要?jiǎng)?chuàng)建一個(gè)新的對(duì)象才行。
function 組件里也是,要這么寫:
綜上,不管是 class 組件,還是 function 組件,setState 時(shí)都要?jiǎng)?chuàng)建新的 state,并且對(duì)應(yīng)的 key 的值的時(shí)候,如果是對(duì)象,要?jiǎng)?chuàng)建新的對(duì)象(雖然普通 class 組件里可以不這么寫,但還是建議統(tǒng)一用這種寫法,不然容易引起困惑)。
但這樣又有了一個(gè)新的問(wèn)題:
如果 state 的內(nèi)容很多呢?
而你只想修改其中的一部分,要把整個(gè)對(duì)象復(fù)制一次:
是不是很麻煩?
能不能我修改了對(duì)象的值,立馬給我返回一個(gè)新的對(duì)象呢?
就是最開頭的時(shí)候,我們的那種寫法改造一下:
const newState = this.state.set('a.b', 2);
this.setState(newState);這么一個(gè)明顯的痛點(diǎn)需求,自然就有相應(yīng)的庫(kù)了,也就是 immutable,這個(gè)是 facebook 官方出的,說(shuō)是花了三年寫的。
它有這么幾個(gè) api:fromJS、toJS、set、setIn、get、getIn。
我們?cè)囈幌戮椭懒耍?/p>
const immutableObj = fromJS({
a: {
b: 1
}
});
const newObj = immutableObj.get('a').set('b', 2);用 fromJS 把 JS 對(duì)象轉(zhuǎn)成 immutable 內(nèi)部的數(shù)據(jù)結(jié)構(gòu),然后 get a,再 set b 的值。
這樣返回的是 immutable 的數(shù)據(jù)結(jié)構(gòu),并且對(duì) b 做了修改:
你和之前的 a 屬性的值對(duì)比下,發(fā)現(xiàn)也不一樣了:
這就是它的作用,修改值以后返回新的 immutable 數(shù)據(jù)結(jié)構(gòu)。
那如果像修改一個(gè)層數(shù)比較深的值,但希望返回的值是整個(gè)對(duì)象的新的 immutable 結(jié)構(gòu)呢?
可以用 setIn:
這樣修改了任意屬性之后,都能拿到最新的對(duì)象,這不就完美解決了我們的痛點(diǎn)問(wèn)題么?
你還可以用 toJS 再把 immutable 數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)成 JS 對(duì)象:
再來(lái)回顧下 immutable 的 api:fromJS、toJS、set、get、setIn、getIn 這些都很容易理解。再就是 immutable 內(nèi)部的數(shù)據(jù)結(jié)構(gòu) Map、Set 等。(注意這里的 Map、Set 不是 JS 里的那個(gè),而是 immutable 實(shí)現(xiàn)的)
這些 immutable 數(shù)據(jù)結(jié)構(gòu)一般不大需要手動(dòng)創(chuàng)建,直接用 fromJS 讓 immutable 去創(chuàng)建就行。
然后我們?cè)?React 組件里用一下試試:
先在 class 組件里用用:
a 的值是個(gè)對(duì)象,我們用 fromJS 轉(zhuǎn)成 immutable 的數(shù)據(jù)結(jié)構(gòu),之后修改調(diào)用 set、setIn 來(lái)修改。
不過(guò),渲染的時(shí)候也得用 get、getIn 的 api 來(lái)取了。
這樣也解決了 setState 需要?jiǎng)?chuàng)建新對(duì)象的問(wèn)題,而且更優(yōu)雅。
有的同學(xué)可能會(huì)問(wèn),為什么要 sate.a 用 fromJS 轉(zhuǎn)成 immutable,而不是整個(gè) state 呢?
因?yàn)?react 內(nèi)部也會(huì)用到這個(gè) state 呀,就比如上面那個(gè)淺比較那里:
react 需要把每個(gè) key 的值取出來(lái)對(duì)比下變沒(méi)變,而 immutable 對(duì)象只能用 get、getIn 來(lái)取,所以class 組件里不能把整個(gè) state 變?yōu)?immutable,只能把某個(gè) key 值的對(duì)象變?yōu)?immutable。
再在 function 組件里用下:
function 組件里就可以這樣寫了,把整個(gè) state 用 fromJS 變?yōu)?immutable 的,然后后面修改用 setIn,獲取用 getIn。
也同樣解決了 setState 要?jiǎng)?chuàng)建新對(duì)象的問(wèn)題。
為啥 function 組件里就可以把整個(gè) state 變?yōu)?immutable 的了呢?
因?yàn)橹挥薪M件內(nèi)部會(huì)用呀,我們自己寫的代碼是知道用 setIn、getIn 來(lái)操作的,但是 class 組件的話 react 還會(huì)對(duì) PureComponent 做一些優(yōu)化,會(huì)在組件外把 state 取出來(lái)處理,所以那個(gè)就只能把某些 key 變?yōu)?immutable 了。
immutable 介紹完了,大家覺(jué)得怎么樣?
immutable 確實(shí)解決了創(chuàng)建新對(duì)象的復(fù)雜度的問(wèn)題,而且性能也好,因?yàn)樗鼊?chuàng)建了一套自己的數(shù)據(jù)結(jié)構(gòu)。
但也相應(yīng)的,導(dǎo)致使用的時(shí)候必須要用 getIn、setIn 的 api 才行,有一些心智負(fù)擔(dān)。
這種心智負(fù)擔(dān)是不可避免的吧?
還真可以,這幾年又出了一個(gè)新的 immutable 庫(kù),叫做 immer(MobX 作者寫的)。它就覆蓋了 immutable 的功能的同時(shí),還沒(méi)有心智負(fù)擔(dān)。
沒(méi)有心智負(fù)擔(dān)?怎么可能?
我們?cè)囈幌戮椭懒耍?/p>
import { produce } from 'immer';
const obj = {
a: {
b: 1
}
};
const obj2 = produce(obj, draft => {
draft.a.b = 2
});obj 是原對(duì)象,調(diào)用 produce 傳入該對(duì)象和要對(duì)它做的修改,返回值就是新對(duì)象:
后面就是普通 JS 對(duì)象的用法,也不用啥 getIn、setIn 啥的。
我們?cè)?class 組件里用一下:
setState 的時(shí)候調(diào)用 produce,傳入原來(lái)的 state 和修改函數(shù),這樣返回的就是新的 state。
用 state 的時(shí)候依然是普通 JS 對(duì)象的用法。是不是簡(jiǎn)單的一批,心智負(fù)擔(dān)基本為 0?
我們?cè)僭?function 組件里用一下:
同樣簡(jiǎn)單的一批,只要 setState 的時(shí)候調(diào)用下 produce 來(lái)產(chǎn)生新對(duì)象就行。
又學(xué)完了 immer,我們來(lái)對(duì)比下 immutable 和 immer:
直接看圖吧:
class 組件里,immutable 這樣寫:
immer 這樣寫:
function 組件里,immutable 這樣寫:
immer 這樣寫:
沒(méi)有對(duì)比就沒(méi)有傷害,從使用體驗(yàn)上,immer 完勝。
這么說(shuō),我們只用 immer 不就行了?
也不全是,90% 的場(chǎng)景下用 immer 就行,但 immutable 也有它獨(dú)特的優(yōu)點(diǎn):
immutable 有自己的數(shù)據(jù)結(jié)構(gòu),修改數(shù)據(jù)的時(shí)候會(huì)創(chuàng)建新的節(jié)點(diǎn)連接之前的節(jié)點(diǎn)組成新的數(shù)據(jù)結(jié)構(gòu)。
而 immer 沒(méi)有自己的數(shù)據(jù)結(jié)構(gòu),它只是通過(guò) Proxy 實(shí)現(xiàn)了代理,內(nèi)部自動(dòng)創(chuàng)建新的對(duì)象:
只不過(guò)是把手動(dòng)創(chuàng)建新對(duì)象的過(guò)程通過(guò)代理給自動(dòng)化了:
所以從性能上來(lái)說(shuō),如果有特別大的 state 的話,immutable 會(huì)好一些,因?yàn)樗玫氖菍S脭?shù)據(jù)結(jié)構(gòu),做了專門的優(yōu)化,除此以外,immer 更好一些。
綜上,90% 的 React 應(yīng)用,用 immer 比 immutable 更好一些,代碼寫起來(lái)簡(jiǎn)單,也更容易維護(hù)。有大 state 的,可以考慮 immutable。
此外,immutable 在 redux 里也很有用的:
用 immutable 的話是這樣寫:
const initialState = fromJS({})
function reducer(state = initialState, action) {
switch (action.type) {
case SET_NAME:
return state.set('name', 'guang')
default:
return state
}
}取 store 的 state 要用 getIn 或 get:
function mapStateToProps(state) {
return {
xxxx: state.getIn(['guangguang', 'guang']),
yyyy: state.getIn(['dongdong', 'dong'])
}
}而 immer 是這樣寫:
const reducer = produce((state = initialState, action) => {
switch (action.type) {
case SET_NAME:
state.name = 'guang';
break;
default:
return state
}
})用 store 的 state 是普通對(duì)象的用法:
function mapStateToProps(state) {
return {
xxxx: state.guangguang,
yyyy: state.dongdong
}
}從結(jié)合 redux 的角度來(lái)看,也是 immer 在體驗(yàn)上完勝。
在 React 組件里 setState 是要?jiǎng)?chuàng)建新的 state 對(duì)象的,在繼承 PureComponent 的 class 組件、function 組件都是這樣。
繼承 PureComponent 的 class 組件會(huì)淺對(duì)比 props 和 state,如果 state 變了,并且 state 的 key 的某個(gè)值變了,才會(huì)渲染。
function 組件的 state 對(duì)象變了就會(huì)重新渲染。
雖然在普通 class 組件里,不需要?jiǎng)?chuàng)建新的 state,但我們還是建議統(tǒng)一,所有的組件里的 setState 都創(chuàng)建新的對(duì)象。
但是創(chuàng)建對(duì)象是件比較麻煩的事情,要一層層 ...,所以我們會(huì)結(jié)合 immutable 的庫(kù)。
主流的 immutable 庫(kù)有兩個(gè), facebook 的 immutable 和 MobX 作者寫的 immer。
immutable 有自己的數(shù)據(jù)結(jié)構(gòu),Map、Set 等,有 fromJS、toJS 的 api 用來(lái)轉(zhuǎn)換 immutable 數(shù)據(jù)結(jié)構(gòu)和普通 JS 對(duì)象,操作數(shù)據(jù)需要用 set、setIn、get、getIn。
immer 只有一個(gè) produce api,傳入原對(duì)象和修改函數(shù),返回的就是新對(duì)象,使用新對(duì)象就是普通 JS 對(duì)象的用法。
從使用體驗(yàn)上來(lái)說(shuō),不管是和 react 的 setState 結(jié)合還是和 redux 的 reducer 結(jié)合,都是 immer 完勝,但是 immutable 因?yàn)橛袑S脭?shù)據(jù)結(jié)構(gòu)的原因,在有大 state 對(duì)象的時(shí)候,性能會(huì)好一些。
90% 的情況下,immer 能完勝 immutable。