上一篇实现了页面的搭建,这一篇将实现状态机的代码编写。
我的项目中用到了两个状态机:
covidMachine
:父状态机,存储所有国家信息(countryList
)和选中国家的新冠数据(countryData
)covidDataMachine
:子状态机,存储指定国家的新冠数据(countryData
)
父状态机只有一个,子状态机能够多个(取决于用户选了多少个国家) 。
状态机剖析
子状态机是当咱们通过下拉框抉择某个国家时,才进行创立的;选了多少个国家,就会有多少个子状态机;某个国家的新冠数据保留在对应的子状态机中,父状态机只有保护好子状态机的援用就行。
也就是说,父状态机的 context
外面会有静态数据(countryList
)和动态数据,静态数据就是一次获取后不再更改的所有国家信息;动态数据则是一个动静援用,援用了某个国家的新冠数据。
相干概念
在编写状态机之前,先理解一下 invoke
和 spawn
。
Invoke Service
调用服务
invoke
是状态节点的配置属性,次要用来调用其余的服务,这些服务能够包含:promise
、Callbacks
、Observable
、Machine
。
调用这些服务,是为了获取特定的材料。比方在这个我的项目中,咱们须要通过拜访特定的网站,去获取所有的国家的材料,就能够通过invoke promise
去调用咱们的申请。
在 invoke
过程中,还提供了一些生命钩子:
// invoke配置loading: { invoke: { src: someSrc, // 服务的起源 onDone: {/* ... */}, // 调用实现后执行 onError:{/* ... */} // 调用失败后执行 // ... },}
Spawning Actors
创立参与者
在 Actor
模型中,每个状态机实例都被认为是一个“参与者” ,能够向其余“参与者”发送和接管事件(音讯) ,并对它们做出反馈。
spawn
能够动静增加“参与者”,而后返回这个参与者的援用:
// 创立参与者,获取援用countryRef = spawn(createCovidDataMachine(event.name));
在本我的项目中,咱们在下拉框抉择某个国家后,进行 Spawning Actors
,而后把这个援用返回给父状态机。
代码实现
父状态机
这里把fetchCountriesState
独自抽离了进去,是因为这些状态都只有一个目标:invoke promise
获取所有国家的信息。
// covidMachine.jsimport { Machine, assign, spawn } from "xstate";import { createCovidDataMachine } from "./covidDataMachine";const URL_COUNTRIES = "https://covid19.mathdro.id/api/countries";// 立刻执行,获取国家列表const fetchCountriesState = { initial: "loading", states: { loading: { invoke: { id: "fetch-countries", src: "getListCountries", onDone: { target: "loaded", actions: assign({ listCountries: (_, event) => event.data.countries }) }, onError: { target: "failure" } } }, loaded: { type: "final" }, failure: { on: { RETRY: "loading" } } }};export const covidMachine = Machine( { id: "covid-machine", initial: "idle", context: { // 国家列表 listCountries: [], // 以后国家的援用 countryRef: null, // 缓存 countryCashMap: {} }, states: { idle: { ...fetchCountriesState }, selected: {}, completed: {} }, on: { SELECT: { target: ".selected", actions: "initContext" }, // 承受子状态机发送的事件 "EVENT.LOADED": "completed" } }, { actions: { // 初始化上下文 initContext: assign((context, event) => { // 获取援用 let countryRef = context.countryCashMap[event.name]; if (countryRef) { return { ...context, countryRef }; } // 增加参与者,保留援用 countryRef = spawn(createCovidDataMachine(event.name)); return { ...context, countryRef, countryCashMap: { ...context.countryCashMap, [event.name]: countryRef } }; }) }, services: { getListCountries: () => fetch(URL_COUNTRIES).then(res => res.json()) } });
有几个须要关注的点:
fetchCountriesState
目标繁多,抽出来便于管理- 父状态机的
context
中的countryCashMap
对象,是为了缓存查问过的国家(参与者),如果下次再选到之前查问过的国家,就不再发送申请,间接从缓存中查找到该国家(参与者) - 父状态机监听的事件中,有个
EVENT.LOADED
属性,这个属性用来监听子组件数据加载实现。因为状态机之间的数据不是共享的,父状态机无奈晓得子状态机产生的事件,他们之间须要通过发送事件和监听事件来实现通信
父状态机可视化图表:
子状态机
子状态机也就是下面提到的参与者,他指代某个特定的国家,他的 context
外面存有咱们通过 invoke promise
获取的该国家的新冠动静。
// covidDataMachine.jsimport { Machine, assign, sendParent } from "xstate";const COVID_DATA_URL = "https://covid19.mathdro.id/api";// 用工厂办法创立export const createCovidDataMachine = country => Machine( { id: "covid-data", initial: "loading", context: { country, // 确诊 confirmed: null, // 痊愈 recovered: null, // 死亡 deaths: null, // 上一次更新工夫 lastUpdateTime: null }, states: { loading: { invoke: { id: "fetch-covid-data", src: "getStatistics", onDone: { target: "loaded", actions: "handleCovidData" } } }, loaded: { type: "final", entry: sendParent("EVENT.LOADED") }, failure: { on: { RETRY: "loading" } } } }, { actions: { handleCovidData: assign({ // 确诊 confirmed: (_, event) => event.data.confirmed.value, // 痊愈 recovered: (_, event) => event.data.recovered.value, // 死亡 deaths: (_, event) => event.data.deaths.value, // 上一次更新工夫 lastUpdateTime: (_, event) => event.data.lastUpdate }) }, services: { getStatistics: context => { let changedUrl = COVID_DATA_URL; if (context.country !== undefined) { changedUrl = `${COVID_DATA_URL}/countries/${context.country}`; } return fetch(changedUrl).then(response => response.json()); } } } );
有几个须要关注的点:
- 子状态机不是马上产生,这里返回了一个工厂函数,因为咱们是通过交互去增加子状态机(参与者)的
- 状态节点中有个
sendParent("EVENT.LOADED")
值,这是子状态机向父状态机发送一个事件,便是数据获取实现
子状态机可视化图表:
下一章咱们会综合使用。
线上Demo