WdBly Blog

懂事、有趣、保持理智

周维的个人Blog

懂事、有趣、保持理智

站点概览

周维 | Jim

603927378@qq.com

推荐阅读

Mobx + React使用指南

Mobx是一个简单、可扩展的状态管理库

一、Mobx - 前端状态管理框架 基础概念

什么是Mobx?

Mobx是一个前端“状态管理框架”

什么是状态管理?

状态管理就是将分布在各个组件、各个模块中的状态的变化,按照一定的规则,进行统一的管理。

为什么需要状态管理?

随着ES6模块化标准的确定和更新,前端开发以及进入了按组件开发的阶段。前端承载的需求和功能也在逐步增加,各种SPA搭建的中台、后台应用也越来越多,前端工作的复杂度也在直线上升,前端页面上展示的信息越来越多也越来越复杂。

随着组件数量的增加、系统的结构越来越复杂。各大前端框架(React、Vue)提供的单向数据流的运转方式,已经不能满足复杂系统的需求。

  • 跨层次组件的数据共享
  • 兄弟组件的数据共享
  • 数据状态的所有变化无法方便追溯

状态管理的一般思想(Flux)

Flux的核心思想就是数据和逻辑永远单向流动。

image.png

Flux中的数据单向和React中的单向数据流有所不同,React中的单向数据流是指的组件间通信的数据流向只能是从父组件->子组件的这样一种形式。而Flux思想中的单向数据流,指的是在应用程序中,数据变化的过程和方向是单向的。

不难发现,Flux其实是提供了一个数据中心化控制的方案。每个数据的变化都是在“动作”中去触发。Flux架构的特点主要是组件内部不包含状态,所有状态放到Store中统一管理,通过监听Action来具体执行操作,这样的好处在于:

  • 视图组件不包含状态,很纯粹,只包含了渲染逻辑和触发 action 这两个职责。
  • 通过Action即可方便追溯Store的所有变化
  • 由于是中心化的数据管理,也就不存在兄弟/跨层级组件数据共享问题

市面上也有很多基于Flux的实现,如Redux,Vuex等。

有哪些常见的实现?

  • Redux
  • Mobx
  • Vuex
  • Context (React Hook)

二、mobx的用法

1、定义状态并使其可观察

import { makeAutoObservable } from 'mobx' // 1、自定义用法 import { configure, makeObservable, observable, action, computed } from 'mobx' // 使用该配置,可以将 Proxy 降级为 Object.defineProperty configure({ useProxies: "never" }); // 构造响应对象 const state = makeObservable( // 需要代理的响应对象 { count: 0, get double() { return this.count * 2 }, increment() { this.count += 1 }, decrement() { this.count -= 1 } }, // 对各个属性进行包装,用于标记该属性的作用 { count: observable, // 需要跟踪的响应属性 double: computed, // 计算属性 increment: action, // action 调用后,会修改响应对象 decrement: action, // action 调用后,会修改响应对象 } ) // 或 const appState = observable({ count: 0 }); // 2、最简单的用法 makeAutoObservable 中的所有方法都会被处理成 action。 const store = makeAutoObservable({ count: 0, get double() { return this.count * 2 }, increment() { this.count += 1 }, decrement() { this.count -= 1 } })
computed 计算值

计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值

MobX 的计算属性属性与 Vue 的 computed 一样,在makeAutoObservable中就是一个gettergetter依赖的值一旦发生变化,getter本身的返回值也会跟随变化。

会自动将 getter 属性推导成计算属性

2、创建视图以响应状态的变化

image.png

单独的Mobx库只是提供了,action->store的转换,没有store->view的转变。原因在于,mobx提供的是一个通用的状态管理方案,各个前端框架的store->view的做法是不相同的,所有没有纳入Mobx库。但是一个完整的状态管理,这一步也是必不可少的,所有mobx提供了(mobx-react, mobx-react-lite)库来处理数据变化后的视图更新问题。

React是通过手动调用setState({})方法更新视图,以达到store->view的转换,Vue store->view的转换则是在过对象劫持(Object.defineProperty / Proxy)的自动回调中更新的。

我们这里先只使用Mobx达到效果

autorun
import { autorun, makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, setCount(count) { this.count = count }, increment() { this.count++ }, decrement() { this.count-- } }) document.getElementById("increment").onclick = function () { store.count++ } const $count = document.getElementById("count") $count.innerText = `${store.count}` autorun(() => { $count.innerText = `${store.count}` })

通过 autorun 方法可以实现这个能力,我们可以把 autorun 理解为 React Hooks 中的 useEffect。每当 store 的响应属性发生修改时,传入 autorun 的方法(effect)就会被调用一次。

