关于前端:二-利用xstate追踪新冠动态-实现状态机

29次阅读

共计 3627 个字符,预计需要花费 10 分钟才能阅读完成。

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

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

  • 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.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

正文完
 0