Jotai
是一个原始且灵活的React
状态管理库。
原始:提供了Hooks方式的Api,使用方式类似于
useState
,useReducer
灵活:可以组合多个
Atom
来创建新的Atom
,并且支持异步高性能:更新对应的
Atom
只会重新渲染订阅了这个Atom
的组件,并不会像Context
那样导致整个父组件重新渲染,所以可以做到精确渲染
# 定义 Atom
atom: 原子的意思。一个Atom代表一个状态,使用atom函数创建一个Atom,需要传入一个参数作为初始值。
import { atom } from 'jotai';
// 定义后并没有与原子关联的值。只有通过 useAtom 使用后的原子,初始值才会存储在状态中
export const textAtom = atom('hello');
// Ts的使用和 React.useState 的方式相同,基础类型可以不定义类型
const numAtom = atom<number>(0)
const numAtom = atom<number | null>(0)
const arrAtom = atom<string[]>([])
2
3
4
5
6
7
8
9
10
11
12
13
# 使用 Atom
useAtom函数接受一个参数,参数值为 一个
Atom
返回值是一个数组
数组第一个值是 Atom
存储的值,第二个值是更新 Atom
值的函数
import { useAtom } from 'jotai';
// 类似 React.useState 的用法
// text 读取 textAtom 的值,setText更改值并通知订阅了 textAtom 的组件更新
const [text, setText] = useAtom(textAtom);
2
3
4
5
# 创建派生原子,有三种模式
定义或者派生使用时,atom()需要放在函数外,否则会造成死循环。
建议维护一份ts文件单独存放定义的原子
# 1.Read-only atom
// 只读的 atom(Read-only atom)
const textLenAtom = atom((get) => get(textAtom).length);
// 只读,没有第二个参数
const [len] = useAtom(textLenAtom);
2
3
4
5
# 2.Write-only atom
// 只写的 atom
const writeOnlyAtom = atom(
null, // 约定为第一个参数传递 `null`
(get, set, update) => {
// `update` 是我们收到的用于更新这个原子的任何单个值
set(textAtom, `${get(textAtom)} ${update}`);
},
);
// 只写的 atom,第一个参数为 null 不可用
const [, setWrite] = useAtom(writeOnlyAtom);
const handleWrite = () => {
setWrite('world');
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.Read-Write atom
// 3. 可读可写的 atom
const readWriteAtom = atom(
(get) => `${get(textAtom)}~`,
(get, set, update: { text: string }) => {
// `update` 是我们收到的用于更新这个原子的任何单个值
set(textAtom, `${get(textAtom)} ${update.text}`);
},
);
const [rw, setRw] = useAtom(readWriteAtom);
const handleReadWriteAtom = () => {
setRw({ text: 'world' });
console.log(rw); // hello world~
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# Provider (opens new window)
Provider 是为一个组件子树提供状态。多个 Provider 可以用于多个子树,甚至可以嵌套。这就像 React Context 一样工作。
在不提供Provider的情况下,会使用默认状态,我们可以不提供他来包裹使用Atom的组件。
<Provider
initialValues={[
[textAtom, 'initval'], // 给 textAtom 原子提供初始值
]}
>
<Input />
<CharCount />
<Uppercase />
</Provider>
2
3
4
5
6
7
8
9
10
使用 Provider 带来的效果
- 为每个组件树提供不同的状态
- 包含了一些调试信息
- 接受原子的初始值initialValues
# onMount
创建后的原子可以有一个可选的属性onMount
。onMount
是一个函数,它接受一个setAtom
函数,并可以返回onUnmount
函数:一个卸载的函数(类似于useEffect的使用方法)。
onMount
当原子首次在提供程序中使用时调用该函数,不再使用时会触发onUnmount
卸载函数。
// 顺带着使用派生用法模拟下 reducer
const derivedAtom = atom(
(get) => get(countAtom),
(get, set, action: { type: 'init' | 'inc' | 'dec' }) => {
if (action.type === 'init') {
set(countAtom, 8);
} else if (action.type === 'inc') {
set(countAtom, (c) => c + 1);
} else if (action.type === 'dec') {
set(countAtom, (c) => c - 1);
}
},
);
const [count2, setCount2] = useAtom(derivedAtom);
derivedAtom.onMount = (setAtom) => {
setAtom({ type: 'init' })
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Utils
内置了很多工具方法 (opens new window),可以根据需要选择使用。更多方法详见上方文档
1.持久化存储的工具方法
import { atomWithStorage, RESET } from 'jotai/utils';
// atomWithStorage()第一个参数是storage的key值,第二个参数是默认值
const textAtom = atomWithStorage('AtomWithStorageKey', 'hello');
const [text, setText] = useAtom(textAtom);
// RESET可以清除该storage
setText(RESET)
2
3
4
5
6
7
8
9
10
11
2.原子拆解选择器
import deepEquals from "fast-deep-equal";
const defaultPerson = {
name: {
first: 'Jane',
last: 'Doe',
},
birth: {
year: 2000,
month: 'Jan',
day: 1,
time: {
hour: 1,
minute: 1,
},
},
}
// 数据源原子
const personAtom = atom(defaultPerson)
// 进行数据拆解出name
const nameAtom = selectAtom(personAtom, (person) => person.name)
// 进行数据拆解出birth,第三个参数传入 equalityFn 判断是否一致,有助于避免无效的渲染,提高性能
const birthAtom = selectAtom(personAtom, (person) => person.birth, deepEquals)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 第三方集成
Jotai内部对多个状态库进行了继承,可根据需要安装对应的包,来配合Jotai使用。
# 调试工具
Jotai官方提供了两种Debug方式:
- 用Redux DevTools去查看特定
atom
的状态,只需要将atom
和label传进jotai/devtools
中的useAtomDevtools
Hooks。 - 用React Dev Tools去查看Jotai的Provider,全部
atom
的状态都存放在DebugState
中,但需要额外设定atom
的debugLabel作为key,否则会显示为<no debugLabel>
。
// 1. Redux DevTools 调试
import { useAtomDevtools } from 'jotai/devtools'
useAtomDevtools(countAtom, 'aaa');
// React DevTools 调试
countAtom.debugLabel = '第一个count';
2
3
4
5
6
7