reaction

reaction(() => data, (data, reaction) => { sideEffect }, options?)

reactionautorun的变种,对于如何追踪 observable 赋予了更细粒度的控制

// 第一个方法的返回值修改后才会调用后面的 effect reaction( // 表示 state.count 修改后才会调用 () => state.count, // 第一个参数为当前值,第二个参数为修改前的值 // 有点类似与 Vue 中的 watch (value, prevValue) => { console.log('diff', value - prevValue) }, { // 去抖动 delay: 200, onError: () => {} } );
when

when(predicate: () => boolean, effect?: () => void, options?)

when 观察并运行给定的 predicate,直到返回true。 一旦返回 true,给定的 effect 就会被执行,然后 autorunner(自动运行程序) 会被清理。 该函数返回一个清理器以提前取消自动运行程序。

// 第一个方法的返回值为真,立即调用后面的 effect when(() => state.count > 10, () => { console.log('when', state.count) })

通俗来说,when的回调只会执行一次

3、改变状态以更新视图

action

mobx中通过动作(action)来修改应用状态,并且建议对任何修改 observables 或具有副作用的函数使用action

应该永远只对修改状态的函数使用动作。 只执行查找,过滤器等函数不应该被标记为动作

// 定义动作(action) // 1、通过action函数 const increment2 = action(() => { state.count += 2 }) // 2、通过makeObservable // 3、通过makeAutoObservable // 触发动作 document.getElementById("count").onclick = function () { // 1、通过动作修改状态 state.increment() // 2、直接修改状态 不推荐,Vue就是这种方式 state.count++ // 3、定义在外边的action函数 increment2() }

三、Mobx结合React

结合React和不结合React使用的区别在于 创建视图以响应状态变化那一步

通过props传递状态

// 1、安装mobx-react,并导入observer高阶函数 import { observer } from 'mobx-react' // 2、定义自身组件 function Count({ state }) { return ( <div> <button onClick={() => state.increment()}>+1</button> <button onClick={() => state.decrement()}>-1</button> <div>count: {state.count}</div> <div>double: {state.double}</div> </div> ) } // 3、将组件传入高级函数 返回一个Mobx监控后的组件 export const CountView = observer(Count) // 4、使用CountView组件 <CountView state={state} />

也可以结合 Provider 和 inject注入Store

// App.js import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from "mobx-react"; import Native from './Native'; import * as store from './store' import { CountView } from './Count'; import { ColorView } from './Color'; ReactDOM.render( <React.StrictMode> <Provider {...store} > <CountView /> <ColorView /> <Native /> </Provider> </React.StrictMode>, document.getElementById('root') ); // CountView.js import { observer, inject } from 'mobx-react' function Count({ countState }) { console.log('执行countState了'); return ( <div> <h3>1、Mobx + React + countState</h3> <button onClick={() => countState.increment()}>+1</button> <button onClick={() => countState.decrement()}>-1</button> <div>count: {countState.count}</div> <div>double: {countState.double}</div> </div> ) } export const CountView = inject("countState")(observer(Count))

四、为什么选择Mobx?

回到上面的简介中,我们提到的三个问题,为什么要使用状态管理,这里我们将mobx和redux、useContext进行简单对比说明。

兄弟和跨层级状态共享

首先不管是mobx、redux、useContext都是解决了此问题的。
但是redux、useContext是通过将所有状态放到一个状态树里,全局公用来解决的。
而mobx维护了多个状态树,每个状态树相互独立,通过import或 Provider + inject将状态注入至组件。

使用redux、useContext时,状态树发送变动,所有依赖状态树的组件更新,好处是使用方便,任何组件都可以直接获取到整个组件树,(常见的做法还是只会把一些全局状态放到redux中维护),不好的地方在于组件会发送很多不必要更新。

使用redux、useContext时,状态树发送变动,只会更新使用了此状态的组件,其它组件不会更新。用法上来讲麻烦一点,需要手动往组件里注入状态。

数据状态的所有变化方便追溯

redux、useContext中状态的修改需要发出dispatch,通过触发action来修改。不能直接修改Store,那么可以通过dispatch、action追溯状态的变化。

而Mobx虽然也是遵循了Flux思想,让数据和逻辑单向流动,但是,Mobx底层使用的还是数据劫持(Object.defineProperty / Proxy)。它任然支持直接修改数据并更新视图。

“强制动作” 强制所有状态变更都必须通过动作action来完成。在大型、长期的项目中,这是十分有用的最佳实践。

直接优点

  • 上手快
  • 代码量少
  • 渲染性能高
提交

全部评论0

暂时没有评论...