XState:都1202年了,不会真有人还在用假的状态管理库吧?

587次阅读  |  发布于3年以前

写在最前

谈到状态管理库,有人就会说谁还没用过Redux、Vuex之类的,实际上它们并不是真的状态管理库,本文今天介绍一个真正的状态管理库--XState,一个完全的有限状态机的实现。

前置知识

包括状态模式和策略模式、状态机以及状态图,可以帮助更好地理解XState。

状态模式和策略模式

XState是状态管理工具,应对程序中状态变化切换行为的需求,是一个状态机的实现,理解状态机有必要先理解一下状态模式,说到状态模式不得不提一下策略模式,因为它们太像了,是行为设计模式中的一对亲兄弟,下面是对应的类图:

状态模式和策略模式有一些显著特征便于理解和区别

状态机

释义

疑惑

模型

应用

状态图

XState

简介

XState是一个比较标准的有限状态机的实现,并可以通过图形化的方式转换为状态图的形式; 优势

对比

XState是一个状态管理库,Redux、Vuex之类,也被称为状态管理库,但是他们之间的区别还是较大的,这里以Redux为例,与XState做一个简单的比较;

目标&定位

功能

状态维护

上下文内容修改

组件间通信

代码组织

逻辑可视化

总结

优缺点

实例

XState定位为一个通用的工具,官方给出了在不同的语言框架中的使用示例,这里从基本的状态机开始,给出一些简单实例;

简单的状态机

import { createMachine, interpret } from 'xstate';
const lightMachine = createMachine({
    initial:'red',
    states: {
        red: {
            on:{
                click:'green',
            }
        },
        green: {
            on:{
                press:'yellow',
            }
        },
        yellow: {
            on:{
                keyup:'red',
            }
        },
    },
});
//获取初试状态
const state0 = lightMachine.initialState;
console.log(state0);
//通过transition函数切换状态,第一个参数为原状态,第二个参数为自定义操作
const state1 = lightMachine.transition(state0, 'click');
console.log(state1);
const state2 = lightMachine.transition(state1, 'press');
console.log(state2);
const state3 = lightMachine.transition(state2, 'keyup');
console.log(state3);
const state0 = lightMachine.initialState; 
const state1 = lightMachine.transition(state0, 'click'); 
console.log('state1',state1); 
const state2 = lightMachine.transition(state1, 'click'); 
console.log('state2',state2); 

虽然通过machine可以创建状态机,并配合一些api和property可以使用,但是却有很多不方便,最关键的缺少一个管理者的角色,最直接的问题,如何获得当前状态呢?

接入服务

XState提供了Interpret来创建服务作为状态机的管理者角色

import { createMachine, interpret } from 'xstate'; 
const lightMachine = createMachine({ 
    initial:'red', 
    states: { 
        red: { 
            on:{ 
                click:'green', 
            } 
        }, 
        green: { 
            on:{ 
                press:'yellow', 
            } 
        }, 
        yellow: { 
            on:{ 
                keyup:'red', 
            } 
        }, 
    }, 
}); 
//包装服务 
const service  = interpret(lightMachine); 
//启动服务 
service.start(); 
//通过send切换状态,相当于lightMachine.transition函数 
service.send('click'); 
//获取当前状态 
console.log('service.state',service.state) 
service.send('press'); 
console.log('service.state',service.state) 
console.log('state.value',service.state.value); 
console.log('matches green',service.state.matches('green',)); 
console.log('state.nextEvents',service.state.nextEvents); 
service.stop(); 

这样一个简单的machine好像做不了什么,和Redux相比,基本的上下文数据都没有,别急。。。

接入上下文和行为

import { createMachine, interpret, assign} from 'xstate'; 
const lightMachine = createMachine({ 
    id:'lightMachine', 
    initial:'red', 
    context:{ 
        redCount: 0, 
        greenCount: 0, 
        yellowCount: 0, 
    }, 
    states: { 
        red: { 
            //退出action 
            exit: assign({ redCount: (ctx) => ctx.redCount + 1 }), 
            on:{ 
                click:'green', 
            } 
        }, 
        green: { 
            on:{ 
                press:{ 
                    target:'yellow', 
                    actions: assign({ greenCount: (ctx) => ctx.greenCount + 1 }),//单个action 
                }, 
            }, 
        }, 
        yellow: { 
            //进入action 
            entry: assign({ yellowCount: (ctx) => ctx.yellowCount + 1 }), 
            on:{ 
                keyup:{ 
                    target:'red', 
                    actions: ['countAction','doSomething'],//actions数组 
                }, 
            } 
        }, 
    }, 
},{ 
    actions:{ 
        countAction: assign({ count: (ctx) => ctx.greenCount + ctx.redCount + ctx.yellowCount}), 
        doSomething: () => console.log("为所欲为"), 
    } 
}); 


