文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

前端:从状态管理到有限状态机的思考

2024-12-03 06:07

关注

Vue我们会使用Vuex来管理全局状态, React会使用Redux来管理。

首先是不是,在问为什么?

在使用类似Vue,React框架时,我们一定会使用状态管理吗?这个答案是肯定的。或许我不会主动去使用Vuex, Redux,但我们编写每一个组件的时候就已经在管理状态,Vuex, Redux只是更方便我们进行全局的状态管理。

为什么一定会使用状态管理?这是因为现代前端框架使用数据驱动视图的形式来描述页面。比如,Vue、 React组件会有一个自己内部,外部的状态来共同决定组件的如何显示的,用户与组件交互导致数据变更,进而改变视图。

框架 内部状态 外部状态
Vue data props
React state, useState props

所以我们所写大部分业务逻辑,是在管理状态,框架会帮我们状态映射成视图,这可以说是很经典的MVVM模式。

  1. View = ViewModel(Model); 
  2. // 视图 =  状态 + 管理 
  3. 复制代码 

2. 有限状态机:计算机中一种用来进行对象行为建模的工具

其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。

我们来理解一下上面这段话。

状态机有基本几个要素:

  1. 当前所处状态

    在各个时刻只处于一种状态

  2. 状态转移函数

    在某种条件下,会从一种状态转移到另外一种状态。

  3. 有限状态序列

    拥有有限,可枚举的状态数

 

 


 

 上面这张图所描述的状态机,我们使用js对象来进行描述

  1. const stateTool = { 
  2.   // 当前状态 
  3.   currentState: '1'
  4.    
  5.   // 状态转变函数 
  6.   transition: (event) => { 
  7.   switch(event.type) { 
  8.       case '1': { 
  9.         this.currentState = event.status; 
  10.         doSomething1(); 
  11.         break
  12.       } 
  13.       case '2': { 
  14.         this.currentState = event.status; 
  15.         doSomething2(); 
  16.         break
  17.       } 
  18.       case '3': { 
  19.         this.currentState = event.status; 
  20.         doSomething3(); 
  21.         break
  22.       } 
  23.       default:  
  24.         console.log('Invalid State!'); 
  25.         break
  26.     } 
  27.   } 
  28. 复制代码 

使用有限自动机是一种状态管理的思考方式,我们可以列举组件状态列表,设计触发状态函数。通过外部或内部交互行为,触发函数改变状态,根据状态改变视图

3. Flux思想

Flux是什么?Flux是一个Facebook开发的、利用单向数据流实现的应用架构

简单说,Flux 是一种架构思想,专门解决软件的结构问题。可以说他是有限状态机的另外一种形式。

一个Flux管理分为4个状态:

 

 


 

 

 

要是同学了解flux的的工作流程,那么很容易就发现这是一种工程化的状态机。

 

我们通过store 存放的是初始化状态,这种初始化状态数据可以页面初始化时设定 或 页面加载时请求后端接口数据,来初始化store数据。

通过store的初始化数据,来构建初始化的视图层。

根据视图层的行为会触发action,我们通过统一的dispatcher来收集action, dispatcher将行为派发给store。

store通过判断事件的类型 和 payload,来修改内部存储状态。达到状态转移的目的,并统一提醒view层更新页面;

4. 全局到局部的状态管理

既然我们是通过数据状态来管理视图的,那么在设计初期我们就可以从有限的状态转移来思考业务逻辑。通过思考每个状态对应的数据,状态转移函数,我们可以很清晰的罗列出数据更变逻辑。从数据去控制视图也是现代前端所接触到的MVVM模式。

一个大型应用,我们也会使用Vuex 或 Redux来进行一整个应用的管理。

在平时的业务中,我们会遇到一个痛点是:Vuex,Redux是一个全局状态管理,但我们现在需要在局部需要一个局部状态管理变更,只能使用 mutation 或 dispatch 去提交更改。

如果我们频繁的更新状态,那么我们需要为每一个局部模块编写大量dispatch函数来间接修改全局状态。随着应用的扩充,dispatch文件会越来越臃肿。

那么我们是不是可以使用不同的状态管理工具,来实现局部状态的管理。在局部状态更新完之后,再去用局部更新去更新全局呢?

注:但这也会有一个缺点,局部管理相对独立。有些高度复用的提交函数需要放在全局状态管理上

a. 框架原生组件状态管理

React Hooks + React.createContext

React Hooks提供了useReducer + useContext + Context 可以实现一个小型的状态管理

  1. // 以下代码就实现了一个能够穿透组件的状态管理 
  2. import React, { useReducer, useContext } from 'react'
  3.  
  4. const reducer = (state = 0, { type, ...payload }) => { 
  5.   switch (type) { 
  6.     case 'add'
  7.       return state + 1
  8.     case 'desc'
  9.       return state - 1
  10.     default
  11.       return state; 
  12.   } 
  13.  
  14. const Context = React.createContext(); 
  15. const Parent = () => { 
  16.   const [state, dispatch] = useReducer(reducer, 0); 
  17.   return ( 
  18.     <> 
  19.        
  20.          
  21.        
  22.      
  23.   ) 
  24.  
  25. function Son() { 
  26.   return  
  27.  
  28. function Counter() { 
  29.   const { state, dispatch } = useContext(Context); 
  30.   return ( 
  31.     
     
  32.        dispatch({ type: 'desc' })}>- 
  33.       {state} 
  34.        dispatch({ type: 'add' })}>+ 
  35.     
 
  •   ) 
  • export default Parent; 
  • 复制代码 
  • Vue响应式数据 + vue.Provide/inject

    使用vue响应式系统 + provide/inject API来实现一个具有穿透性的局部状态管理

    1. // Parent.vue 
    2.  
    3.  
    4.  
    5. import { provide, reactive, readonly } from "vue"
    6. import Son from "./Son.vue"
    7. const data = reactive({ 
    8.   count: 0
    9. }); 
    10. const onAdd = () => { 
    11.   data.count++; 
    12. }; 
    13. const onDesc = () => { 
    14.   data.count--; 
    15. }; 
    16. provide("store", { 
    17.   data: readonly(data), // 只读属性 
    18.   onAdd, // 修改函数add 
    19.   onDesc, // 修改函数desc 
    20. }); 
    21.  
    22.  
    23. 复制代码 
    1. // Son.vue 
    2.  
    3.  
    4.  
    5. import Counter from "./Counter.vue"
    6.  
    7. 复制代码 
    1. // Counter.vue 
    2.  
    3.  
    4.  
    5. import { inject } from "vue"
    6. const store = inject("store", {}); // 穿透读取store 
    7.  
    8. 复制代码 

    b. 线性状态管理:Xstate

     

     

    Xstate是一个很有趣的类似有限状态机的状态管理, Xstate 着重点在于 管理状态 ,通过 状态转换去维护数据 。

    我们来定义一个简单的promise状态机,使用官方提供的工具进行可视化 

     

     

     

    1. import { Machine } from 'xstate'
    2.  
    3. // 创建状态机 
    4. const promiseMachine = Machine({ 
    5.   id: 'promise'// 唯一id 
    6.   initial: 'pending'// 初始化状态 
    7.   states: { // 状态集合 
    8.     pending: { 
    9.       on: { 
    10.         RESOLVE: 'resolved'
    11.         REJECT: 'rejected'
    12.    } 
    13.     }, 
    14.     resolved: { 
    15.       type: 'final'
    16.     }, 
    17.     rejected: { 
    18.       type: 'final' 
    19.     } 
    20.   } 
    21. }) 
    22. 复制代码 

    注意:warning::状态机不拥有状态,他只是定义状态和定义状态转移

    Xstate有提供函数来实现状态机服务,实现拥有状态的实体

    1. import { interpret } from 'xstate' 
    2. const promiseService = interpret(promiseMachine).onTransition(state =>  
    3.   console.log(state.value) 
    4. // 创建服务,指定状态转移时回调函数 
    5. promiseService.start() // 启动服务 
    6. promiseService.send('RESOLVE'); // 通知服务转移状态,并执行回调函数 
    7. 复制代码 

    这样子我们就实现了一个简单的Promise状态机。他有很多应用,可以结合Vue,结合React进行使用。更加深入的内容就需要到官方文档中自行探索了!

    就我个人的看法,状态机思想非常适合状态转移相对线形的场景,在某些状态多循环的场景转移会相对复杂些

    c. 可响应式的状态管理器:Mobx

    mobx是一种响应式的状态管理,他所提倡的是拆分store做数据管理。这就很适合做局部的状态管理,根据局部状态管理来更新全局状态。

    相同的,我们举个例子

    1. import { action, autorun, observable } from 'mobx' 
    2. import { observer } from 'mobx-react' 
    3. import React from 'react' 
    4.  
    5. const appStore = observable({ // 建立store 
    6.   count: 0
    7.   age: 18
    8. }) 
    9.  
    10. // autorun 只会观察依赖的相关数据 
    11. // 使用当appStore.age更新时,才会触发该函数 
    12. autorun(() => { 
    13.   // doSomething(); 
    14.   console.log('autorun', appStore.age); 
    15. }) 
    16.  
    17. const Counter = observer(() => { 
    18.   const { count } = appStore; 
    19.   const onAdd = action(() => { // 使用action更新store数据 
    20.     appStore.count++; 
    21.   }) 
    22.   const onDesc = action(() => { 
    23.     appStore.count--; 
    24.   }) 
    25.   return ( 
    26.     
       
    27.       
    28.       {count} 
    29.       
    30.     
     
  •   ) 
  • }) 
  •  
  • export default Counter; 
  • 复制代码 
  • 5. 总结

    现在前端主流使用数据驱动视图的形式,来实现业务。希望给大家带来两点启发

    1. 用有限状态机去思考某些线性状态场景的数据管理。

    2. 在之前的业务开发的时候,就会出现一个痛点,应用全局状态管理非常臃肿。

      在不断功能迭代的过程中,需要做不同的状态管理,虽然都是对同一份数据进行维护,但维护的方式不同,进行一次状态更新就需要编写一个不同的dispatch函数。随着业务需求的增加,dispatch函数越来越多,难以管理和复用。

      思考如何解决这个问题的时,偶然看到了有限状态机相关文章,思考到应用的功能模块在某一个时刻是相互独立的,我们在局部将数据进行更新,之后用一个全局函数对数据进行统一替换。

    注:本文为探索性质,使用原生组件进行局部管理不需要引入依赖。但使用第三方工具造成包体积大小的增加,是否会增加性能消耗有待讨论

     

    来源:高级前端进阶内容投诉

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    软考中级精品资料免费领

    • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

      难度     813人已做
      查看
    • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

      难度     354人已做
      查看
    • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

      难度     318人已做
      查看
    • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

      难度     435人已做
      查看
    • 2024年上半年系统架构设计师考试综合知识真题

      难度     224人已做
      查看

    相关文章

    发现更多好内容

    猜你喜欢

    AI推送时光机
    位置:首页-资讯-后端开发
    咦!没有更多了?去看看其它编程学习网 内容吧
    首页课程
    资料下载
    问答资讯