Open brickspert opened 4 years ago
说到状态管理器,轮子满天飞。在 Class 时代,redux 与 mobx 几乎占据了全部市场,几乎没有没用过 redux 的同学。随着 Hooks 的诞生,新的一批轮子应运而生,其中有代表性的有 unstated-next、constate 等等。当然无论什么轮子,要解决的问题都是一样的:跨组件状态共享。在解决这个核心问题的同时,需要尽可能的满足以下几个特性:
最近,facebook 官方出了一个状态管理器解决方案 Recoil,我们来体验一下。
使用 Recoil,我们需要在项目最外层包一个 RecoilRoot ,这个和大部分状态管理器一致,通过 context 来跨组件传递数据。
RecoilRoot
import React from 'react'; import { RecoilRoot } from 'recoil'; function App() { return ( <RecoilRoot> ... </RecoilRoot> ); }
状态最简单的就是定义和使用。在 Recoil 中,通过 atom 来定义一个状态。
atom
const inputValueState = atom({ key: "inputValue", default: "" });
如上面的代码所示,我们定义了一个 inputValue 状态,它的默认值是空字符串。需要注意的是 key 字段,它应该是全局唯一的。这个 key 主要为了 debug 方便,持久化数据(数据恢复时的唯一标识),以及可以方便的看到全局 atoms 树。 消费状态也比较简单,通过 useRecoilState 来消费状态。
inputValue
key
useRecoilState
import React from "react"; import { useRecoilState } from "recoil"; import { inputValue } from "../store"; const InputA = () => { const [value, setValue] = useRecoilState(inputValueState); return <input value={value} onChange={e => setValue(e.target.value)} />; }; export default InputA;
是不是很简单?Recoil 的基础用法就是这样的。我在这里写了一个 demo,你可以体验下。
有些状态需要依赖其它状态,这时候就要用 selector 来定义这个状态了。比如,我们需要定义一个新的状态 filterdInputValue ,它是过滤 inputValue 中的数字后的值。
selector
filterdInputValue
const filterdInputValue = selector({ key: "filterdInputValue", get: ({get}) => { // 通过 get 可以读取其它状态 const inputValue = get(inputValueState); return inputValue.replace(/[0-9]/ig, ""); }, });
selector 比较简单,就是为了实现状态的依赖。你可以在这个 demo 体验下。
良好的异步请求支持是状态管理器必不可少的。Recoil 提供了一个 useRecoilValueLoadable 来处理异步请求。直接上例子:
useRecoilValueLoadable
const currentUserNameQuery = selector({ key: "CurrentUserName", get: async () => { const response = await queryUserInfo(); return response.name; } });
我们需要通过 selector 来定义异步状态,如果 get 函数是一个 Promise,则代表该状态为异步状态,需要使用 useRecoilValueLoadable 来消费该状态。
get
const UserName = () => { const userNameLoadable = useRecoilValueLoadable(currentUserNameQuery); switch (userNameLoadable.state) { case "hasValue": return <div>{userNameLoadable.contents}</div>; case "loading": return <div>Loading...</div>; case "hasError": throw userNameLoadable.contents; } };
从上面例子可以看到, useRecoilValueLoadable 返回的状态,可以通过 state 字段读取到异步请求的状态。我写了个 demo,你可以体验下。 当然通过 useRecoilValueLoadable 来消费异步状态,比较符合我们当前的习惯。但 Recoil 更推荐通过 React.Suspense 来消费异步状态,这里就仁者见仁了,虽然 Suspense 可能是方向,但用起来是还不太习惯。
state
React.Suspense
Suspense
const UserName = () => { const userName = useRecoilValue(currentUserNameQuery); return <>{userName}</> } }; function MyApp() { return ( <React.Suspense fallback={<div>Loading...</div>}> <UserName /> </React.Suspense> ); }
当前 Recoil 还处于开发阶段,文档都还不是很全。基于现状,说几点我的感受。
这点我很惊讶,也是写这个文章的时候才发现的,很奇怪。讲道理 Recoil 支持 typescript 应该是顺手的事情,可能后期需要来个 @types/recoil 吧。
@types/recoil
这个特性应该是必备的,应该不会彻底抛弃 Class 组件。估计下个版本肯定会支持的这个特性的。实现成本较低,不支持的话就太反人类了。
各类 API 一共有 19 个,偏复杂了。感觉很多都是可以合并的,比如 atom 和 selector 合并成一个等等(也可能是我考虑不成熟)。建议官方可以考虑精简精简,本来是一个很简单的东西,搞的太复杂了。
我们需要消费一个状态的时候,需要 import 两个东西,比较繁琐。
import { useRecoilState } from "recoil"; import { inputValueState } from "../store"; // 用法 useRecoilState(inputValueState);
本来应该可以直接通过字符串 key 消费的,但这样和 redux 问题一样了,无法支持 ts。
import { useRecoilState } from "recoil"; useRecoilState('inputValueState');
无论如果,import 两个东西不是一个好的用法。
没有看到让人眼前一亮的东西,没有使用冲动。静观发展~
Recoil 整体看下来,比较中庸,需要静观发展。另外推荐一下我目前正在用的最简单的 React 状态管理器 hox,只有一个 API,非常符合直觉,没有任何上手成本,完全拥抱 Hooks 😋。
关注公众号「前端技术砖家」,拉你进交流群,大家一起共同交流和进步。
您好,我觉得文章写的非常不错,很有帮助。 但是有一点可能需要纠正一下,就是recoil并不是一个所谓的“官方”产物
说到状态管理器,轮子满天飞。在 Class 时代,redux 与 mobx 几乎占据了全部市场,几乎没有没用过 redux 的同学。随着 Hooks 的诞生,新的一批轮子应运而生,其中有代表性的有 unstated-next、constate 等等。
当然无论什么轮子,要解决的问题都是一样的:跨组件状态共享。在解决这个核心问题的同时,需要尽可能的满足以下几个特性:
Recoil 体验
最近,facebook 官方出了一个状态管理器解决方案 Recoil,我们来体验一下。
准备工作
使用 Recoil,我们需要在项目最外层包一个
RecoilRoot
,这个和大部分状态管理器一致,通过 context 来跨组件传递数据。跨组件状态共享
状态最简单的就是定义和使用。在 Recoil 中,通过
atom
来定义一个状态。如上面的代码所示,我们定义了一个
inputValue
状态,它的默认值是空字符串。需要注意的是
key
字段,它应该是全局唯一的。这个key
主要为了 debug 方便,持久化数据(数据恢复时的唯一标识),以及可以方便的看到全局 atoms 树。消费状态也比较简单,通过
useRecoilState
来消费状态。是不是很简单?Recoil 的基础用法就是这样的。我在这里写了一个 demo,你可以体验下。
![2020-05-17 13.09.49.gif](https://cdn.nlark.com/yuque/0/2020/gif/189350/1589692204330-5e86d0b1-4c1e-45ba-b878-3d2bfe5604e1.gif#align=left&display=inline&height=179&margin=%5Bobject%20Object%5D&name=2020-05-17%2013.09.49.gif&originHeight=502&originWidth=800&size=32516&status=done&style=none&width=286)
状态互相依赖
有些状态需要依赖其它状态,这时候就要用
selector
来定义这个状态了。比如,我们需要定义一个新的状态
filterdInputValue
,它是过滤inputValue
中的数字后的值。selector
比较简单,就是为了实现状态的依赖。你可以在这个 demo 体验下。异步支持
良好的异步请求支持是状态管理器必不可少的。Recoil 提供了一个
useRecoilValueLoadable
来处理异步请求。直接上例子:我们需要通过
selector
来定义异步状态,如果get
函数是一个 Promise,则代表该状态为异步状态,需要使用useRecoilValueLoadable
来消费该状态。从上面例子可以看到,
![2020-05-17 15.34.53.gif](https://cdn.nlark.com/yuque/0/2020/gif/189350/1589700906147-ca31fb74-fac4-4782-ac40-6dc1316facb5.gif#align=left&display=inline&height=145&margin=%5Bobject%20Object%5D&name=2020-05-17%2015.34.53.gif&originHeight=266&originWidth=734&size=28327&status=done&style=none&width=401)
useRecoilValueLoadable
返回的状态,可以通过state
字段读取到异步请求的状态。我写了个 demo,你可以体验下。当然通过
useRecoilValueLoadable
来消费异步状态,比较符合我们当前的习惯。但 Recoil 更推荐通过React.Suspense
来消费异步状态,这里就仁者见仁了,虽然Suspense
可能是方向,但用起来是还不太习惯。评价
优点
不足
当前 Recoil 还处于开发阶段,文档都还不是很全。基于现状,说几点我的感受。
1. 没有使用 ts 实现,目前不支持 ts
这点我很惊讶,也是写这个文章的时候才发现的,很奇怪。讲道理 Recoil 支持 typescript 应该是顺手的事情,可能后期需要来个
@types/recoil
吧。2. 目前没有支持 Class 组件消费状态。
这个特性应该是必备的,应该不会彻底抛弃 Class 组件。估计下个版本肯定会支持的这个特性的。实现成本较低,不支持的话就太反人类了。
3. API 偏多,有一定上手成本。
各类 API 一共有 19 个,偏复杂了。感觉很多都是可以合并的,比如
atom
和selector
合并成一个等等(也可能是我考虑不成熟)。建议官方可以考虑精简精简,本来是一个很简单的东西,搞的太复杂了。4. 消费较繁琐
我们需要消费一个状态的时候,需要 import 两个东西,比较繁琐。
本来应该可以直接通过字符串
key
消费的,但这样和 redux 问题一样了,无法支持 ts。无论如果,import 两个东西不是一个好的用法。
5. 没有足够的亮点
没有看到让人眼前一亮的东西,没有使用冲动。静观发展~
后记
Recoil 整体看下来,比较中庸,需要静观发展。
另外推荐一下我目前正在用的最简单的 React 状态管理器 hox,只有一个 API,非常符合直觉,没有任何上手成本,完全拥抱 Hooks 😋。
❤️感谢阅读
关注公众号「前端技术砖家」,拉你进交流群,大家一起共同交流和进步。