当前位置: 首页 > 科技观察

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

时间:2023-03-13 12:59:18 科技观察

1.状态管理在我们的前端开发中,肯定会接触到最流行的框架(Vue、React等)。在使用框架的过程中,我们一定要接触到一些状态管理工具。对于Vue,我们会使用Vuex来管理全局状态,React会使用Redux来管理。首先,你问为什么?在使用Vue、React这样的框架时,是否一定要使用状态管理?答案是肯定的。可能我不会主动去用Vuex和Redux,但是我们在写每个组件的时候就已经在管理状态了。Vuex和Redux只是更方便我们管理全局状态。为什么必须使用状态管理?这是因为现代前端框架以数据驱动视图的形式描述页面。例如,Vue和React组件都有自己的内部和外部状态,共同决定组件如何显示,用户与组件交互引起数据变化,进而改变视图。FrameworkInternalstateExternalstateVuedatapropsReactstate,useStateprops所以我们写的大部分业务逻辑都是在管理状态,而框架会帮我们把状态映射成视图,可以说是非常经典的MVVM模式.View=ViewModel(Model);//View=state+managementcopycode2.有限状态机:在计算机中用来对对象行为进行建模的工具,其作用主要是描述对象在其生命周期中所经历的状态序列,以及如何应对来自外界的各种事件。让我们理解上面这段话。对象行为建模工具我们用来描述对象行为、状态随时间变化的行为的工具。世界上的大多数事情都可以模拟。生命周期我们可以通过抽象对象所经历的状态序列来确定对象的一系列可能的生命周期和转变。响应外部事件外部事件可以影响对象的内部状态。对象可以响应外部事件。状态机有几个基本要素:当前状态在每一时刻只处于一种状态。状态转移函数会在一定条件下从一种状态转移到另一种状态。有限状态序列具有有限且可枚举的状态数。上图描述的状态机,我们使用js对象来描述conststateTool={//当前状态currentState:'1',//状态转换函数transition:(event)=>{switch(event.type){case'1':{this.currentState=event.status;doSomething1();break;}case'2':{this.currentState=event.status;doSomething2();break;}case'3':{this.currentState=event.status;doSomething3();break;}default:console.log('InvalidState!');break;}}}复制代码使用有限自动机是一种状态管理的思路,我们可以枚举组件状态列出和设计触发状态函数。通过外部或内部的交互行为,触发函数改变状态,根据状态改变视图3.Flux思想什么是Flux?Flux是Facebook开发的一种使用单向数据流的应用程序架构。简单来说,Flux是一种专门解决软件结构问题的架构思想。可以说他是有限状态机的另一种形式。一个Flux管理分为4种状态:View:视图层Action(动作):视图层触发的动作或行为Dispatcher(调度器):收集触发行为,统一管理,统一分发到store层。Store(数据层):用于存储应用程序的状态。根据dispatcher触发的行为,提醒Views更新页面。如果同学们了解flux的工作流程,很容易发现这是一个工程状态机。初始状态我们通过商店存储初始化状态。这个初始化状态数据可以在页面初始化时设置,也可以在页面加载时请求后端接口数据来初始化store数据。通过store的初始化数据构建初始化视图层。状态转换事件根据视图层的行为,触发动作。我们通过统一的调度器收集动作,调度器将动作调度到商店。状态转换函数store通过判断事件的类型和payload来修改内部存储状态。达到状态传递的目的,统一提醒视图层更新页面;4.全局到局部的状态管理由于我们是通过数据状态来管理视图的,所以在设计初期就可以从有限的状态转移来思考业务逻辑。通过思考每个状态对应的数据和状态转移函数,我们可以清晰的列出数据变化逻辑。从数据来控制视图也是现代前端所接触到的MVVM模式。对于大型应用,我们也会使用Vuex或者Redux来管理整个应用。在正常的业务中,我们会遇到一个痛点:Vuex,Redux是全局的状态管理,但是现在我们需要在本地进行局部状态管理变更,只能使用mutation或者dispatch来提交变更。如果我们频繁更新状态,那么我们需要为每个本地模块编写大量的调度函数来间接修改全局状态。随着应用程序的扩展,调度文件会变得越来越臃肿。那么我们是否可以使用不同的状态管理工具来实现本地的状态管理。局部状态更新后,如何使用局部更新来更新全局呢?注意:但这也有一个缺点,本地管理相对独立。一些高复用的提交功能需要放在全局状态管理上框架原生组件状态管理ReactHooks+React.createContextReactHooks提供useReducer+useContext+Context实现一个小型的状态管理//下面的代码实现了一个可以穿透组件的状态管理importReact,{useReducer,useContext}from'react';construducer=(state=0,{type,...payload})=>{switch(type){case'add':returnstate+1;case'desc':returnstate-1;default:returnstate;}}constContext=React。createContext();constParent=()=>{const[state,dispatch]=useReducer(reducer,0);return(<>/>)}functionSon(){return}functionCounter(){const{state,dispatch}=useContext(Context);return(

