Recoil 默认值及数据级联的使用
刘哇勇 人气:0
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, 首先将应用包裹在 index.tsx import { RecoilRoot } from "recoil"; ReactDOM.render( <React.StrictMode> <RecoilRoot> <Suspense fallback="loading..."> <App /> </Suspense> </RecoilRoot> </React.StrictMode>, document.getElementById("root") ); 添加 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]; }, }), }); 可选择的可用区依赖于当前选中的地域,通过 可用区的默认值也是拿到当前可选的所有地域,然后取第一个, 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; 至此完成了整个程序的实现。 最终效果来看看效果: 地域及可用区联动效果 带默认值的状态未自动更新的问题上面的实现乍一看实现了功能,但进行可用区的选择之后问题便会暴露。  可用区未联动的问题 可以看到可用区更新后,再切换地域,虽然下拉框中可选的可用区更新了,但实际上当前可用区的值停留在了上一次选中的值,并没有与地域联动。如果不是把可用区展示出来,不容易发现这里的问题,具有一定迷惑性。 看看可用区下拉值 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]; }, }), }); 它通过 当切换地域时, 当我们手动进行了可用区选择时,在可用区下拉组件中, <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>
手动添加依赖直接的修复方式可以在可用区组件中监听地域的变化,当地域变化后,设置一次可用区。 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` 方式来修正,可用区更新会滞后
|
The text was updated successfully, but these errors were encountered: |
加载全部内容