作者: 磚家
轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/cjwv25hSLBsUQ9m5De6vUg
前言
說到狀態(tài)管理器,輪子滿天飛。在 Class 時(shí)代,redux 與 mobox 幾乎占據(jù)了全部市場(chǎng),幾乎沒有沒用過 redux 的同學(xué)。隨著 Hooks 的誕生,新的一批輪子應(yīng)運(yùn)而生,其中有代表性的有 unstated-next、constate 等等。當(dāng)然無論什么輪子,要解決的問題都是一樣的:跨組件狀態(tài)共享。在解決這個(gè)核心問題的同時(shí),需要盡可能的滿足以下幾個(gè)特性:
- TypeScript 支持
- 友好的異步支持
- 支持狀態(tài)互相依賴
- 同時(shí)支持 Class 與 Hooks 組件
- 使用簡(jiǎn)單
Recoil 體驗(yàn)
最近,facebook 官方出了一個(gè)狀態(tài)管理器解決方案 Recoil[1],我們來體驗(yàn)一下。
準(zhǔn)備工作
使用 Recoil,我們需要在項(xiàng)目最外層包一個(gè) RecoilRoot ,這個(gè)和大部分狀態(tài)管理器一致,通過 context 來跨組件傳遞數(shù)據(jù)。
import React from 'react';
import { RecoilRoot } from 'recoil';
function App() {
return (
<RecoilRoot>
...
</RecoilRoot>
);
}
跨組件狀態(tài)共享
狀態(tài)最簡(jiǎn)單的就是定義和使用。在 Recoil 中,通過 atom 來定義一個(gè)狀態(tài)。
const inputValueState = atom({
key: "inputValue",
default: ""
});
如上面的代碼所示,我們定義了一個(gè) inputValue 狀態(tài),它的默認(rèn)值是空字符串。需要注意的是 key 字段,它應(yīng)該是全局唯一的。這個(gè) key 主要為了 debug 方便,持久化數(shù)據(jù)(數(shù)據(jù)恢復(fù)時(shí)的唯一標(biāo)識(shí)),以及可以方便的看到全局 atoms 樹。消費(fèi)狀態(tài)也比較簡(jiǎn)單,通過 useRecoilState 來消費(fèi)狀態(tài)。
import React from "react";
import { useRecoilState } from "recoil";
import { inputValue } from "../store";
const InputA = () => {
const [value, setValue] = useRecoilState(inputValueState);
return <input value={value} onChange={e => setValue(e.target.value)} />;
};
export default InputA;
是不是很簡(jiǎn)單?Recoil 的基礎(chǔ)用法就是這樣的。我在這里寫了一個(gè) demo[2],你可以體驗(yàn)下。
狀態(tài)互相依賴
有些狀態(tài)需要依賴其它狀態(tài),這時(shí)候就要用 selector 來定義這個(gè)狀態(tài)了。比如,我們需要定義一個(gè)新的狀態(tài) filterdInputValue ,它是過濾 inputValue 中的數(shù)字后的值。
const filterdInputValue = selector({
key: "filterdInputValue",
get: ({get}) => {
// 通過 get 可以讀取其它狀態(tài)
const inputValue = get(inputValueState);
return inputValue.replace(/[0-9]/ig, "");
},
});
selector 比較簡(jiǎn)單,就是為了實(shí)現(xiàn)狀態(tài)的依賴。你可以在這個(gè) demo[3] 體驗(yàn)下。
異步支持
良好的異步請(qǐng)求支持是狀態(tài)管理器必不可少的。Recoil 提供了一個(gè) useRecoilValueLoadable 來處理異步請(qǐng)求。直接上例子:
const currentUserNameQuery = selector({
key: "CurrentUserName",
get: async () => {
const response = await queryUserInfo();
return response.name;
}
});
我們需要通過 selector 來定義異步狀態(tài),如果 get 函數(shù)是一個(gè) Promise,則代表該狀態(tài)為異步狀態(tài),需要使用 useRecoilValueLoadable 來消費(fèi)該狀態(tài)。
const UserName = () => {
const userNameLoadable = useRecoilValueLoadable(currentUserNameQuery);
switch (userNameLoadable.state) {
case "hasValue":
return <div>{userNameLoadable.contents}</div>;
case "loading":
return <div>Loading...</div>;
case "hasError":
throw userNameLoadable.contents;
}
};
從上面例子可以看到, useRecoilValueLoadable 返回的狀態(tài),可以通過 state 字段讀取到異步請(qǐng)求的狀態(tài)。我寫了個(gè) demo[4],你可以體驗(yàn)下。

當(dāng)然通過 useRecoilValueLoadable 來消費(fèi)異步狀態(tài),比較符合我們當(dāng)前的習(xí)慣。但 Recoil 更推薦通過 React.Suspense 來消費(fèi)異步狀態(tài),這里就仁者見仁了,雖然 Suspense 可能是方向,但用起來是還不太習(xí)慣。
const UserName = () => {
const userName = useRecoilValue(currentUserNameQuery);
return <>{userName}</>
}
};
function MyApp() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<UserName />
</React.Suspense>
);
}
評(píng)價(jià)
優(yōu)點(diǎn)
- 之前狀態(tài)管理器滿天飛,如果官方能一統(tǒng)天下,應(yīng)該算一件好事情。
- 對(duì) React concurrent 模式支持良好。
不足
當(dāng)前 Recoil 還處于開發(fā)階段,文檔都還不是很全。基于現(xiàn)狀,說幾點(diǎn)我的感受。
1. 沒有使用 ts 實(shí)現(xiàn),目前不支持 ts
這點(diǎn)我很驚訝,也是寫這個(gè)文章的時(shí)候才發(fā)現(xiàn)的,很奇怪。講道理 Recoil 支持 typescript 應(yīng)該是順手的事情,可能后期需要來個(gè) @types/recoil 吧。
2. 目前沒有支持 Class 組件消費(fèi)狀態(tài)。
這個(gè)特性應(yīng)該是必備的,應(yīng)該不會(huì)徹底拋棄 Class 組件。估計(jì)下個(gè)版本肯定會(huì)支持的這個(gè)特性的。實(shí)現(xiàn)成本較低,不支持的話就太反人類了。
3. API 偏多,有一定上手成本。

各類 API 一共有 19 個(gè),偏復(fù)雜了。感覺很多都是可以合并的,比如 atom 和 selector 合并成一個(gè)等等(也可能是我考慮不成熟)。建議官方可以考慮精簡(jiǎn)精簡(jiǎn),本來是一個(gè)很簡(jiǎn)單的東西,搞的太復(fù)雜了。
4. 消費(fèi)較繁瑣
我們需要消費(fèi)一個(gè)狀態(tài)的時(shí)候,需要 import 兩個(gè)東西,比較繁瑣。
import { useRecoilState } from "recoil";
import { inputValueState } from "../store";
// 用法
useRecoilState(inputValueState);
本來應(yīng)該可以直接通過字符串 key 消費(fèi)的,但這樣和 redux 問題一樣了,無法支持 ts。
import { useRecoilState } from "recoil";
useRecoilState('inputValueState');
無論如果,import 兩個(gè)東西不是一個(gè)好的用法。
5. 沒有足夠的亮點(diǎn)
沒有看到讓人眼前一亮的東西,沒有使用沖動(dòng)。靜觀發(fā)展~
后記
Recoil 整體看下來,比較中庸,需要靜觀發(fā)展。另外推薦一下我目前正在用的最簡(jiǎn)單的 React 狀態(tài)管理器 hox[5],只有一個(gè) API,非常符合直覺,沒有任何上手成本,完全擁抱 Hooks 。