dispatch({type:'desc'})}>-{state}dispatch({type:'add'})}>+
)}exportdefaultParent;复制代码Vue响应式数据+vue.Provide/inject使用vue响应式系统+provide/injectAPI实现一个穿透式的Partial状态管理//Parent.vue复制代码//Son.vue复制代码//Counter.vue复制代码b.线性状态管理:XstateXstate是一个非常有趣的类似于有限状态机的状态管理,Xstate专注于通过状态转换来管理状态和维护数据下面定义一个简单的promise状态机,使用官方提供的可视化工具import{Machine}from'xstate';//创建一个状态机constpromiseMachine=Machine({id:'promise',//uniqueidinitial:'pending',//初始化状态states:{//状态收集pending:{on:{RESOLVE:'resolved',REJECT:'rejected',}},resolved:{type:'final',},rejected:{type:'final'}}})copycodenote:warning::statemachinedoesn'thaveastate,itjustdefinedthestateanddefinedthestatetransitionxstateprovidesfunctionstorealizethestatemachineservice,实现实体import{interpret}from'xstate'thathasthestateconstpromiseService=interpret(promiseMachine).onTransition(state=>console.log(state.value))//创建服务并指定状态转移时的回调函数promiseService.start()//开始服务promiseService.send('RESOLVE');//通知服务转移状态,执行回调函数复制代码。这样,我们就实现了一个简单的Promise状态机。他有很多应用,可以结合Vue和React使用。更深入的内容需要在官方文档中挖掘!个人认为状态机思想很适合状态转换这种比较线性的场景,一些状态多周期的场景转换会比较复杂c。Responsivestatemanager:Mobxmobx是响应式状态管理,他提倡的是拆分store进行数据管理。这非常适合本地状态管理,全局状态根据本地状态管理进行更新。同样,我们举个例子import{action,autorun,observable}from'mobx'import{observer}from'mobx-react'importReactfrom'react'constappStore=observable({//Createstorecount:0,age:18,})//autorun只会观察依赖的相关数据//当appStore.age更新时,会触发函数autorun(()=>{//doSomething();console.log('autorun',appStore.age);})constCounter=observer(()=>{const{count}=appStore;constonAdd=action(()=>{//使用action更新store数据appStore.count++;})constonDesc=action(()=>{appStore.count--;})return(
-{count}+
)})exportdefaultCounter;复制代码5.总结现在前端主流都是采用数据驱动视图的形式来实现业务。希望能给大家带来两个启发:用有限状态机来思考某些线性状态场景下的数据管理。在之前的业务开发中,会有一个痛点,应用全局状态管理非常臃肿。在不断的功能迭代过程中,需要做不同的状态管理。虽然维护的是同一个数据,但是维护的方式不同。要执行状态更新,需要编写不同的调度函数。随着业务需求的增加,调度功能越来越多,难以管理和复用。在思考如何解决这个问题的时候,偶然看到了有限状态机相关的文章。考虑到应用程序的功能模块在某个时刻是相互独立的,我们在本地更新数据,然后使用一个全局函数统一替换数据。注意:本文是探索性的,使用原生组件进行本地管理不需要引入依赖。但是使用第三方工具导致包体积增大,是否会增加性能消耗还有待商榷