recoil atom effects——迹忆客-ag捕鱼王app官网
atom effects 是一个新的实验性 api,用于管理副作用和初始化 recoil atom。它们有很多有用的应用,比如状态持久化、状态同步、管理历史、日志等。它们被定义为 atom 定义的一部分,所以每个 atom 都可以指定和组成它们自己的策略。这个 api 目前仍在发展中,因此被标记为 _unstable
。
atom effect 是一个 函数,其定义如下:
type atomeffect = ({
node: recoilstate, // 对 atom 本身的引用
trigger: 'get' | 'set', // 触发 atom 初始化的行动
// 用于设置或重置 atom 值的回调。
// 可以从 atom effect 函数中直接调用,以初始化
// atom 的初始值,或者在以后异步调用以改变它。
setself: (
| t
| defaultvalue
| promise // 目前只能用于初始化
| ((t | defaultvalue) => t | defaultvalue),
) => void,
resetself: () => void,
// 订阅 atom 值的变化。
// 由于这个 effect 自己的 setself() 的变化,该回调没有被调用。
onset: (
(newvalue: t | defaultvalue, oldvalue: t | defaultvalue) => void,
) => void,
}) => void | () => void; // 可以返回一个清理程序
atom effects 通过 effects_unstable
选项附加到 。每个 atom 都可以引用这些 atom effect 函数的一个数组,当 atom 被初始化时,这些函数会按优先级顺序被调用。atom 在
内首次使用时被初始化,但如果它们未被使用并被清理,则可再次被重新初始化。atom effect 函数可以返回一个可选的清理处理程序来管理清理的副作用。
const mystate = atom({
key: 'mykey',
default: null,
effects_unstable: [
() => {
...effect 1...
return () => ...cleanup effect 1...;
},
() => { ...effect 2... },
],
});
atom 族 也支持参数化以及非参数化的 effect :
const mystatefamily = atomfamily({
key: 'mykey',
default: null,
effects_unstable: param => [
() => {
...effect 1 using param...
return () => ...cleanup effect 1...;
},
() => { ...effect 2 using param... },
],
});
与 react effects 相比
atom effect 大多可以通过 react useeffect() 来实现。然而,这组 atom 是在 react 上下文之外创建的,从 react 组件中管理 effect 会很困难,特别是对于动态创建的 atom。它们也不能用于初始化初始 atom 值或用于服务器端的渲染。使用 atom effect 还可以将 effect 与 atom 定义一起定位。
const mystate = atom({key: 'key', default: null});
function mystateeffect(): react.node {
const [value, setvalue] = userecoilstate(mystate);
useeffect(() => {
// 当 atom 值改变时被调用
store.set(value);
store.onchange(setvalue);
return () => { store.onchange(null); }; // 清理 effect
}, [value]);
return null;
}
function myapp(): react.node {
return (
...
);
}
与 snapshots 相比
snapshot hooks api 也可以监视 atom 的状态变化,并且
中的 initializestate prop 可以初始化初始渲染值。不过,这些 api 监控所有的状态变化,在管理动态 atom —— 特别是 atom 族时 —— 可能会很尴尬。有了 atom effect,副作用可以与 atom 定义一起按 atom 定义,多个规则的组成会变得很容易。
日志示例
一个使用 atom effects 记录 atom 状态变化的简单例子:
const currentuseridstate = atom({
key: 'currentuserid',
default: null,
effects_unstable: [
({onset}) => {
onset(newid => {
console.debug("current user id:", newid);
});
},
],
});
历史示例
一个更复杂的日志例子可能会维护一个不断变化的历史。这个例子提供了一个维护状态变化的历史队列的 effect,并有回调处理程序来撤销该特定变化。
const history: array<{
label: string,
undo: () => void,
}> = [];
const historyeffect = name => ({setself, onset}) => {
onset((newvalue, oldvalue) => {
history.push({
label: `${name}: ${json.serialize(oldvalue)} -> ${json.serialize(newvalue)}`,
undo: () => {
setself(oldvalue);
},
});
});
};
const userinfostate = atomfamily({
key: 'userinfo',
default: null,
effects_unstable: userid => [
historyeffect(`${userid} user info`),
],
});
状态同步示例
使用 atom 作为其他一些状态的本地缓存值可能很有用,比如远程数据库、本地存储等。你可以使用 default 属性设置 atom 的默认值,并使用选择器来获取储存的值。然而,这只是一次性的查找;如果储存的值改变了,atom 的值也不会改变。通过 effect ,我们可以订阅储存,并在储存改变时更新 atom 的值。从 effect 中调用 setself()
会将 atom 初始化为该值,并将用于初始渲染。如果 atom 被重置,它将恢复到 default
值,而不是初始化值。
const syncstorageeffect = userid => ({setself, trigger}) => {
// 将 atom 值初始化为远程存储状态
if (trigger === 'get') { // 避免耗时的初始化
setself(myremotestorage.get(userid)); // 同步调用以初始化
}
// 订阅远程存储变化并更新 atom 值
myremotestorage.onchange(userid, userinfo => {
setself(userinfo); // 异步调用以改变值
});
// 清理远程存储订阅
return () => {
myremotestorage.onchange(userid, null);
};
};
const userinfostate = atomfamily({
key: 'userinfo',
default: null,
effects_unstable: userid => [
historyeffect(`${userid} user info`),
syncstorageeffect(userid),
],
});
直写式缓存实例
我们还可以将 atom 值与远程存储进行双向同步,因此服务器上的变化会更新 atom 值,而本地 atom 的变化会写回到服务器上。当通过该 effect 的 setself()
改变时,该 effect 将不调用 onset()
处理程序,以帮助避免反馈循环。
const syncstorageeffect = userid => ({setself, onset, trigger}) => {
// 将 atom 值初始化为远程存储状态
if (trigger === 'get') { // 避免耗时的初始化
setself(myremotestorage.get(userid)); // 同步调用以初始化
}
// 订阅远程存储变化并更新 atom 值
myremotestorage.onchange(userid, userinfo => {
setself(userinfo); // 异步调用以改变值
});
// 订阅本地变化并更新服务器值
onset(userinfo => {
myremotestorage.set(userid, userinfo);
});
// 清理远程存储订阅
return () => {
myremotestorage.onchange(userid, null);
};
};
本地存储的持久性
atom effect 可以用 浏览器本地存储 来持久化 atom 状态。localstorage
是同步的,所以我们可以直接检索数据而不需要 async
、await
或 promise
。
请注意,以下例子是为说明问题而简化的,并不包括所有情况:
const localstorageeffect = key => ({setself, onset}) => {
const savedvalue = localstorage.getitem(key)
if (savedvalue != null) {
setself(json.parse(savedvalue));
}
onset(newvalue => {
if (newvalue instanceof defaultvalue) {
localstorage.removeitem(key);
} else {
localstorage.setitem(key, json.stringify(newvalue));
}
});
};
const currentuseridstate = atom({
key: 'currentuserid',
default: 1,
effects_unstable: [
localstorageeffect('current_user'),
]
});
异步存储持久性
如果你的持久化数据需要异步检索,你可以在 setself() 函数中 使用 promise 或者 异步 调用它。
下面我们将使用 asynclocalstorage
或 localforage
作为一个异步存储的例子。
使用 promise 进行初始化
通过同步调用 setself() 和 promise,你将能够用
组件包裹
内的组件,在等待 recoil 加载持久值时显示一个回退。
将显示一个回退,直到提供给 setself()
的 promise 被解决。如果 atom 在 promise 解析之前被设置为一个值,那么初始化的值将被忽略。
请注意,如果 atom 后来被 “重置”,它们将恢复到其默认值,而不是初始化值。
const localforageeffect = key => ({setself, onset}) => {
setself(localforage.getitem(key).then(savedvalue =>
savedvalue != null
? json.parse(savedvalue)
: new defaultvalue() // 如果没有存储值,则终止初始化
));
onset(newvalue => {
if (newvalue instanceof defaultvalue) {
localstorage.removeitem(key);
} else {
localstorage.setitem(key, json.stringify(newvalue));
}
});
};
const currentuseridstate = atom({
key: 'currentuserid',
default: 1,
effects_unstable: [
localforageeffect('current_user'),
]
});
异步 setself()
通过这种方法,你可以在值可用时异步调用 setself()。与初始化为 promise 不同,最初将使用 atom 的默认值,所以
不会显示回退,除非 atom 的默认值是 promise 或异步 selector。如果 atom 在调用 setself()
之前被设置为一个值,那么它将被 setself()
覆盖。这种方法不仅限于 await,也适用于任何 setself()
的异步使用,例如 settimeout()
。
const localforageeffect = key => ({setself, onset}) => {
/** 如果有一个持久化的值,在加载时设置它 */
const loadpersisted = async () => {
const savedvalue = await localforage.getitem(key);
if (savedvalue != null) {
setself(json.parse(savedvalue));
}
};
// 加载持久化的数据
loadpersisted();
onset(newvalue => {
if (newvalue instanceof defaultvalue) {
localforage.removeitem(key);
} else {
localforage.setitem(key, json.stringify(newvalue));
}
});
};
const currentuseridstate = atom({
key: 'currentuserid',
default: 1,
effects_unstable: [
localforageeffect('current_user'),
]
});
向后兼容
如果你改变了 atom 的格式怎么办?用新的格式但是有基于旧格式的 localstorage
加载一个页面可能会导致问题。但是,你可以建立 effect 来处理恢复和验证值的类型安全方式:
type persistenceoptions: {
key: string,
restorer: (mixed, defaultvalue) => t | defaultvalue,
};
const localstorageeffect = (options: persistenceoptions) => ({setself, onset}) => {
const savedvalue = localstorage.getitem(options.key)
if (savedvalue != null) {
setself(options.restorer(json.parse(savedvalue), new defaultvalue()));
}
onset(newvalue => {
if (newvalue instanceof defaultvalue) {
localstorage.removeitem(options.key);
} else {
localstorage.setitem(options.key, json.stringify(newvalue));
}
});
};
const currentuseridstate = atom({
key: 'currentuserid',
default: 1,
effects_unstable: [
localstorageeffect({
key: 'current_user',
restorer: (value, defaultvalue) =>
// 值目前是以数字形式持续存在的
typeof value === 'number'
? value
// 如果数值以前是作为字符串保存的,则将其解析为一个数字
: typeof value === 'string'
? parseint(value, 10)
// 如果值的类型不被识别,则使用 atom 的默认值。
: defaultvalue
}),
],
});
如果用来保存数值的 key 发生变化怎么办?过去用一个 key 来持久化的东西现在用了几个 key;反之亦然?这也可以用一种类型安全的方式来处理:
type persistenceoptions: {
key: string,
restorer: (mixed, defaultvalue, map) => t | defaultvalue,
};
const localstorageeffect = (options: persistenceoptions) => ({setself, onset}) => {
const savedvalues = parsevaluesfromstorage(localstorage);
const savedvalue = savedvalues.get(options.key);
setself(
options.restorer(savedvalue ?? new defaultvalue(), new defaultvalue(), savedvalues),
);
onset(newvalue => {
if (newvalue instanceof defaultvalue) {
localstorage.removeitem(options.key);
} else {
localstorage.setitem(options.key, json.stringify(newvalue));
}
});
};
const currentuseridstate = atom({
key: 'currentuserid',
default: 1,
effects_unstable: [
localstorageeffect({
key: 'current_user',
restorer: (value, defaultvalue, values) => {
if (typeof value === 'number') {
return value;
}
const oldvalue = values.get('old_key');
if (typeof oldvalue === 'number') {
return oldvalue;
}
return defaultvalue;
},
}),
],
});
浏览器 url 历史的持久化
atom effects 也可以持久化并与浏览器的 url 历史同步。这对于让状态变化更新当前的 url 是很有用的,因为这样就可以保存或与他人分享以恢复该状态。它还可以与浏览器历史记录整合,以利用浏览器的前进/后退按钮。提供这种类型的持久性的例子或库即将推出……。