由于前段時(shí)間在領(lǐng)導(dǎo)的“威逼利誘”下,了解和學(xué)習(xí)了下Recoil,剛開(kāi)始是比較抗拒的,不過(guò)后來(lái)慢慢的了解了之后,發(fā)現(xiàn)還是很不錯(cuò)的,所以做一個(gè)學(xué)習(xí)的筆記和分享。
Recoil最重要的因?yàn)樗腔贗mmutable的數(shù)據(jù)流管理方案,帶來(lái)的可預(yù)測(cè)性非常利于調(diào)試和維護(hù):
1.斷點(diǎn)調(diào)試時(shí)可預(yù)測(cè),已創(chuàng)建過(guò)的值不會(huì)突變,與斷點(diǎn)位置也無(wú)關(guān)
2.在React框架下組件更新機(jī)制單一只有引用變化才觸發(fā)重新渲染,沒(méi)有forceUpdate的困擾
上手使用
1.初始化:使用Recoil的狀態(tài)的組件需要用RecoilRoot包裹
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
useSetRecoilState
} from 'recoil';
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
2.定義狀態(tài):不需要集中定義,可以像Mobx分散在各個(gè)地方
export const nameState = atom({
key: 'nameState',
default: 'test'
});
其中key在recoliRoot中是唯一的,并且提供一個(gè)默認(rèn)值,默認(rèn)值可以是靜態(tài)值、函數(shù)、異步函數(shù)等
3.訂閱和更新?tīng)顟B(tài):三個(gè)常用API
1). useRecoilState:類(lèi)似useState的一個(gè)Hook,能夠取到Atom的值以及setter函數(shù)
2). useSetRecoilState:只獲取setter函數(shù),如果只是使用了這個(gè)函數(shù),狀態(tài)更新并不會(huì)引起組件重新渲染
3). useRecoilValue:只獲取狀態(tài)
import { nameState } from './store'
// useRecoilState
const NameInput = () => {
const [name, setName] = useRecoilState(nameState);
const onChange = (event) => {
setName(event.target.value);
};
return <>
<input type="text" value={name} onChange={onChange} />
<div>Name: {name}</div>
</>;
}
// useRecoilValue
const SomeOtherComponentWithName = () => {
const name = useRecoilValue(nameState);
return <div>{name}</div>;
}
// useSetRecoilState
const SomeOtherComponentThatSetsName = () => {
const setName = useSetRecoilState(nameState);
return <button onClick={() => setName('Jon Doe')}>Set Name</button>;
}
4.派生狀態(tài):與Mobx的computed類(lèi)似,selector表示一段派生狀態(tài),提供了get、set、分別定義如何賦值,如何取值,同時(shí)其與atom定義的一樣可以使用上述三種API。
const lengthState = selector({
key: 'lengthState',
get: ({get}) => {
const text = get(nameState);
return text.length;
},
});
function NameLength() {
const length = useRecoilValue(lengthState);
return <>Name Length: {length}</>;
}
5.異步狀態(tài):基于selector可以實(shí)現(xiàn)異步數(shù)據(jù)讀取,即修改get函數(shù)為異步函數(shù)
const userNameQuery = selector({
key: 'userName',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});
function CurrentUserInfo() {
const userName = useRecoilValue(userNameQuery);
return <div>{userName}</div>;
}
function MyApp() {
return (
<RecoilRoot>
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo />
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
);
}
其中,異步狀態(tài)可以被Suspense捕獲,異步過(guò)程報(bào)錯(cuò)可以被ErrorBoundary捕獲。
如果不想用Suspense異步阻塞,可以使用useRecoilValueLoadable在當(dāng)前組件內(nèi)管理異步狀態(tài)
不足
- Immutable壓力:API繁多,而且Immutable模式中,對(duì)于數(shù)據(jù)流只有讀和寫(xiě)兩種訴求,但是我們期待讀的含義是,UI能夠在訂閱其變化后自然而然Rerender。
Recoil提供了useRecoilState作為讀寫(xiě)雙重API,useRecoilValue只是簡(jiǎn)化了API,但是useSetRecoilValue在僅寫(xiě)不讀的場(chǎng)景下,是不會(huì)隨著狀態(tài)變更重新渲染組件的。
對(duì)比useState,他是單組件狀態(tài)管理的場(chǎng)景,但Recoil是全局狀態(tài)解決方案,讀寫(xiě)分離的場(chǎng)景下,對(duì)于只寫(xiě)的組件很有必要脫離對(duì)數(shù)據(jù)的訂閱實(shí)現(xiàn)性能最大化。 - 條件訪問(wèn)數(shù)據(jù):因?yàn)镠ooks的通病,無(wú)法寫(xiě)在條件語(yǔ)句中,所以要利用 Hooks 獲取一個(gè)帶有條件判斷的數(shù)據(jù)時(shí),必須回到 selector 模式。
從useRecoilState以及selector來(lái)看,相當(dāng)于Recoil對(duì)useContext和useMemo的封裝。
收獲
盡管短時(shí)間內(nèi)我們不會(huì)在項(xiàng)目上Recoil,但是它帶給我們的絕不只是上述的用法,在狀態(tài)管理上,我們或許可以思考新的出發(fā)點(diǎn):
- 讀與寫(xiě)分離,做到最優(yōu)按需渲染
- 派生的值必須嚴(yán)格緩存,并在命中緩存時(shí)引用保證嚴(yán)格相等
- 原子存儲(chǔ)的數(shù)據(jù)相互無(wú)關(guān)聯(lián),所有關(guān)聯(lián)的數(shù)據(jù)都使用派生值方式推導(dǎo)