說到狀態管理器,輪子滿天飛。在 Class 時代,redux 與 mobx 幾乎佔據了所有市場,幾乎沒有沒用過 redux 的同窗。隨着 Hooks 的誕生,新的一批輪子應運而生,其中有表明性的有 unstated-next、constate 等等。
固然不管什麼輪子,要解決的問題都是同樣的:跨組件狀態共享。在解決這個核心問題的同時,須要儘量的知足如下幾個特性:
前端
-
TypeScript 支持 -
友好的異步支持 -
支持狀態互相依賴 -
同時支持 Class 與 Hooks 組件 -
使用簡單
Recoil 體驗
最近,facebook 官方出了一個狀態管理器解決方案 Recoil[1],咱們來體驗一下。react
準備工做
使用 Recoil,咱們須要在項目最外層包一個 RecoilRoot
,這個和大部分狀態管理器一致,經過 context 來跨組件傳遞數據。git
import React from 'react';
import { RecoilRoot } from 'recoil';
function App() {
return (
<RecoilRoot>
...
</RecoilRoot>
);
}
跨組件狀態共享
狀態最簡單的就是定義和使用。在 Recoil 中,經過 atom
來定義一個狀態。github
const inputValueState = atom({
key: "inputValue",
default: ""
});
如上面的代碼所示,咱們定義了一個 inputValue
狀態,它的默認值是空字符串。
須要注意的是 key
字段,它應該是全局惟一的。這個 key
主要爲了 debug 方便,持久化數據(數據恢復時的惟一標識),以及能夠方便的看到全局 atoms 樹。
消費狀態也比較簡單,經過 useRecoilState
來消費狀態。web
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;
是否是很簡單?Recoil 的基礎用法就是這樣的。我在這裏寫了一個 demo[2],你能夠體驗下。
typescript
狀態互相依賴
有些狀態須要依賴其它狀態,這時候就要用 selector
來定義這個狀態了。
好比,咱們須要定義一個新的狀態 filterdInputValue
,它是過濾 inputValue
中的數字後的值。redux
const filterdInputValue = selector({
key: "filterdInputValue",
get: ({get}) => {
// 經過 get 能夠讀取其它狀態
const inputValue = get(inputValueState);
return inputValue.replace(/[0-9]/ig, "");
},
});
selector
比較簡單,就是爲了實現狀態的依賴。你能夠在這個 demo[3] 體驗下。
微信
異步支持
良好的異步請求支持是狀態管理器必不可少的。Recoil 提供了一個 useRecoilValueLoadable
來處理異步請求。直接上例子:app
const currentUserNameQuery = selector({
key: "CurrentUserName",
get: async () => {
const response = await queryUserInfo();
return response.name;
}
});
咱們須要經過 selector
來定義異步狀態,若是 get
函數是一個 Promise,則表明該狀態爲異步狀態,須要使用 useRecoilValueLoadable
來消費該狀態。less
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
返回的狀態,能夠經過 state
字段讀取到異步請求的狀態。我寫了個 demo[4],你能夠體驗下。
固然經過 useRecoilValueLoadable
來消費異步狀態,比較符合咱們當前的習慣。但 Recoil 更推薦經過 React.Suspense
來消費異步狀態,這裏就仁者見仁了,雖然 Suspense
多是方向,但用起來是還不太習慣。
const UserName = () => {
const userName = useRecoilValue(currentUserNameQuery);
return <>{userName}</>
}
};
function MyApp() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<UserName />
</React.Suspense>
);
}
評價
優勢
-
以前狀態管理器滿天飛,若是官方能一統天下,應該算一件好事情。 -
對 React concurrent 模式支持良好。
不足
當前 Recoil 還處於開發階段,文檔都還不是很全。基於現狀,說幾點個人感覺。
1. 沒有使用 ts 實現,目前不支持 ts
這點我很驚訝,也是寫這個文章的時候才發現的,很奇怪。講道理 Recoil 支持 typescript 應該是順手的事情,可能後期須要來個 @types/recoil
吧。
2. 目前沒有支持 Class 組件消費狀態。
這個特性應該是必備的,應該不會完全拋棄 Class 組件。估計下個版本確定會支持的這個特性的。實現成本較低,不支持的話就太反人類了。
3. API 偏多,有必定上手成本。
各種 API 一共有 19 個,偏複雜了。感受不少都是能夠合併的,好比 atom
和 selector
合併成一個等等(也多是我考慮不成熟)。建議官方能夠考慮精簡精簡,原本是一個很簡單的東西,搞的太複雜了。
4. 消費較繁瑣
咱們須要消費一個狀態的時候,須要 import 兩個東西,比較繁瑣。
import { useRecoilState } from "recoil";
import { inputValueState } from "../store";
// 用法
useRecoilState(inputValueState);
原本應該能夠直接經過字符串 key
消費的,但這樣和 redux 問題同樣了,沒法支持 ts。
import { useRecoilState } from "recoil";
useRecoilState('inputValueState');
不管若是,import 兩個東西不是一個好的用法。
5. 沒有足夠的亮點
沒有看到讓人眼前一亮的東西,沒有使用衝動。靜觀發展~
後記
Recoil 總體看下來,比較中庸,須要靜觀發展。
另外推薦一下我目前正在用的最簡單的 React 狀態管理器 hox[5],只有一個 API,很是符合直覺,沒有任何上手成本,徹底擁抱 Hooks 😋。
Reference
Recoil: https://recoiljs.org/
[2]demo: https://codesandbox.io/s/recoil-input-demo-7vly9?file=/src/components/InputA.js
[3]demo: https://codesandbox.io/s/recoil-selector-demo-sn2kw?file=/src/App.js
[4]demo: https://codesandbox.io/s/recoil-async-demo-qu6vw?file=/src/App.js
[5]hox: https://github.com/umijs/hox
◆ ◆ ◆ ◆ ◆
學習交流
-
關注公衆號【前端宇宙】,每日獲取好文推薦 -
添加微信,入羣交流
本文分享自微信公衆號 - 前端宇宙(gh_8184da923ced)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。