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