React 函數(shù)組件不是有狀態(tài)嗎,為什么還要說他是純函數(shù)
許多人在學(xué)習(xí) React 時會有這樣一個疑問,不斷看到 React 官方團(tuán)隊言論,或者說各路大佬都是在說 React 是函數(shù)式編程,我們寫組件確實(shí)寫的是組件,但問題就在于,我們寫的組件是有內(nèi)部狀態(tài),這樣的函數(shù)就不是純函數(shù)了,這怎么能算是函數(shù)式編程呢?
想不通。
今天這篇文章,就來跟大家解釋一下,為什么 React 的函數(shù)式組件,其實(shí)就是純函數(shù)。
UI = f(state)
一、hook 的特性
我們在聲明一個函數(shù)式組件時,常常會使用到 hook 來聲明一些狀態(tài)或者方法,但是我們在使用 hook 時,你會發(fā)現(xiàn) hook 會有一些奇怪的規(guī)則,那么就是不能把 hook 放到條件判斷中去。
if (a === 1) {
const [count, setCount] = useState(0)
}
然后有的人就很不理解這個現(xiàn)象。于是把這個情況定性為 React 的設(shè)計缺陷。但這真的是設(shè)計缺陷嗎?
我們只需要換個思路,你就能對這個現(xiàn)象豁然開朗。
二、hook 存在哪?
在初學(xué)階段,我們會很自然的認(rèn)為,當(dāng)我們使用 useState 在函數(shù)內(nèi)部定義了一個狀態(tài)時,那么這個狀態(tài)一定是保存在這個函數(shù)內(nèi)部的。
function Demo() {
const [count, setCount] = useState(0)
...
}
然后理解得多了,才發(fā)現(xiàn)并不是這樣。每一個函數(shù)的狀態(tài)都被存在了另外一個模塊里(Fiber tree)。也就是說,只要 React 允許,我們甚至可以在別的組件訪問到任意一個組件里的狀態(tài)。當(dāng)然 React 對這種情況做了限制,只允許通過特定的語法來做到這個事情。
函數(shù)組件中的所有的 hook 都是從外部傳入的。
三、state 其實(shí)是參數(shù)
我們再來看一下這個公式。
UI = f(state)
這個時候我們會恍然發(fā)現(xiàn),雖然 state 在函數(shù)內(nèi)部定義/獲取了,但是很明顯,React 是期望大家把他當(dāng)成外部傳入的參數(shù)來理解的。
例如我們有這樣一個函數(shù)。
function Counter({x}) {
const [count, setCount] = useState(0)
return (
<div>{x + count}</div>
)
}
他可以等價于。
function Counter({x}, [count = 0, setCount]) {
return (
<div>{x + count}</div>
)
}
這個時候我們就明朗了,函數(shù),原來還是純函數(shù)。但是為什么語法不這樣設(shè)計呢,不是更好理解嗎?當(dāng)然是因?yàn)閰?shù)太多了寫不下了呀,因此 React 把傳參的行為,下放到了函數(shù)內(nèi)部,通過 hook 的方式來實(shí)現(xiàn)。
四、重新審視 hook
如果 state 是外部傳入的參數(shù),那么此時我們就要重新審視一下為什么不能把 hook 放到條件判斷中去了。
例如:
function Counter({x}) {
if (a === 0) {
const [loading, setLoading] = useState(false)
}
const [count, setCount] = useState(0)
return (
<div>{x + count}</div>
)
}
所以,useState 是外部傳參,那么參數(shù)本來就應(yīng)該有嚴(yán)格的順序要求,這個時候如果第一個參數(shù)因?yàn)椴环蠗l件而在代碼邏輯里消失了,那第二個參數(shù),不就變成第一個參數(shù)了嗎?
這個時候代碼邏輯中,就會把第二個參數(shù)當(dāng)成第一個參數(shù)去使用,這不就亂了嗎?
當(dāng)我們調(diào)用 setState 時,表示入?yún)⒄诎l(fā)生變化,函數(shù)自然也會重新執(zhí)行。
五、總結(jié)
hook 存放在函數(shù)外部,因此不屬于函數(shù)內(nèi)部的狀態(tài)。我們在理解函數(shù)式組件是純函數(shù)時,應(yīng)該把 hook 當(dāng)成參數(shù)去看待,這樣很多現(xiàn)象就非常自然了。
函數(shù)式編程更加側(cè)重于把邏輯解耦拆分成不同的函數(shù),然后通過函數(shù)組合的形式去構(gòu)建一個完整的邏輯,例如我們非常常見的 map 方法
function func(item) {
return item + 1
}
var newArr = arr.map(func)
所以理解函數(shù)式編程,會對邏輯封裝解耦的能力要求比較高,在這種情況下,理解函數(shù)式編程確實(shí)會存在一定的門檻。所以最后思考一個問題,為什么 state 一定要是不可變數(shù)據(jù)?