Recoil 中默认值及数据间的依赖

通过 Atom 可方便地设置数据的默认值,

const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});

而 Selector 可方便地设置数据的级联依赖关系,即,另一个数据可从现有数据进行派生。

const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px'; return `${fontSize}${unit}`;
},
});

结合这两个特点,在实现数据间存在联动的表单时,非常方便。

一个实际的例子

考察这样的场景,购买云资源时,会先选择地域,根据所选地域再选择该地域下的可用区。

这里就存在设置默认值的问题,未选择时自动选中默认地域及对应地域下的默认可用区,也涉及数据间的级联依赖,可选的可用区要根据地域而变化。

呈现的效果如下:



地域及可用区的选择

实现地域及可用区的选择

下面就通过 Recoil 来实现上述地域及可用区的选择逻辑。

创建示例项目

$  yarn create react-app recoil-nest-select --template typescript

添加并使用 Recoil

安装依赖:

$ yarn add recoil

使用 Recoil, 首先将应用包裹在 RecoilRoot 中:

index.tsx

import { RecoilRoot } from "recoil";

ReactDOM.render(
<React.StrictMode>
<RecoilRoot>
<Suspense fallback="loading...">
<App />
</Suspense>
</RecoilRoot>
</React.StrictMode>,
document.getElementById("root")
);

添加 appState.ts 文件存放 Recoil 状态数据,目前先定义好地域和可用区的类型,

appState.ts

interface IZone {
id: string;
name: string;
} interface IRegion {
id: string;
name: string;
zones: IZone[];
}

添加假数据

根据上面定义的类型,添加假数据:

mock.ts

export const mockRegionData = [
{
id: "beijing",
name: "北京",
zones: [
{
id: "beijing-zone-1",
name: "北京一区",
},
{
id: "beijing-zone-2",
name: "北京二区",
},
{
id: "beijing-zone-3",
name: "北京三区",
},
],
},
{
id: "shanghai",
name: "上海",
zones: [
{
id: "shanghai-zone-1",
name: "上海一区",
},
{
id: "shanghai-zone-2",
name: "上海二区",
},
{
id: "shanghai-zone-3",
name: "上海三区",
},
],
},
{
id: "guangzhou",
name: "广州",
zones: [
{
id: "guangzhou-zone-1",
name: "广州一区",
},
{
id: "guangzhou-zone-2",
name: "广州二区",
},
],
},
];

添加状态数据

添加地域及可用区状态数据,先看地域数据,该数据用来生成地域的下拉框。真实情况下,该数据来自异步请求,这里通过 Promise 模拟异步数据。

appState.ts

import { atom, selector } from "recoil";
import { mockRegionData } from "./mock"; export const regionsState = selector({
key: "regionsState",
get: ({ get }) => {
return Promise.resolve<IRegion[]>(mockRegionData);
},
});

添加一个状态用于保存当前选中的地域:

appState.ts

export const regionState = atom({
key: "regionState",
default: selector({
key: "regionState/Default",
get: ({ get }) => {
const regions = get(regionsState);
return regions[0];
},
}),
});

这里通过使用 atom 并指定默认值为地域第一个数据,达到下拉框默认选中第一个的目的。

添加地域选择组件

添加地域选择组件,使用上面创建的地域数据。

RegionSelect.tsx

import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { regionsState, regionState } from "./appState"; export function RegionSelect() {
const regions = useRecoilValue(regionsState);
const [region, setRegion] = useRecoilState(regionState);
return (
<label htmlFor="regionId">
地域:
<select
name="regionId"
id="regionId"
value={region.id}
onChange={(event) => {
const regionId = event.target.value;
const region = regions.find((region) => region.id === regionId);
setRegion(region!);
}}
>
{regions.map((region) => (
<option key={region.id} value={region.id}>
{region.name}
</option>
))}
</select>
</label>
);
}

至此地域部分完成,可用区同理,只不过可用区的拉下数据依赖于当前选中的地域。

添加可用区状态数据及下拉组件

appState.tsx

export const zonesState = selector({
key: "zonesState",
get: ({ get }) => {
const region = get(regionState);
return region.zones;
},
}); export const zoneState = atom({
key: "zoneState",
default: selector({
key: "zoneState/default",
get: ({ get }) => {
return get(zonesState)[0];
},
}),
});

可选择的可用区依赖于当前选中的地域,通过 const region = get(regionState); 实现获取到当前选中地域的目的。

可用区的默认值也是拿到当前可选的所有地域,然后取第一个,return get(zonesState)[0];

ZoneSelect.tsx

