Recoil入门

官网

recoil 介绍

recoil 入门

介绍

State 和 Context 的问题

假设我们有下面一个场景:有 List 和 Canvas 两个组件,List 中一个节点更新后,Canvas 中的节点也对应更新。

最常规则做法是将一个 state 通过父组件分发给 List 和 Canvas 两个组件,显然这样的话每次 state 改变后 所有节点都会全量更新。

当然,我们还可以使用 Context API,我们将节点的状态存在一个 Context 内,只要 Provider 中的 props 发生改变, Provider 的所有后代使用者都会重新渲染。

为了避免全量渲染的问题,我们可以把每个子节点存储在单独的 Context 中,这样每多一个节点就要增加一层 Provider。

但是,如果子节点是动态增加的呢?我们还需要去动态增加 Provider ,这会让整个树再次重新渲染,显然也是不符合预期的。

Recoil 登场

Recoil 提出了一个新的状态管理单位 Atom,它是可更新和可订阅的,当一个 Atom 被更新时,每个被订阅的组件都会用新的值来重新渲染。如果从多个组件中使用同一个 Atom ,所有这些组件都会共享它们的状态。

对比

比较现有的状态库reduxmobx,它们的区别次要在于:

状态治理库ReduxMobxRecoil
流程标准 / 简单自在 / 简略标准 / 简略
依赖redux/react-redux/redux-sagamobx/mobx-reactrecoil
适应类 / 函数组件类 / 函数组件函数组件
学习老本
思维函数式响应式hook
版本正式正式测试

所以基于以上剖析,我的项目在满足 React项目 && 应用 hook && 小型我的项目的时候,咱们能够思考应用 Recoil。

起步

在应用上要使用 recoril ,需要在父组件或根组件上套一个<<font style="color:rgb(28, 30, 33);background-color:rgb(246, 247, 248);">RecoilRoot></font>

Atom

使用 atom 来初始化 recoil 状态

1
2
3
4
5
6
const todoListState = atom({
key: "todoListState", // 唯一id
default: [], // 默认值
effects: [] as Array<AtomEffect<any>>, // atom副作用时会执行的一系列回调,是个数组
// 可通过 recoil-persist 在 effects 中做该 atom 的持久化
});
1
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
27
28
29
30
type AtomEffect<T> = ({
node: RecoilState<T>, // 对 atom 本身的引用
storeID: StoreID, // ID for the <RecoilRoot> or Snapshot store associated with this effect.
// ID for the parent Store the current instance was cloned from. For example,
// the host <RecoilRoot> store for `useRecoilCallback()` snapshots.
parentStoreID_UNSTABLE: StoreID,
trigger: 'get' | 'set', // 触发 atom 初始化的行动

// 用于设置或重置 atom 值的回调。
// 可以从 atom effect 函数中直接调用,以初始化
// atom 的初始值,或者在以后异步调用以改变它。
setSelf: (
| T
| DefaultValue
| Promise<T | DefaultValue> // 目前只能用于初始化
| ((T | DefaultValue) => T | DefaultValue),
) => void,
resetSelf: () => void,

// 订阅 atom 值的变化。
// 由于这个 effect 自己的 setSelf() 的变化,该回调没有被调用。
onSet: (
(newValue: T | DefaultValue, oldValue: T | DefaultValue) => void,
) => void,
// Callbacks to read other atoms/selectors
getPromise: <S>(RecoilValue<S>) => Promise<S>,
getLoadable: <S>(RecoilValue<S>) => Loadable<S>,
getInfo_UNSTABLE: <S>(RecoilValue<S>) => RecoilValueInfo<S>,

}) => void | () => void; // 可以返回一个清理程序

使用 recoil 状态

1
2
3
4
5
6
7
8
// 获取 atom值
const todoList = useRecoilValue(todoListState);

// 获取 设置atom值 的方法
const setTodoList = useSetRecoilState(todoListState);

// 同 useState 一样的API
const [todoList, setTodoList] = useRecoilState(todoListState);

Selector

Selector 代表一个派生状态,你可以将派生状态视为 将状态传递给 以某种方式修改给定状态的纯函数的输出,它使我们可以构建依赖于其他数据的动态数据。类似 vue 中的计算属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
const todoListState = atom({
key: "todoListState", // 唯一 id
default: [],
});

const getListLength = selector({
key: "getListLength", // 唯一 id
get: ({ get }) => {
const list = get(todoListState);
return list.length;
},
set: ({ set, get, reset }) => {},
});

selector 通常只需要 get

当 selector 只有 get 时 ,状态为只读,不无法使用 useSetRecoilState | useRecoilState ,需要添加 set 后才能使用。

1
2
// 获取 selector 的值
const length = useRecoilValue(getListLength);

异步 selector, get 中只需返回一个 Promise 或者使用一个 async 函数就行了。如果任何依赖关系发生变化,selector 都将重新计算并执行新的查询。结果会被缓存起来,所以查询将只对每个不同的输入执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const currentUserNameQuery = selector({
key: "CurrentUserName",
get: async ({ get }) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});

function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}

报错处理: 但如果请求有错误怎么办?当 selector 抛出错误,一个组件试图使用该 selector 时就会抛出该错误。这可以用 React 来捕捉。例如:

1
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
27
28
29
const currentUserNameQuery = selector({
key: "CurrentUserName",
get: async ({ get }) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
if (response.error) {
throw response.error;
}
return response.name;
},
});

function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}

function MyApp() {
return (
<RecoilRoot>
<ErrorBoundary>
<React.Suspense fallback={<div>加载中……</div>}>
<CurrentUserInfo />
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
);
}


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!