反衝 0.0.9 和 0.0.10 現正推出,具備一些修正程式、TypeScript 支援以及 反衝快照 的全新 API,以觀察、檢查和管理全域反衝原子狀態。再次感謝協助讓這項工作成為可能的人,敬請期待更多精彩的進展。
錯誤修正
- 伺服器端呈現修正程式,儘管我們尚未正式支援。(#233, #220, #284) - 感謝 @fyber-LJX、@Chrischuck 和 @aulneau
- 修正部分情況下循序記錄依存關係訂閱的選擇器 (#296) - 感謝 @drarmstr
- 修正在
useRecoilCallback()
中更新程式取得目前狀態 (#260) - 感謝 @drarmstr - 修正開源建置中拋出特定錯誤時的錯誤訊息。(#199) - 感謝 @jonthomp
- 減少開源版本建置的 Flow 錯誤 (#308) - 感謝 @Komalov
改善
- 拋出具意義訊息的錯誤,如果使用者未於反衝鉤子中使用原子或選擇器 (#205) - 感謝 @alexandrzavalii
- 改善測試 (#321, #318, #294, #262, #295) - 感謝 @aaronabramov、@Komalov、@mondaychen、@drarmstr 和 @tyler-mitchell
- 改善開源建置 (#249, #203, #33) - 感謝 @tony-go、@acutmore 和 @jaredpalmer
TypeScript 支援
TypeScript 支援已納入反衝 GitHub 儲存庫,而非 DefinitelyTyped
,以協助其與 API 保持同步。(#292 和 #339) - 感謝 @csantos42
反衝快照
#312、#311、#310、#309、#260、#259、#258、#257、#256 - 感謝 @drarmstr 和團隊其餘成員
我們在 Recoil 中引入 Snapshot
的概念。Snapshot
是 Recoil 原子的狀態快照,且不可變。這旨在標準化觀察、檢查和管理全局 Recoil 狀態及衍生狀態的 API。對於開發工具、全局狀態同步、歷史記錄和導覽來說,非常有用。
API
讀取快照
Snapshot
類別提供以下方法,用於取得個別 Recoil 原子和選擇器的值
class Snapshot {
getLoadable: <T>(RecoilValue<T>) => Loadable<T>;
getPromise: <T>(RecoilValue<T>) => Promise<T>;
...
}
在原子狀態方面,快照是唯讀的。可用於讀取原子狀態,並評估選擇器衍生狀態。對於非同步選擇器,可以使用 getPromise()
方法,等待評估值,因此您可以看到選擇器值如何根據靜態原子狀態設定。
轉換快照
有時候您可能想變異快照。即使快照是不可變的,但它們有方法可以使用一組轉換,將自己對映至一項新的不可變快照。對映方法會採用一個回呼,將 MutableSnapshot
傳遞給它,整個回呼過程中會變異 MutableSnapshot
,而它最後會成為對映操作所傳回的新快照。
class Snapshot {
...
map: (MutableSnapshot => void) => Snapshot;
asyncMap: (MutableSnapshot => Promise<void>) => Promise<Snapshot>;
}
class MutableSnapshot {
set: <T>(RecoilState<T>, T | DefaultValue | (T => T | DefaultValue)) => void;
reset: <T>(RecoilState<T>) => void;
}
請注意,set()
和 reset()
具有和回寫選擇器的 set()
函數所提供的回呼相同簽章。
範例
const newSnapshot = snapshot.map(({set}) => set(myAtom, 42));
Hooks
Recoil 有以下 Hooks,可讓您使用快照
useRecoilSnapshot()
- 同步存取快照useRecoilCallback()
- 非同步存取快照useRecoilTransactionObserver()
- 訂閱所有狀態更新的快照useGotoRecoilSnapshot()
- 將目前狀態更新為符合快照
useRecoilSnapshot()
function useRecoilSnapshot(): Snapshot
您可以使用此 Hook,在繪製元件時同步取得目前狀態的快照。即使概念簡單,這個 Hook 會訂閱任何使用它的元件至任何 Recoil 狀態變更,因此始終使用目前狀態的快照進行繪製。因此,請小心使用這個 Hook。您可能想要用它的情況之一,是支援伺服器端繪製,當時您需要使用第一次繪製時進行同步狀態。在未來,我們可能會提供暫緩功能來提升效能。
範例
定義一個 <LinkToNewState>
元件,用帶有突變所套用當前狀態為基礎的 href
呈示一個 <a>
錨。在此範例中,uriFromSnapshot()
是一個使用者定義的函式,可用於編碼 URI 中的當前狀態,以便在載入網頁時還原。
function LinkToNewState() {
const snapshot = useRecoilSnapshot();
const newSnapshot = snapshot.map(({set}) => set(myAtom, 42));
return <a href={uriFromSnapshot(newSnapshot)}>Click Me!</a>;
}
這是簡化的範例。我們有一個協助程式,如我們瀏覽器記錄持久性程式庫中用於產生連結,它更具擴充性和最佳化,即將推出。例如,它將劫持按一下處理常式來更新區域狀態,而不需歷經瀏覽器記錄。
useRecoilCallback()
type CallbackInterface = {
snapshot: Snapshot,
gotoSnapshot: Snapshot => void,
set: <T>(RecoilState<T>, (T => T) | T) => void,
reset: <T>(RecoilState<T>) => void,
};
function useRecoilCallback<Args, Return>(
callback: CallbackInterface => (...Args) => ReturnValue,
deps?: $ReadOnlyArray<mixed>,
): (...Args) => ReturnValue
useRecoilCallback()
掛鉤類似於 React useCallback()
掛鉤用於產生一個回呼函式。但是,您並非只是提供一個輸入回呼函式,而是用一個提供回呼介面參數的函式對其進行包裝,讓您能夠存取 Snapshot
和 set()
/reset()
回呼函式來更新當前全域狀態。提供的 Snapshot
代表回呼函式呼叫時,而非最初建立回呼函式時的狀態。
注意:這是 API 中一個稍有變更的部分,但是我們仍在 Recoil 的 0.0.x
版本,尚未完全開始語意版本控管。
useRecoilCallback()
也採用一個可選的 deps
陣列參數來控制備忘。您可以擴充 react-hooks/exhaustive-deps
棉絮法則,以確保適當使用它。
使用 useRecoilCallback()
的一些動機
非同步使用 Recoil 狀態,如果原子或選擇器已更新,而不會訂閱一個 React 元件以重新呈示。
將昂貴的尋找遞延到一個您不希望在呈示時間執行的非同步動作。
執行副作用,您希望同時讀取或寫入 Recoil 狀態。
動態更新原子或選擇器,在呈示時間,我們可能不知道我們想要更新哪個原子或選擇器,因此我們無法使用
useSetRecoilState()
。於呈示前預先擷取
範例
按一下時,將評估一個昂貴選擇器的按鈕元件。
function ShowDetailsButton() {
const onClick = useRecoilCallback(({snapshot}) => async () => {
const data = await snapshot.getPromise(expensiveQuery);
showPopup(data);
});
return <button onClick={onClick}>Show Details</button>;
}
useRecoilTransactionObserver()
function useRecoilTransactionObserver_UNSTABLE(({
snapshot: Snapshot,
previousSnapshot: Snapshot,
}) => void)
此掛鉤訂閱一個回呼函式,以便在 Recoil 原子狀態發生變更時隨時執行。可能會在一個交易中將多個更新批次編組在一起。此掛鉤非常適合永久保留狀態變更、開發工具、建立記錄等用途。在未來,我們可能會允許訂閱特定條件或提供效能減緩。
偵錯觀察者範例
function DebugObserver() {
useRecoilTransactionObserver_UNSTABLE(({snapshot}) => {
window.myDebugState = {
a: snapshot.getLoadable(atomA).contents,
b: snapshot.getLoadable(atomB).contents,
};
});
return null;
}
useGotoRecoilState()
function useGotoRecoilSnapshot(): Snapshot => void
這個鉤子回傳一個回呼,該回呼將 Snapshot
作為參數並將更新目前的 <RecoilRoot>
狀態,以匹配此原子狀態。
時間旅行範例
狀態變更歷程範例清單,具有返回並復原先前狀態的功能。
function TimeTravelObserver() {
const [snapshots, setSnapshots] = useState([]);
useRecoilTransactionObserver_UNSTABLE(({snapshot}) => {
setSnapshots([...snapshots, snapshot]);
});
const gotoSnapshot = useGotoRecoilSnapshot();
return (
<ol>
{snapshots.map((snapshot, i) => (
<li key={i}>
Snapshot {i}
<button onClick={() => gotoSnapshot(snapshot)}>
Restore
</button>
</li>
)}
</ol>
);
}
狀態初始化
<RecoilRoot>
元件也有 initializeState
道具,可以用來初始化原子狀態。此道具採用一個函式,其參數是一個 MutableSnapshot
,可以用來設定初始原子狀態。當你事先知道所有原子時,這會有助於載入持續的狀態。這對於伺服器端渲染很有用,其中狀態應在第一次渲染時同步設定。
範例
function MyApp() {
return (
<RecoilRoot
initializeState={({set}) => {
for (const [atom, value] of atoms) {
set(atom, value);
}
}}
>
<AppContents />
</RecoilRoot>
);
}
接下來是什麼?
快照讓我們可以觀察和同步全域狀態。但如果我們想要一個更細緻、可組合的系統來處理個別的原子,怎麼辦?我們正在研究 原子效應 的概念,用於在原子層級觀察和處理副作用。這將使持續狀態或雙向與可變儲存同步更容易。想想同步狀態與瀏覽器 URI 歷程、瀏覽器本機儲存、RESTful API 等。很快就會推出!
這裡介紹的 Snapshot
API 讓我們可以檢查個別原子和選擇項目前的狀態。我們將擴充此 API,以檢查可用的節點集合並探索資料流程圖結構。這對開發工具建置而言非常強大。敬請期待!
當然囉,我們仍舊持續對應 React Concurrent Mode,並致力於改善速度、擴充性和記憶體管理。