import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { zonesState, zoneState } from "./appState"; export function ZoneSelect() {
const zones = useRecoilValue(zonesState);
const [zone, setZone] = useRecoilState(zoneState);
return (
<label htmlFor="zoneId">
可用区:
<select
name="zoneId"
id="zoneId"
value={zone.id}
onChange={(event) => {
const zoneId = event.target.value;
const zone = zones.find((zone) => zone.id === zoneId);
setZone(zone!);
}}
>
{zones.map((zone) => (
<option key={zone.id} value={zone.id}>
{zone.name}
</option>
))}
</select>
</label>
);
}

展示当前地域及可用区

将前面两个下拉框展示出来,同时展示当前地域及可用区。

App.tsx

import React from "react";
import { useRecoilValue } from "recoil";
import "./App.css";
import { regionState, zoneState } from "./appState";
import { RegionSelect } from "./RegionSelect";
import { ZoneSelect } from "./ZoneSelect"; function App() {
const region = useRecoilValue(regionState);
const zone = useRecoilValue(zoneState);
return (
<div className="App">
<p>region:{region.name}</p>
<p>zone:{zone.name}</p>
<RegionSelect />
<ZoneSelect />
</div>
);
} export default App;

至此完成了整个程序的实现。

最终效果

来看看效果:

地域及可用区联动效果

带默认值的状态未自动更新的问题

上面的实现乍一看实现了功能,但进行可用区的选择之后问题便会暴露。

可用区未联动的问题

可以看到可用区更新后,再切换地域,虽然下拉框中可选的可用区更新了,但实际上当前可用区的值停留在了上一次选中的值,并没有与地域联动。如果不是把可用区展示出来,不容易发现这里的问题,具有一定迷惑性。

看看可用区下拉值 zones 的来源不难发现,

export const zonesState = selector({
key: "zonesState",
get: ({ get }) => {
const region = get(regionState);
return region.zones;
},
});

因为可用区是从当前选中的地域数据 regionState 中获取的,当变更地域后,regionState 更新,导致 zonesState 更新,所以下拉框能正确同步,没问题。

再看看当前选中的可用区 zoneState

export const zoneState = atom({
key: "zoneState",
default: selector({
key: "zoneState/default",
get: ({ get }) => {
return get(zonesState)[0];
},
}),
});

它通过 atom 承载,同时指定了默认值,为 zonesState 中第一个数据。

当切换地域时,zonesState 确实更新了,进而 zoneState 的默认值也会重新获取,所以始终会默认选中第一个可用区。

当我们手动进行了可用区选择时,在可用区下拉组件中,

      <select
name="zoneId"
id="zoneId"
value={zone.id}
+ onChange={(event) => {
+ const zoneId = event.target.value;
+ const zone = zones.find((zone) => zone.id === zoneId);
+ setZone(zone!);
}}
>
{zones.map((zone) => (
<option key={zone.id} value={zone.id}>
{zone.name}
</option>
))}
</select>

onChange 事件的回调中通过 setZone 更新了 zoneState,此时可用区 zoneState 已经有一个人为设置的值,默认值就不起作用了,因此在切换地域后,zoneState 仍为这里 onChange 设置的值。

手动添加依赖

直接的修复方式可以在可用区组件中监听地域的变化,当地域变化后,设置一次可用区。

export function ZoneSelect() {
+ const region = useRecoilValue(regionState);
const zones = useRecoilValue(zonesState);
const [zone, setZone] = useRecoilState(zoneState); + console.log("zone:", zone.id); + useEffect(() => {
+ setZone(zones[0]);
+ }, [region]); return (
<label htmlFor="zoneId">

</label>
);
}

能达到目的,但通过打印出来的可用区值来看,当地域切换后,可用区的值更新并不及时,首先会打印出一个错误的值,待 useEffect 执行完毕后,才打印出正确的值,即,这种方式的修复,有滞后性。

通过 `useEffect` 方式来修正,可用区更新会滞后

useResetRecoilState

查阅 Recoil 文档,发现 useResetRecoilState 可用于重置状态到默认值。

这里的思路可以是,在地域变化后,重置一下可用区,这样之前手动选择的值便失效,可用区恢复到默认状态。

export function RegionSelect() {
const regions = useRecoilValue(regionsState);
const [region, setRegion] = useRecoilState(regionState);
+ const resetZone = useResetRecoilState(zoneState);
return (
<label htmlFor="regionId">
地域:
<select
name="regionId"
id="regionId"
value={region.id}
onChange={(event) => {
const regionId = event.target.value;
const region = regions.find((region) => region.id === regionId);
+ resetZone();
setRegion(region!);
}}
>
{regions.map((region) => (
<option key={region.id} value={region.id}>
{region.name}
</option>
))}
</select>
</label>
);
}

这里 resetZonesetRegion 的顺序不影响,都能达到目的。

通过 `useResetRecoilState` 重置状态到默认值

