共计 3627 个字符,预计需要花费 10 分钟才能阅读完成。
上一篇实现了页面的搭建,这一篇将实现状态机的代码编写。
我的项目中用到了两个状态机:
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.js
import {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.js
import {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