• xstate -- JavaScript state machines and statecharts


    xstate

    https://xstate.js.org/docs/

    https://github.com/statelyai/xstate

    为现代的web提供有限状态机功能。

    JavaScript and TypeScript finite state machines (opens new window) and statecharts (opens new window) for the modern web.

    https://xstate.js.org/docs/about/concepts.html#finite-state-machines

    XState is a library for creating, interpreting, and executing finite state machines and statecharts, as well as managing invocations of those machines as actors. The following fundamental computer science concepts are important to know how to make the best use of XState, and in general for all your current and future software projects.

    Code Demo

    import { createMachine, interpret } from 'xstate';
    
    // Stateless machine definition
    // machine.transition(...) is a pure function used by the interpreter.
    const toggleMachine = createMachine({
      id: 'toggle',
      initial: 'inactive',
      states: {
        inactive: { on: { TOGGLE: 'active' } },
        active: { on: { TOGGLE: 'inactive' } }
      }
    });
    
    // Machine instance with internal state
    const toggleService = interpret(toggleMachine)
      .onTransition((state) => console.log(state.value))
      .start();
    // => 'inactive'
    
    toggleService.send('TOGGLE');
    // => 'active'
    
    toggleService.send('TOGGLE');
    // => 'inactive'

    WHY XSTATE?

    https://xstate.js.org/docs/#why

    状态图是建立有状态和响应式系统的一种形式化建模方法。

    有助于显示声明式地描述 应用的行为,从单个组件,到整个系统逻辑都使用。

    个人理解: 前端越来越重的情况下, 应用的状态管理是提高应用健壮性的手段,合理管理前端业务逻辑的方法(拆分动态的流程)

    Statecharts are a formalism for modeling stateful, reactive systems. This is useful for declaratively describing the behavior of your application, from the individual components to the overall application logic.

    Finite State Machines

    import { createMachine } from 'xstate';
    
    const lightMachine = createMachine({
      id: 'light',
      initial: 'green',
      states: {
        green: {
          on: {
            TIMER: 'yellow'
          }
        },
        yellow: {
          on: {
            TIMER: 'red'
          }
        },
        red: {
          on: {
            TIMER: 'green'
          }
        }
      }
    });
    
    const currentState = 'green';
    
    const nextState = lightMachine.transition(currentState, 'TIMER').value;
    
    // => 'yellow'

    Promise example - inspiration

    https://xstate.js.org/docs/#promise-example

    此例为 异步工作流的一个简单例子, 工作节点使用promise实现。

    更加复杂的异步流程, 可以丰富起来。

    例如 获得所有狗的品种, 依次展示狗的照片。

    import { createMachine, interpret, assign } from 'xstate';
    
    const fetchMachine = createMachine({
      id: 'Dog API',
      initial: 'idle',
      context: {
        dog: null
      },
      states: {
        idle: {
          on: {
            FETCH: 'loading'
          }
        },
        loading: {
          invoke: {
            id: 'fetchDog',
            src: (context, event) =>
              fetch('https://dog.ceo/api/breeds/image/random').then((data) =>
                data.json()
              ),
            onDone: {
              target: 'resolved',
              actions: assign({
                dog: (_, event) => event.data
              })
            },
            onError: 'rejected'
          },
          on: {
            CANCEL: 'idle'
          }
        },
        resolved: {
          type: 'final'
        },
        rejected: {
          on: {
            FETCH: 'loading'
          }
        }
      }
    });
    
    const dogService = interpret(fetchMachine)
      .onTransition((state) => console.log(state.value))
      .start();
    
    dogService.send('FETCH');

    Prepare

    为实现上面的inspiration, 验证其处理复杂异步逻辑的有效性, 做一些预备 研究工作。

    获取所有狗的品种

    https://dog.ceo/dog-api/documentation/

    获取指定狗的品种的照片

    https://dog.ceo/dog-api/breeds-list

    promise 接口

    https://xstate.js.org/docs/guides/communication.html#invoking-promises

    callback接口

    https://xstate.js.org/docs/guides/communication.html#invoking-callbacks

    fetch接口

    https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

    json object

    https://www.w3schools.com/js/js_json_objects.asp

    react bootstrap

    https://react-bootstrap.netlify.app/components/pagination/

    Inspiration Implement

    https://github.com/fanqingsong/async_workflow_on_xstate

    With the help of the Finite State Machine function of XSTATE library, implement a small dog gallery to display one dog per breed iteratively.

    https://github.com/fanqingsong/async_workflow_on_xstate/blob/main/src/dog.machine.ts

    核心状态机

    下载狗品种 --》 校验狗品种指针 --》 下载对应狗品种的照片 --》 等待10s --》 增加狗品种指针计数 , 接着进入第二步, 第二步骤还有一个跳出目标状态为完成, 即遍历完所有狗的品种。

    import { createMachine, interpret, assign } from 'xstate';
    
    
    const fetchDogBreed = async (context, event) => {
      const data = await fetch('https://dog.ceo/api/breeds/list/all');
    
      console.log("dog breed data = ", data);
    
      return await data.json();
    }
    
    const getBreeds = (context, event) => {
      console.log("event.data.message =", event.data.message)
    
      let rawDogBreeds = event.data.message;
    
      console.log("raw dog breeds:", rawDogBreeds, typeof(rawDogBreeds));
      let dogBreeds = [];
    
      for(let oneBreed in rawDogBreeds){
        let oneBreedChildren = rawDogBreeds[oneBreed];
        // only get this breed without sub breed.
        if (oneBreedChildren.length == 0){
          dogBreeds.push(oneBreed);
        }
      }
      console.log("dog breeds filtered:", dogBreeds);
    
      return dogBreeds;
    }
    
    const checkPointer = (context, event) => (callback, onReceive) => {
      console.log("checkPointer is called.");
    
      let dogBreeds = context.dogBreeds;
      let dogPointer = context.dogPointer;
    
      console.log("dog breeds:", dogBreeds);
      console.log("dog pointer:", dogPointer);
    
      if (dogPointer >= dogBreeds.length) {
        console.log('No more dog breed.')
        callback("TO_THE_END");
      } else {
        callback("PASSED")
      }
    
      // Perform cleanup
      return () => {};
    }
    
    const getDogURL = async (context, event) => {
      console.log("getDogURL promise is called.");
    
      let dogBreeds = context.dogBreeds;
      let dogPointer = context.dogPointer;
    
      console.log("dog breeds:", dogBreeds);
      console.log("dog pointer:", dogPointer);
    
      let dogBreed = dogBreeds[dogPointer];
    
      console.log("before call api.");
    
      let api = `https://dog.ceo/api/breed/${dogBreed}/images/random`;
    
      let data = await fetch(api);
    
      console.log("dog breed data = ", data);
    
      return await data.json();
    };
    
    const increasePointer = (context, event) => (callback, onReceive) => {
      console.log("increasePointer is called.");
    
      let dogPointer = context.dogPointer;
    
      console.log("dog pointer:", dogPointer);
    
      callback("OK")
    
      // Perform cleanup
      return () => {};
    }
    
    export const fetchMachine = createMachine({
      id: 'Dog API',
      initial: 'idle',
      context: {
        dogURL: null,
        dogBreeds: null,
        dogPointer: null
      },
      states: {
        idle: {
          on: {
            FETCH: 'breedLoading'
          }
        },
        breedLoading:{
          invoke: {
            id: 'fetchDogBreed',
            src: fetchDogBreed,
            onDone: {
              target: 'breedShowing',
              actions: assign({
                dogBreeds: getBreeds,
                dogPointer: 0
              })
            },
            onError: 'failure'
          },
          on: {
            CANCEL: 'idle'
          }
        },
        pointerChecking: {
          invoke: {
            id: 'checkPointer',
            src: checkPointer
          },
          on: {
            PASSED: { 
              target: 'breedShowing',
            },
            TO_THE_END: {
              target: 'success'
            }
          }
        },
        breedShowing:{
          invoke: {
            id: 'getDogURL',
            src: getDogURL,
            onDone: {
              target: 'waiting',
              actions: assign({
                dogURL: (context, event) => {
                    console.log("event.data.message =", event.data.message)
    
                    return event.data.message
                },
              })
            },
            onError: 'failure'
          },
          on: {
            CANCEL: 'idle'
          }
        },
        waiting: {
          invoke: {
            id: 'wait',
            src: (context, event) => (callback, onReceive) => {
              // This will send the 'TIMEOUT' event to the parent every second
              const id = setInterval(() => callback('TIMEOUT'), 10000);
        
              // Perform cleanup
              return () => clearInterval(id);
            }
          },
          on: {
            TIMEOUT: { 
              target: 'pointerIncreasing'
            }
          }
        },
        pointerIncreasing: {
          invoke: {
            id: 'increasePointer',
            src: increasePointer
          },
          on: {
            OK: { 
              target: 'breedShowing',
              actions: assign({
                dogPointer: context => context.dogPointer + 1
              })
            },
          }
        },
        success: {
          type: 'final'
        },
        failure: {
          on: {
            RETRY: 'idle'
          }
        }
      }
    });

    Demo Pic

    https://github.com/fanqingsong/async_workflow_on_xstate

    与redux差别?

    redux

    https://redux.js.org/

    其目标是状态容器,管理状态, 便于和UI框架集成。

    A Predictable State Container for JS Apps

    Redux is a predictable state container for JavaScript apps.

    It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

    You can use Redux together with React, or with any other view library. It is tiny (2kB, including dependencies), but has a large ecosystem of addons available.

    What is an actual difference between redux and a state machine (e.g. xstate)?

    从作者回答可知, xstate主要面向 状态机, 关注状态的变迁。

    两者的面向的问题是不一样的。

    有的使用者,将 xstate用作状态管理器,是误用。

    I created XState, but I'm not going to tell you whether to use one over the other; that depends on your team. Instead, I'll try to highlight some key differences.

    • Redux is essentially a state container where events (called actions in Redux) are sent to a reducer which update state.
    • XState is also a state container, but it separates finite state (e.g., "loading", "success") from "infinite state", or context (e.g., items: [...]).
    • Redux does not dictate how you define your reducers. They are plain functions that return the next state given the current state and event (action).
    • XState is a "reducer with rules" - you define legal transitions between finite states due to events, and also which actions should be executed in a transition (or on entry/exit from a state)
    • Redux does not have a built-in way to handle side-effects. There are many community options, like redux-thunk, redux-saga, etc.
    • XState makes actions (side-effects) declarative and explicit - they are part of the State object that is returned on each transition (current state + event).
    • Redux currently has no way to visualize transitions between states, since it does not discern between finite and infinite state.
    • XState has a visualizer: https://statecharts.github.io/xstate-viz which is feasible due to the declarative nature.
    • The implicit logic/behavior represented in Redux reducers can't be serialized declaratively (e.g., in JSON)
    • XState's machine definitions, which represent logic/behavior, can be serialized to JSON, and read from JSON. This makes behavior very portable and configurable by external tools.
    • Redux is not strictly a state machine.
    • XState adheres strictly to the W3C SCXML specification: https://www.w3.org/TR/scxml/
    • Redux relies on the developer to manually prevent impossible states.
    • XState uses statecharts to naturally define boundaries for handling events, which prevents impossible states and can be statically analyzed.
    • Redux encourages the use of a single, "global" atomic store.
    • XState encourages the use of an Actor-model-like approach, where there can be many hierarchical statechart/"service" instances that communicate with each other.

    I will add more key differences to the docs this week.

    redux生态自己的状态机

    https://github.com/mocoding-software/redux-automata

    Finite state machine for Redux

    redux-automata - is a Finite State Machine implementation for Redux store. It allows developer to generate Redux reducer automatically based on FST graph object. The library was developed to support the following scenarios:

    • Provide different behavior in response to the same action depending on a current state
    • Ignore specific actions while in specific states (or better say - react on actions only in specific states)
    • Use declarative approach for defining actions, states and transitions instead of switch-case and if-then-else statements
    出处:http://www.cnblogs.com/lightsong/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
  • 相关阅读:
    突然想谈谈——我的软件测试入门
    js+rem动态计算font-size的大小,适配各种手机设备!
    iOS 如何打测试包,直接给测试人员使用(绝对的新手入门)
    去掉无用的多余的空格(string1.前后空格,2.中间空格)
    iOS 自定义键盘ToolBar(与键盘的弹出、收起保持一致)
    iOS上线...踩坑
    iOS10 导航条,这个二狗子变了...踩坑
    ios程序发布测试打包
    获取毫秒级时间戳
    弹簧动画效果(系统自带方法)
  • 原文地址:https://www.cnblogs.com/lightsong/p/15092323.html
Copyright © 2020-2023  润新知