共计 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