通过打印的值来看,一切正常,问题得以修正。

相关资源

The text was updated successfully, but these errors were encountered:

Recoil 默认值及数据级联的使用的更多相关文章

  1. Recoil 中默认值的正确处理

    继续使用 Recoil 默认值及数据级联的使用 的地域可用区级联的例子. 地域变更后可用区随之联动,两个下拉框皆默认选中第一个可选项. 从 URL 获取默认值 考虑这种情况,当 URL 中带了 que ...

  2. mysql 插入默认值的问题 sql-mode

    刚好碰到如果不给默认值mysql数据就插入不成功的问题,后来百度了很多,试了下结果 把my.ini里面的[mysqld]的sql-mode 换成下面的一行,如果没有则添加  sql-mode=&quo ...

  3. 关于daterangepicker取消默认值的设置

    1.项目中用到了daterangepicker这个插件,需求要求不能有默认值. 2.查资料得知,可以修改插件内的属性 autoUpdateInput值来实现这个效果. 顾虑有二: 1.修改插件内容,导 ...

  4. 使用protobuf-java-format包 JsonFormat转Json部分默认值字段消失问题

    使用protobuf-java-format包 JsonFormat转Json部分默认值字段消失问题 1.产生的bug XXXXXXXXRequest.Builder request = XXXXXX ...

  5. element 的 Cascader 级联选择器设定默认值

    Cascader 级联选择器 发现在很多的CRM管理系统里面,都有不少页面是用到这种级联选择器的,确实,功能很实用, 不过要设置默认值则应该让不少人头痛,因为你选择的时候 @change 事件的参数就 ...

  6. SQL Server2000导出数据时包含主键、字段默认值、描述等信息

    时经常用SQL Server2000自带的导出数据向导将数据从一台数据库服务器导出到另一台数据库服务器: 结果数据导出了,但表的主键.字段默认值.描述等信息却未能导出,一直没想出什么方法,今天又尝试了 ...

  7. easyui的combobox将得到的数据设定为下拉框默认值和复选框设定默认值

    通过easyui做了一个表,表里是从数据库拿到的数据. 现在双击某一行,通过点击行的id取到这一行的所有数据,现在需要修改这些得到的数据, 其中部分数据是<select>这个选择的, 问题 ...

  8. hibernate 插入数据时让数据库默认值生效

    用hibernate做数据库插入操作时,在数据库端已经设置了对应列的默认值,但插入的数据一直为null.查找资料发现,原来是hibernate的配置项在作怪. Hibernate允许我们在映射文件里控 ...

  9. EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的

    我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...

随机推荐

  1. qrcode & console.log

    qrcode & console.log image https://fs-api.lightyy.com/service/utils/qrcode?url=http://169.254.13 ...

  2. react-app 编写测试

    jest Enzyme 文档 为什么要写测试 单元测试(unit testing)指的是以软件的单元(unit)为单位,对软件进行测试.单元可以是一个函数,也可以是一个模块或组件.它的基本特征就是,只 ...

  3. Flutter: SliverAppBar 应用程序栏与滚动视图集成,以便它可以根据滚动偏移量在高度上变化

    API class _MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin { @override W ...

  4. 为什么NGK推出的DEFI项目这么火热?

    进入到2020年的下半年,DeFi的锁仓量基本上是以日破新高的态势,不断的成为一个独角兽.DeFi逐渐形成一个独角兽的同时,也在不断的给区块链生态赋能,源源不断进行金融价值输送.所以加密货币体量的不断 ...

  5. 《容器高手实战: Dockerfile最佳实践》

    Dockerfile最佳实践一个容器对应一个进程一个Docker容器应该只对应一个进程,也就是一个Docker 镜像一般只包含一个应用的制品包(比如.jar). 在需要组合多个进程的场景,使用容器组( ...

  6. Python学习笔记_购物车案例

    goods_dic = { "iphone":6000, "ipad":3000, "T-shirt":100, "coffee& ...

  7. hadoop支持lzo完整过程

    简介 启用lzo 启用lzo的压缩方式对于小规模集群是很有用处,压缩比率大概能降到原始日志大小的1/3.同时解压缩的速度也比较快. 安装lzo lzo并不是linux系统原生支持,所以需要下载安装软件 ...

  8. the import java.util cannot be resolve

    重新配置一下build path 的jre,如果不行的话就重新设置jre(在add library中installed JREs)

  9. Centos8.2安装Mongodb4.4.2(社区版)

    1:下载 wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-4.4.2.tgz 官网地址: 2:解压 tar -zxv ...

  10. Oracle check TBS usage

    select d.tablespace_name, space||'M' "SUM_SPACE(M")", blocks "SUM_BLOCKS", ...