从 Recoil 到 Jotai(下)
接着上一篇,我们来聊聊 Recoil
到 Jotai
,由于下篇属于 高级用法,我会穿插一些原理以及个人的想法(有错误可以指正)。
selector
-> atom
派生状态,顾名思义就是从一个基础的状态,派生为其他更多的状态,有点类似面向对象的继承,其实都是一个意思。
recoil
const todoListFilterState = atom({
key: 'TodoListFilter',
default: 'Show All',
});
const filteredTodoListState = selector({
key: 'FilteredTodoList',
get: ({get}) => {
const filter = get(todoListFilterState);
const list = get(todoListState);
switch (filter) {
case 'Show Completed':
return list.filter((item) => item.isComplete);
case 'Show Uncompleted':
return list.filter((item) => !item.isComplete);
default:
return list;
}
},
});
jotai
import { selectAtom } from 'jotai/utils'
import { atom } from 'jotai'
export const baseAtom = atom(Math.random());
export const oneAtom = selectAtom(baseAtom, val => val);
export const twoAtom = selectAtom(baseAtom, val => val*2);
但是 recoil
和 jotai
的还是有一些区别的,可以看到代码中, recoil
内部有 get
方法可以直接获取到多个原子的状态。但是 jotai
仅能获取当前传入的。
那我们有办法吗?请看下文:
export const baseAtom = atom(Math.random());
export const baseAtom1 = atom(Math.random());
export const oneAtom = selectAtom(baseAtom, val => val);
export const twoAtom = atom<any, any, any>(get => get(baseAtom)*8, (get, set, params) => {
set(baseAtom, params)
})
我们的 atom
具备四个能力:
- 原始原子(拥有初始值,仅作存储,可以随意塞值)
- 读原子
- 写原子
- 读写原子
其实这里在使用上还是有困惑点的,也就是 selectAtom
的能力其实仅仅就是个派生+监听+equal ,不具备写能力。
这点已经询问过 dai-shi
了:
atomFamily
-> atomFamily
atomFamily
的定义就是返回一个带有写原子能力的函数。
recoil
和 jotai
,前者自己做了 memo
,后者需要手动去做 memo
recoil
const elementPositionStateFamily = atomFamily({
key: 'ElementPosition',
default: : param => defaultBasedOnParam(param)
});
function ElementListItem({elementID}) {
const position = useRecoilValue(elementPositionStateFamily(elementID));
return (
<div>
Element: {elementID}
Position: {position}
</div>
);
}
jotai
import { atom } from 'jotai'
import { atomFamily } from 'jotai/utils'
import deepEqual from 'fast-deep-equal'
const fooFamily = atomFamily((param) => atom(param), deepEqual)
有同学可能对于此处的 deepEqual
不是很理解。
简单理解就是:根据请求的 param
去作 Map 存储,当相同的时候就从缓存里拿,不再重新创建新的原子,这个时候 params 为 object
时候,尤为有效。
但是存在内存泄漏风险,不论有没有添加 deepEqual
函数,因为始终是存在 map
里的,所以我们要在恰当的时机去清除。否则就会内存泄漏。
当然我是有一个 💡,自己撸一个
atomFamily
,结合Provider
的能力,变更Provider
之后,自动清理内部的Map
,但是这跟设计又有点强耦合了(:有待商榷
那么 recoil
是如何实现的呢?
本质还是借助 Map
去实现的,但是 key 有些不一样,你可以自己传入一个 keyMapper
函数过滤键值。
其实两者有异曲同工之妙啊。。。大家有没有发现。
Loadable
recoil
useRecoilStateLoadable
| useRecoilValueLoadable
function UserInfo({userID}) {
const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
}
jotai
import { loadable } from "jotai/utils"
const asyncAtom = atom(async (get) => ...)
const loadableAtom = loadable(asyncAtom);
// Does not need to be wrapped by a <Suspense> element
const Component = () => {
const [value] = useAtom(loadableAtom)
if (value.state === 'hasError') return <Text>{value.error}</Text>
if (value.state === 'loading') {
return <Text>Loading...</Text>
}
console.log(value.data) // Results of the Promise
return <Text>Value: {value.data}</Text>
}
相比较于 recoil
,jotai
的组合看起来更便捷,但是 recoil
同样方便,直接使用特定的 API
集成。
useRecoilRefresher_UNSTABLE
-> atomWithRefresh
recoil
const myQuery = selector({
key: 'MyQuery',
get: () => fetch(myQueryURL),
});
function MyComponent() {
const data = useRecoilValue(myQuery);
const refresh = useRecoilRefresher_UNSTABLE(myQuery);
return (
<div>
Data: {data}
<button onClick={() => refresh()}>Refresh</button>
</div>
);
}
jotai
import { atom, Getter } from 'jotai'
export function atomWithRefresh<T>(fn: (get: Getter) => T) {
const refreshCounter = atom(0)
return atom(
(get) => {
get(refreshCounter)
return fn(get)
},
(_, set) => set(refreshCounter, (i) => i + 1)
)
}
import { atomWithRefresh } from 'XXX'
const postsAtom = atomWithRefresh((get) =>
fetch('https://jsonplaceholder.typicode.com/posts').then((r) => r.json())
)
好了,到此为止,常用的 API
类的过渡差异基本结束,下一篇我会从《jotai 实战》开始入手,从一个应用常见的数据存储处理、回显入手,讲一下 jotai
的实战和常见的坑。
转载自:https://juejin.cn/post/7285233095058178084