上一篇实现了页面的搭建,这一篇将实现状态机的代码编写。

我的项目中用到了两个状态机:

  • covidMachine :父状态机,存储所有国家信息(countryList)选中国家的新冠数据(countryData)
  • covidDataMachine :子状态机,存储指定国家的新冠数据(countryData)

父状态机只有一个,子状态机能够多个(取决于用户选了多少个国家) 。

状态机剖析

子状态机是当咱们通过下拉框抉择某个国家时,才进行创立的;选了多少个国家,就会有多少个子状态机;某个国家的新冠数据保留在对应的子状态机中,父状态机只有保护好子状态机的援用就行。

也就是说,父状态机的 context 外面会有静态数据(countryList)动态数据,静态数据就是一次获取后不再更改的所有国家信息;动态数据则是一个动静援用,援用了某个国家的新冠数据。

相干概念

在编写状态机之前,先理解一下 invokespawn

Invoke Service 调用服务

invoke 是状态节点的配置属性,次要用来调用其余的服务,这些服务能够包含:promiseCallbacksObservableMachine

调用这些服务,是为了获取特定的材料。比方在这个我的项目中,咱们须要通过拜访特定的网站,去获取所有的国家的材料,就能够通过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