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. Cookie 政策

    Cookie 政策 合规/隐私协议 https://www.synology.cn/zh-cn/company/legal/cookie_policy Cookie Cookie 政策 生效日期:20 ...

  2. 「NGK每日快讯」12.31日NGK第58期官方快讯!

  3. Redis 对过期数据的处理

    Redis 对过期数据的处理 在 redis 中,对于已经过期的数据,Redis 采用两种策略来处理这些数据,分别是惰性删除和定期删除 惰性删除 惰性删除不会去主动删除数据,而是在访问数据的时候,再检 ...

  4. [Python学习笔记]调试

    编码占了编程工作量的90%,调试占了另外90%,这是一个流传着的笑话.调试在编程中占有很大的分量,即使专业的程序员也一直在制造缺陷. 抛出异常 抛出异常相当于是说:"停止运行这个函数中的代码 ...

  5. centos7下载和安装 通过xshell连接,有手就行,小白教程

    下载步骤: https://mirrors.aliyun.com/centos/?spm=a2c6h.13651104.0.0.3c3712b2NaHUdY 点击下载或者复制链接到迅雷下载 下载好以后 ...

  6. Why GraphQL? 6个问题

    Why GraphQL? 6个问题 GraphQL, 是一个API的标准: specification. 对于每个新技术, 要搞清楚的6个问题: 1.这个技术出现的背景, 初衷, 要达到什么样的目标或 ...

  7. iOS图片预览、放大缩小

    思路 图片预览,优先考虑基础控件UIImageView.UIButton 图片预览中可能需设置不同的mode,优先考虑UIImageView typedef NS_ENUM(NSInteger, UI ...

  8. Ping 的工作原理你懂了,那 ICMP 你懂不懂?

    计算机网络我也连载了很多篇了,大家可以在我的公众号「程序员cxuan」 或者我的 github 系统学习. 计算机网络第一篇,聊一聊网络基础 :计算机网络基础知识总结 计算机网络第二篇,聊一聊 TCP ...

  9. iOS之CoreBluetooth

    思路 手机与设备间的通讯方式CoreBluetooth是比较常见且通用的.在iOS开发中需明晰以下几点 蓝牙4.0最多可联机7个设备,iPhone6以上都是蓝牙4.0 两台iPhone并不直接通过蓝牙 ...

  10. PAT-1151(LCA in a Binary Tree)+最近公共祖先+二叉树的中序遍历和前序遍历

    LCA in a Binary Tree PAT-1151 本题的困难在于如何在中序遍历和前序遍历已知的情况下找出两个结点的最近公共祖先. 可以利用据中序遍历和前序遍历构建树的思路,判断两个结点在根节 ...