Recoil 0.0.9 和 0.0.10 发布了,其中包含一些错误修复,TypeScript 支持,以及一个用于 Recoil 快照 的新 API,用于观察、检查和管理全局 Recoil 原子状态。再次感谢所有帮助实现这一目标的人,请继续关注即将推出的更多激动人心的发展!
错误修复
- 修复了服务器端渲染的错误,但我们目前还不正式支持它。 (#233, #220, #284) - 感谢 @fyber-LJX、@Chrischuck 和 @aulneau
- 修复了选择器在某些情况下记录依赖项订阅的错误 (#296) - 感谢 @drarmstr
- 修复了
useRecoilCallback()
中更新程序获取当前状态的错误 (#260) - 感谢 @drarmstr - 修复了在开源版本中抛出某些错误时的错误信息。 (#199) - 感谢 @jonthomp
- 减少了开源版本的 Flow 错误 (#308) - 感谢 @Komalov
改进
- 如果用户没有使用大多数 Recoil 钩子的原子或选择器,则会抛出一个带有有意义消息的错误 (#205) - 感谢 @alexandrzavalii
- 改进测试 (#321, #318, #294, #262, #295) - 感谢 @aaronabramov、@Komalov、@mondaychen、@drarmstr 和 @tyler-mitchell
- 改进开源版本 (#249, #203, #33) - 感谢 @tony-go、@acutmore 和 @jaredpalmer
TypeScript 支持
TypeScript 支持将被整合到 Recoil GitHub 仓库中,而不是 DefinitelyTyped
,这将有助于更好地与 API 保持同步。 (#292 & #339) - 感谢 @csantos42
Recoil 快照
#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
,该快照在整个回调函数中被变异,最终将成为映射操作返回的新的快照。
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));
钩子
Recoil 具有以下用于处理快照的钩子
useRecoilSnapshot()
- 对快照的同步访问useRecoilCallback()
- 对快照的异步访问useRecoilTransactionObserver()
- 订阅所有状态更新的快照useGotoRecoilSnapshot()
- 将当前状态更新为与快照匹配
useRecoilSnapshot()
function useRecoilSnapshot(): Snapshot
您可以使用此钩子在渲染组件时同步获取当前状态的快照。虽然概念上很简单,但此钩子将订阅使用它的任何组件到任何 Recoil 状态更改,因此它始终使用当前状态的快照进行渲染。因此,请谨慎使用此钩子。当您可能希望使用它的一个示例是支持服务器端渲染,您需要在首次渲染时同步获取状态。将来,我们可能会提供一种用于性能优化的去抖动功能。
示例
定义一个 <LinkToNewState>
组件,该组件渲染一个 <a>
锚点,其 href
基于当前状态并应用了变异。在此示例中,uriFromSnapshot()
是某个用户定义的函数,它将当前状态编码在 URI 中,该 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
lint 规则以确保它被正确使用。
使用 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 并发模式的支持以及改进速度、可扩展性和内存管理。