//包装服务 
const service  = interpret(lightMachine); 
//启动服务 
service.start(); 
//获取当前上下文数据 
console.log('service.state.context',service.state.value,service.state.context) 
service.send('click'); 
console.log('service.state.context',service.state.value,service.state.context) 
service.send('press'); 
console.log('service.state.context',service.state.value,service.state.context) 
service.send('keyup'); 
console.log('service.state.context',service.state.value,service.state.context) 
service.stop(); 

green: { 
       on:{ 
           press:{ 
               target:'yellow', 
               actions: assign({ greenCount: (ctx) => ctx.greenCount + 1 }), 
                }, 
            }, 
        }, 
green: { 
        on:{ 
            press:'yellow', 
        } 
        }, 

接入React/Vue3

import "./styles.css"; 
import * as React from "react"; 
import * as ReactDOM from "react-dom"; 
import { createMachine, assign } from "xstate"; 
import { useMachine } from "@xstate/react"; 
interface ToggleContext { 
  count: number; 
} 
const toggleMachine = createMachine<ToggleContext>({ 
  id: "toggle", 
  initial: "inactive", 
  context: { 
    count: 0 
  }, 
  states: { 
    inactive: { 
      on: { TOGGLE: "active" } 
    }, 
    active: { 
      entry: assign({ count: (ctx) => ctx.count + 1 }), 
      on: { TOGGLE: "inactive" } 
    } 
  } 
}); 
function App() { 
  const [current, send] = useMachine(toggleMachine); 
  const active = current.matches("active"); 
  const { count } = current.context; 
  return ( 
    <div className="App"> 
      <h1>XState React Template</h1> 
      <h2>Fork this template!</h2> 
      <button onClick={() => send("TOGGLE")}> 
        Click me ({active ? "yes" : "no"}) 
      </button>{" "} 
      <code> 
        Toggled <strong>{count}</strong> times 
      </code> 
    </div> 
  ); 
} 
const rootElement = document.getElementById("root"); 
ReactDOM.render(<App />, rootElement); 

接入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'); 

总结

参考资料

[1]JavaScript-State-Machine: https://github.com/jakesgordon/javascript-state-machine

[2]Xstate: https://github.com/jakesgordon/javascript-state-machine

[3]Redux: https://redux.js.org/

[4]Vuex: https://vuex.vuejs.org/

[5]从有限状态机、图灵机到现代计算机: https://blog.csdn.net/chenchao868/article/details/6833612

[6]xstate.js.org/viz/: https://link.juejin.cn/?target=https://xstate.js.org/viz/

[7]点击跳转: https://xstate.js.org/docs/guides/actions.html#declarative-actions

[8]点击跳转: https://xstate.js.org/docs/guides/communication.html#the-invoke-property

[9]https://xstate.js.org/viz/: https://xstate.js.org/viz/

[10]浅谈状态模式和状态机 - 掘金: https://juejin.cn/post/7005539587510517796

[11]状态模式和策略模式的区别与联系? - 知乎: https://www.zhihu.com/question/23693088

[12]策略模式 VS 状态模式 | 菜鸟教程: https://www.runoob.com/w3cnote/state-vs-strategy.html

[13]下推自动机: https://zh.wikipedia.org/wiki/%E4%B8%8B%E6%8E%A8%E8%87%AA%E5%8A%A8%E6%9C%BA

[14]什么是图灵机: https://zhuanlan.zhihu.com/p/33288542

[15]前端深水区(Deepsea)React 状态管理库研究: https://github.com/w10036w/blog/blob/master/posts/subjects/fe-state-mgmt.zh.md

[16]从有限状态机、图灵机到现代计算机_AdamChen游戏开发-CSDN博客: https://blog.csdn.net/chenchao868/article/details/6833612

[17]XState 状态机与状态图简介 【译】: https://zhuanlan.zhihu.com/p/408123696

[18]浅谈对比 XState、Redux 使用 - 掘金: https://juejin.cn/post/6844904160077283335

[19]XState 新手教學 - Context & Actions: https://blog.jerry-hong.com/posts/xstate-tutorials-context-actions/

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8