mobx api学习和使用

613 阅读12分钟

Mobx

mobx.png

前言

  • 在介绍Mobx前,看一下Mobx的中文文档简介,看到这样写着:“MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。”

  • 战火洗礼的库” 怎么看都感觉很奇怪,读起来很拗口??,而且网上很多介绍 MobX 的文章都是这么写的,在 github 翻阅其 README 发现写的是:"MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP)"

    可以看到作者原本要表达的意思是 MobX 是经过了许多的测试,拥有比较强的健壮性。下面是通过谷歌翻译的结果,看起来也比中文网的表达要准确一些。

微信图片_20220224152043.png

  • 简介

    • 是什么

      • Mobx是一个简单的,可扩展的Javascript状态管理库
      • MobX 是由 Mendix、Coinbase、Facebook 开源和众多所赞助的。
      • 一般回和React结合使用(大多数是和react,自己也可以试试vue或者angular)
    • 浏览器支持

      Mobx versionactivity supportedsupported browsers
      5.XYNOT : IE 11 and lower,Node5 and lower
      4.XYany es5 complaint browser
      1-3.XNany es5 complaint browser
    • 变动

      • 最新的mobx版本应该是6.x了,主要是去掉了之前版本的装饰器风格写法,另外引入了makeAutoObservable 这个api,后续也会提到
  • Mobx核心思量

    • 任何源自应用状态的东西都应该自动的获得
    • WechatIMG118.png
    • mobx的核心原理是通过action触发state的变化,进而触发state的衍生对象(computed value & Reactions);开发者只需要定义Observable的数据和由此衍生的数据(computed value)或者操作(Reactions),剩下的更新自然就交给Mobx去做就可以了
  • observable用法

    import {observable} from 'mobx'
    observable(value)
    @observable classProperty = value
    

    observable值可以是JS基本数据类型,引用数据类型,类实例,对象,数组或者映射

  • observable.map

    • observable.map(values) - 创建一个动态键的 observable 映射,如果你不只关注某个特定的entry的更改,而且对添加或删除其他entry时也做出反应的话,那么Observable maps会非常有用

    • observable.map(values) 中的valuse可以是对象,数组或者字符串键的ES6 map

    • 通过es6 map构造函数,你可以使用observable(new Map())或者使用装饰器 @observable map = new Map()的类属性来初始化observable映射

    • 下列observable映射所暴露的方法是根据ES6 Map规范:

      • has(key) - 返回映射是否有提供键对应的项。注意键的存在本身就是可观察的。
      • set(key, value) - 把给定键的值设置为 value 。提供的键如果在映射中不存在的话,那么它会被添加到映射之中。
      • delete(key) - 把给定键和它的值从映射中删除。
      • get(key) - 返回给定键的值(或 undefined)。
      • keys() - 返回映射中存在的所有键的迭代器。插入顺序会被保留。
      • values() - 返回映射中存在的所有值的迭代器。插入顺序会被保留。
      • entries() - 返回一个(保留插入顺序)的数组的迭代器,映射中的每个键值对都会对应数组中的一项 [key, value]
      • forEach(callback:(value, key, map) => void, thisArg?) - 为映射中每个键值对调用给定的回调函数。
      • clear() - 移除映射中的所有项。
      • size - 返回映射中项的数量。
    • 以下函数不属于es6规范,而是由mobx提供:

      • toJS() - 将 observable 映射转换成普通映射。
      • toJSON(). 返回此映射的浅式普通对象表示。(想要深拷贝,请使用 mobx.toJS(map))。
      • intercept(interceptor) - 可以用来在任何变化作用于映射前将其拦截。参见 observe & intercept
      • observe(listener, fireImmediately?) - 注册侦听器,在映射中的每个更改时触发,类似于为 Object.observe 发出的事件。想了解更多详情,请参见 observe & intercept
      • merge(values) - 把提供对象的所有项拷贝到映射中。values 可以是普通对象、entries 数组或者 ES6 字符串键的映射。
      • replace(values) - 用提供值替换映射全部内容。是 .clear().merge(values) 的简写形式。
  • observable.array

    • observable.array(values)可将数组转变为可观察的,也是递归的,数组中的所有值都是可观察的

    • 除了所有内置函数(map(),filter(),reduce(),some()等),observable.map还可以使用

      • intercept(interceptor) - 可以用来在任何变化作用于数组前将其拦截。参见 observe & intercept
      • observe(listener, fireImmediately? = false) - 监听数组的变化。
      • clear() - 从数组中删除所有项。
      • replace(newItems) - 用新项替换数组中所有已存在的项。
      • find(predicate: (item, index, array) => boolean, thisArg?) - 基本上等同于 ES7 的 Array.find 提议。
      • remove(value) - 通过值从数组中移除一个单个的项。如果项被找到并移除的话,返回 true
  • observable.object

    • observable.object(values)可将一个普通js对象的所有属性拷贝至一个克隆对象并将克隆对象可观察化,(普通对象是值不是用构造函数创建出来的对象,而是以Object作为其原型,或者根本没有原型),默认情况下,observable是递归以用的

    • 注意:

      • 只有普通的对象可以转变成 observable 。对于非普通对象,构造函数负责初始化 observable 属性。 要么使用 @observable 注解,要么使用 extendObservable 函数。
      • 属性的 getter 会自动转变成衍生属性,就像 @computed 所做的。
      • observable 是自动递归到整个对象的。在实例化过程中和将来分配给 observable 属性的任何新值的时候。Observable 不会递归到非普通对象中。
      • 这些默认行为能应对95%的场景,但想要更细粒度的控制,比如哪些属性应该转变成可观察的和如何变成可观察的,请参见装饰器
  • boxed values

    • observable.box(value) 接收任何值并把值存储到箱子中。 使用 .get() 可以获取当前值,使用 .set(newValue) 可以更新值。

    • 此外,还可以使用它的 .observe 方法注册回调,以监听对存储值的更改。 但因为 MobX 自动追踪了箱子的变化,在绝大多数情况下最好还是使用像 mobx.autorun 这样的 reaction 来替代

    • observable.box(scalar) 返回的对象签名是:

      • .get() - 返回当前值。
      • .set(value) - 替换当前存储的值并通知所有观察者。
      • intercept(interceptor) - 可以用来在任何变化应用前将其拦截。参见 observe & intercept
      • .observe(callback: (change) => void, fireImmediately = false): disposerFunction - 注册一个观察者函数,每次存储值被替换时触发。返回一个函数以取消观察者。参见 observe & interceptchange 参数是一个对象,其中包含 observable 的 newValueoldValue
  • observable小结:

       import {observable,isArrayLike,extendObservable} from 'mobx'//通过observable将变量转换为可观察的对象
        //mobx对任意变量对处理方式有两种:// 1 :数组、对象以及es6中的map 
            //直接把observable作为函数来将变量转换为可观察对对象,之后对数组、以及map中的数据进行合理的操作。
        //1.1 数组(array)const arr = observable(['a','b','c'])
        console.log(arr[0],arr.push(‘d’))
        //如果直接用observable函数赶回的结果不是数组,而引入isArrayLike模块后,返回的就相当于数组
        //可以用一些数组的方法,但注意不能通过数组越界的方式访问或改变数据数据,mobx不回监视越界的访问
        ​
        ​
        //1.2 对象
        const obj = observable({a:1,b:1})
        console.log(obj.a,obj.b)
        //mobx只能对已有的数据进行监视,如果要监视新增的属性,需要使用mobx的extendObservable()方法,因此最好在程序初始化时就说明所有程序会用的属性//1.3 mapconst map = obervable(new Map())
        map.set('a',1)
        console.log(map.has('a'))
        map.delete('a')
        console.log(map.has('a'))
        //和数组类型相似,返回的结果不是真正的map。但表现的足够接近map,可以调用一些map的方法// 2 :其他类型
        //  一些原始类型数据,如String、Number、Boolean等,需要调用observable.box来将变量包装为可观察的对象,之后对该变量的直接赋值将会被监视。const num = observable.box(20)
        const str = observable.box('hello')
        const bool = observable.box(true)
        console.log(num.get(),str.get(),bool.get())
        num.set(1)
        str.set('hi')
        bool.set(false)
        //使用get方法得到原始类型的值,使用set方法修改原始类型的值//3: 使用deacorators修饰器声明可观察的对象
        //decorator只能修饰类或类的成员,因为此我们要创建一个类class Store {
            @observable num = 20
            @observable str = 'hello'
            @observable bool = true
        }
        const store = new Store()
        ​
        //这个时候不需要用.box来包装可观察的对象,访问直接使用 store.num等,内部方法可以使用this.num获取
    
  • 对可观察的数据做出反应

  • computed

        -   computed可以将其它可观察数据以用户想要的方式组合起来,变成一个新的可观察数据
        -   也可以结合observable使用,来修饰类的成员
        -   ```
            import {observable,computed,autorun} from 'mobx'
            class Store {
                @observable count = 10;
                @observable price = 21
            }
            const store = new Store()
            const desc = comouted(()=>{
                return '个数:'+ store.count +'个; 单价:'+store.price +'元/个'
            })
            ​
            console.log(desc.get())   // 个数:10个;单价:21元/个
            ​
            desc.observable((obj)=>{
                console.log(obj.newValue)
            })
            ​
            store.count = 12
             // count发生变化: 个数:12个; 单价:21元/个
            ​
            store.price = 15
             // price发生变化: 个数:12个; 单价:15元/个
            ​
            ```
    
  • autorun

    • 可观察数据发生改变后,自动执行依赖可观察数据的行为,这个行为一般指的是传入autorun的函数
    • 当项目中可观察数据比较多,数据每发生一次就触发一次autorun,会很浪费计算资源
    • computed值可以作为一种新的可观察数据对待
    • //接上
      autorun(()=>{
          console.log(store.count + '/' + store.price)
      })
      stroe.count = 10 // 10/15
      store.price = 20 // 10/20
      
  • when(提供执行逻辑条件,算是一种改进后的autorun),接收两个参数:

    • 第一个函数:根据可观察数据返回一个布尔值,当布尔值为true时,执行第二个参数,且只执行一次
    • 第二个函数:如果可观察数据返回的布尔值一开始就是true,那么立即同步执行第二个函数
    • import { observable,isArrayLike,computed,autorun,when } from 'mobx';
       
      class Store {
          @observable array = [];
          @observable obj = {};
          @observable map = new Map();
       
          @observable string = 'Hello';
          @observable number = 20;
          @observable bool = false;
       
          @computed get mixed() {
              return store.string + '/' + store.number;
          }
      }
       
      var store = new Store();
       
      when(() => store.bool,() => {console.log("it's true");});
      store.bool = true  // it's true
      
  • reaction:autorun的变种,对于如何追踪observable赋予了更细粒度的控制,接收两个参数:

    • 第一个函数:引用可观察数据,并返回一个值,这个值会作为第二个函数的参数。
    • 第二个函数:副作用函数,当调用时会接收两个参数,第一个参数是由data函数返回的值,第二个参数是当前的reaction,可以用来在执行期间清理reaction
    • import {observable,computed,when,reaction} from 'mobx'
      class Store {
          @observable list = [
              {
                  title:'redux',
                  isFunPro:true
              },{
                  title'mobx',
                  isFunPro:false
              }
          ];
        @observable num = 0
      }
      const store = new Store()
      ​
      // 对title变化做出反应
      const reaction1 = reaction(
        () => store.list.map(item => item.title),
          titles => console.log('reaction1:',titles.join(','))
      )
      ​
      const autorun1 = autorun(() => {
          console.log('autorun1:',store.map(item => item.title).join(','))
      })
      ​
      store.list.push({title:'flux',isFunPro:true})
      ​
      //输出
      reaction1:redux,mobx,flux
      autorun1: redux,mobx,flux
      ​
      const reaction2 = reaction(
        () => store.count,
          (count,reaction) => {
              console.log('store.count:',count)
              reaction.dispose()
          }
      )
      store.count =1// store.count:1
      ​
      store.count = 2//无输出,第二个参数作为清理函数使用console.log(store.count) //2
      
  • 修改可观察数据

    • 可以对多次状态数据的赋值合并为一次,从而减少触发autorun或reaction的次数

    • action既可以作为普通函数也可以作为decorator来使用,常用的是decorator的方式

    • import {observable,computed,action,reaction,autorun} from 'mobx'class Store {
          @observable list = [
              {
                  title:'orange',
                  price:11
              },{
                  title:'apple',
                  price:10
              }
          ];
        @action addFruit(){
              this.list.push({
                  title:'banana',
                  price:20
              })
              this.list.push({
                  title:'cheeries',
                  price:68
              })
          }
      }
      var store = new Store()
      ​
      reaction (()=>store.list.map(item => item.title),titles =>{
          console.log(titles.join(','))
      })
      ​
      store.addFruit()
      // orange,apple,banana,cheeries   只执行了一次reaction
      
    • action还有一种特殊使用方法:action.bound,action.bound 可以用来自动地将动作绑定到目标对象。 注意,与 action 不同的是,(@)action.bound 不需要一个name参数,名称将始终基于动作绑定的属性。

      import {observable,computed,reaction,action} from 'mobx'class Store {
          @observable name = 'Jacky';
        @observable age = 61
        
        @computed get mixed(){
              return this.name + '/' + store.age
          }
        
        @action.bound 
        bar() {
              this.name = 'Alan'
              this.age = 71 
          }
      }
      ​
      var store = new Store()
      ​
      reaction(() => [store.name, store.age], arr => {
          console.log(arr)
      })
      store.bar() // ["Alan", 71]

      注意:action.bound不要和箭头函数一起使用,箭头函数已经是绑定过的并且不能重新绑定

  • runInAction(name?,thunk)

    • runInAction是个简单的工具函数,它接收代码块并在(异步的)动作中执行,这对于及时创建和执行动作非常有用,允许随时定义匿名的action,并运行它
    • import {observable,computed,reaction,runInAction,action} from 'mobx'
      ​
      class Store {
          @observable array = [];
        @observable obj = {};
        @observable map = new Map();
        @observable string = 'hello';
        @observable number = 20;
        @observable bool = false;
        
        
        @computed get mixed(){
              return store.string  + '/' + store.number
          }
      ​
        @action.bound bar(){
              this.string = 'world';
              this.number = 30
          }
      }
      ​
      var store = new Store()
      ​
      reaction(
          ()=> [store.string,store.number],
          arr => console.log(arr.join(','))
      )
      ​
      runInAction(()=>{
          store.string = 'wordl';
          store.number = 30
      })      // 和调用store.bar()  效果一样
      
  • 结合react使用

    • 安装依赖

      npm i mobx mobx-react -S
      
    • 入口文件配置

      import React from 'react'
      import {render} from 'react-dom'
      import {Provider} from 'mobx-react'
      import store from './store'    
      import App from './app'render(
        <Provider {...store}>
            <App />
          </Provider>,
          doucment.querySelector('#root')
      )
      
    • store文件

       // store/index.js
      import {observable,action} from 'mobx'
      ​
      class Store {
        @observable name = 'test'
        @observable list = []
      ​
        @action.bound 
        async fetch(){
              const res = await .....   //异步请求
              this.list = res.data
          }
        setName(name){
              this.name = name
          }
      }
      const store = new Store()
      ​
      export default store
      
    • App页面

      import React, {Compunent} from 'react'
      import {inject,observer} from 'mobx-react'@inject('store')
      @observer
      class App extends Component {
          componentDidMount(){
              this.props.store.fetch()   //获取数据
          }
          edit = () => this.props.store.setName('mobx-test')
        render(){
              const {list,name} = this.props.store
              return (
                <div>
                    <div>name:{name}</div>
                      <button onClick={this,edit}>修改name</button>
                      <ul className='list-box'>
                        {
                              list.map((item,index)=> {
                                  return (
                                    <li key={index}>{item.id}</li>
                                    <li key={index}>{item.name}</li>
                                    <li key={index}>{item.list}</li>
                                  )
                              })
                          }
                      </ul>
                  </div>
              )
          }
      }
      export default App
      
  • mobx 6

    • mobx现在的最新版应该是6,这个版本的api相比于之前有了极大的简化,装饰器声明已经无效,要实现Observable,Action等声明,必须makeObservable或者makeAutoObservable声明
    • //before 
      import {observable,cpmputed,action,makeObservable} form 'mobx'
      ​
      class Store {
          @observable string ='hello'
        @observable num = 10
        @observable list = []
      ​
        @computed
        get unfinishListCount(){
              return this.list.filter(item => !item.done).length
          }
        
        @action.bound
        addItem(item) {
              this.list.push(item)
          }
      }
      ​
      //after
      class Store {
          list = []
        constructor(){
              makeObservable(this, {
                  todos:observable,
                  unfinishListCount:get,
                  addItem:action
              })
          }
        
        get unfinishListCount(){
              return this.list.filter(item => !item.done).length
          }
        addItem(item){
              this.list.push(todo)
          }
      ​
      }
      
    • 看起来貌似比之前的写法并没有简化很多,6.0推出了另外一个api makeAutoObservable
    • import {runInAction,makeAutoObservable,autorun} from 'mobx'
      const  store = makeAutoObservable({
          list:[],
          get unfinishListCount(){
             return this.list.filter(item => !item.done).length
          },
          addItem(item){
              this.list.push(item)
          },
          setList(list){
              this.list = list
          }
          
          async getList(){     //异步
              const res = await *******    //请求数据
              if(res.code===0){
                  const {list}  = res.data
                  
                  runInAction(()=>{
                      this.list = list
                  })
                  //或者可以 使用已经存在的方法 this.setList(list)
              }
          }
      })
      ​
      ​
      autorun((store.list)=> {
          console.log('list长度为:',store.list.length)   
      })
      ​
      //注:makeAutoObservable中所有的方法都被处理成action  get后面的方法都被处理成计算属性 
      
  • 总结:

    • mobx基本介绍到这里就结束了,只是大致的说明了一下mobx中的api和使用方法,个人感觉中文文档写的比较乱,但是个人英文水平也不太高,读起来官方文档也是比较吃力,更不用说按照源文档进行一些演示了,后续还是会深入研究mobx的实现,也希望大家也有所收获,之前也是准备结婚的事情,耽搁了一些时间,现准备差不多了,等结完婚后,再和大家见面啦。
  • 代码地址

  • 参考