持久化 atom
在默认情况下,所有的 atom 都是保存在内存中的,一旦离开页面或者页面发生刷新,atom 的内容就恢复成初始值了。但是在实际使用中往往会对一些数据存在持久化的需求。对于浏览器来说,持久化的数据一般都是放置在localStorage
、sessionStorage
或者是IndexedDB
中。
如果在一个 atom 的 read 函数和 write 函数中编写访问localStorage
的功能,固然是可以实现将 atom 的内容持久化到localStorage
的功能,但是对于原子化的状态来说,可能需要持久化的 atom 有数个,逐一编写相同的功能非常不现实也不明智。所以此时就需要用到jotai/utils
中提供的atomWithStorage
函数来提供可以将内部数据保存到localStorage
或者sessionStorage
能力的 atom。
atomWithStorage
函数接受四个参数:
key
,这是 atom 的内容在持久化存储中使用的键值,构建 atom 时必须提供。initialValue
,这是 atom 内容的初始化值,构建 atom 时必须提供。storage
,要持久化到的 Storage 的实现,构建 atom 时不必提供,在不提供的时候 Jotai 会默认将 atom 的内容持久化到localStorage
。options
,用于提供给atomWithStorage
的选项,构建 atom 时不必提供。options
时一个对象,可以使用以下内容对 atom 的运作特性进行配置。getOnInit
,一个布尔类型的值,在不设置或者设置为false
的时候,表示 atom 在初始化的时候只会返回初始值,不会主动去存储中获取值。如果更希望 atom 积极从存储中获取值,可以将此项配置设置为true
。
所以atomWithStorage
最简单的使用方式就是直接提供key
和initialValue
两个参数即可。
以下模拟一个将主题配置保存到localStorage
的示例。
const themeAtom = atomWithStorage("theme", "dark");
// 如果想积极从 localStorage 中读取值,可以使用以下方式定义 atom
const themeAtom = atomWithStorage("theme", "dark", undefined, {
getOnInit: true,
});
const App = () => {
const [theme, setTheme] = useAtom(themeAtom);
return (
<div data-theme={theme}>
{theme === "light" && (
<button onClick={() => setTheme("dark")}>Dark</button>
)}
{theme === "dark" && (
<button onClick={() => setTheme("light")}>Light</button>
)}
</div>
);
};
在这个示例中可以看出,支持持久化的 atom 在使用的时候与普通的 atom 没有任何两样,在对 atom 进行写入操作的时候,atom 会自动将其持有的内容写入相应的存储中。
持久化内容的删除
定义好的 atom 在使用set
方法设置其值的时候,无论设置为空白字符串或者是null
,都表示在localStorage
里存在一个值。但是有一些情况下我们的确需要将存储中的内容删去。这时就需要利用到jotai/utils
中定义的RESET
信号值了。
例如在上面的示例中调用setTheme(RESET)
就可以将localStorage
中的键为theme
的记录删去。
自定义 Storage
Jotai 默认提供的存储实现是localStorage
,如果需要使用其他的存储实现,那么就需要自行动手实现或者寻找其他人已经完成的实现。
在原理上,要使用一个自定义的存储,只需要在定义 atom 的时候传入第三个参数来指定要使用的storage
即可。这个storage
参数的定义在 Jotai 里也有非常明确的定义。任何一个storage
参数都必须提供以下几个方法。
getItem(key, initialValue)
,用于从存储中获取键对应的值,如果键不存在,将返回initialValue
,这个方法是必须提供的。setItem(key, value)
,用于将指定值放入存储的对应键,这个方法也是必须提供的。removeItem(key)
,用于从存储中删除指定的键,这个方法也是必须提供的。subscribe(key, callback, initialValue)
,用于订阅存储更新的方法,这个方法是可选的。
从上面这四个需要提供的方法上看,实现一个自定义的storage
还是十分容易的,不过 Jotai 提供了另外一个工具函数createJSONStorage
,如果是以 JSON 格式存储数据,那么可以更方便的完成storage
的自定义。
createJSONStorage
的函数签名如下:
function createJSONStorage<Value>(
getStringStorage: () => AsyncStringStorage | SyncStringStorage,
options?: {
reviver?: (key: string, value: unknown) => unknown;
replacer?: (key: string, value: unknown) => unknown;
}
): AsyncStorage<Value> | SyncStorage<Value>;
createJSONStorage
的函数签名中所出现的AsyncStringStorage
和SyncStringStorage
就是上文中所提到的实现了四种指定方法的使用字符串进行数据存储的storage
,默认使用的是window.localStorage
。options
里的reviver
是用来将字符串信息转换为对象的,默认实现是JSON.parse
,replacer
是用来将一个对象转换为字符串的,默认实现为JSON.stringify
。
如果要使用其他字符串以外的格式存储数据,那么就不能使用createJSONStorage
来进行storage
的自定义了。