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

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

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

  • 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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理