浅解Mobx

585 阅读7分钟

一.什么是mobx

MobX类似于vue中的vuex,是一种较为简洁的状态管理解决方案,他使用类装饰器或函数装饰可观察值、动作和计算值,然后在我需要的任何地方使用组件中的存储,我们将只需将存储注入组件并在我需要的任何地方观察状态. 并且有效的使用它。

MobX 的一些核心原则是:

  • MobX可以有多个存储来存储应用程序的状态。
  • 任何可以从状态派生而不需要任何进一步交互的东西都是派生。
  • Action是任何可以改变状态的代码。
  • 当状态发生变化时,所有的派生都会自动和自动地更新。

image.png

二.mobx初始化

2.1 mobx依赖安装

yarn add mobx mobx-react -S
yarn add react-app-rewired customize-cra @babel/plugin-proposal-decorators  -D

2.2 mobx依赖配置

在根目录下新建config-overrides.js

const { override, addDecoratorsLegacy } = require("customize-cra")
module.exports = override(
  addDecoratorsLegacy()
)

将package.json的script修改为如下

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom" ,
    "eject": "react-scripts eject"
  },

三.基本使用

3.1 基础计数器案例

  • 1.初始化容器仓库
  • 2.在组件中使用 mobx容器状态
  • 3.在组件中发起action修改容器状态
import React,{Component} from "react";
import ReactDOM from "react-dom";
import { observable, action, makeAutoObservable } from "mobx";
import {observer} from 'mobx-react'
//1.初始化容器仓库
class Store {
  //将count转化为可以被观测的数据
  @observable count = 0;
  //mobx6.0以后一定要加这一行
  constructor(){
    makeAutoObservable(this)
}
  @action
   increment=()=> {
    this.count++;
  }
}

//2.在组件中使用 mobx容器状态
@observer
class App extends Component {
 render(){
  const {store}=this.props
  return (
    <>
      <h1>I am ws</h1>
      <h1>{store.count}</h1>
      {/* 调用仓库里面的方法 */}
      <button onClick={store.increment}>加一</button>
    </>
  )
 }
}
//3.在组件中发起action修改容器状态

//这里将Store仓库传递给组件,连接mobx仓库与组件
ReactDOM.render(<App store={new Store()}/>, document.getElementById("root"));

3.2 装饰器语法

装饰器(Decorator)是es6之后一种与类(class)相关的语法,用来注释或修改类和类方法,装饰器只能用于类和类的方法,不能用于函数,因为函数存在函数提升。如果一定要装饰函数,可以采用高阶函数的形式直接执行。

3.2.1 装饰器的基本使用

//装饰器就是修饰类的一个函数
//添加类的静态成员
function fn(target){
    target.foo='ws'
}
@fn
class Myclass{}
console.log(Myclass.foo);

//带参数的装饰器
@fn2(10)
class Mycfass{}
function fn2(value){
   return function(target){
      target.count=value
   }
}
console.log(Mycfass.count);
);

3.2.2 装饰器实现继承

//这里将list对象展开后,将其属性放到目标元素的原型上
function mixin(...list){
    return function(target){
    Object.assign(target.prototype,...list)
    }

}

const Ws={
    play(){
        console.log('ws');
    }
}

@mixin(Foo)
class Myclass{

}

//这里继承了mixin的方法
new Myclass().play()

3.3.3 装饰器修饰类成员

//装饰器修饰类的内部成员
class Mrclass{
    @readonly message='heelo'
    @noenumerable bar='fff'
}

function readonly(target,name,descriptor){
    console.log(target);//目标类的prototype
    console.log(name);//被修饰类成员的名称
    console.log(descriptor);//被修饰的类成员的描述对象

    //只读的
    descriptor.writable=false

}


function noenumerable(target,name,descriptor){
    console.log(target);//目标类的prototype
    console.log(name);//被修饰类成员的名称
    console.log(descriptor);//被修饰的类成员的描述对象

    //不可以被遍历
    descriptor.enumerable=false

}



const c=new Mrclass()
console.log(c.message);
for(let k in c){
    console.log(c[k]);
}

3.3 属性方法

3.3.1 observable

Observable将普通的数据转化为可以被观测的数据,它的值可以是JS基本数据类型、引用类型、普通对象、类实例、数组和映射,希望数据发生改变触发试图更新,就可以使用这个装饰器来修饰数据

class Store {
  //将count转化为可以被观测的数据,外部可以检测它的改变
  @observable count = 0
  //一定要加这一行
  constructor(){
      makeAutoObservable(this)
  }
  @action 
  increment=()=>{
    this.count++
  }
  foo='bar'
}

3.3.2 computed

计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。 概念上来说,它们与excel表格中的公式十分相似。 不要低估计算值,因为它们有助于使实际可修改的状态尽可能的小。 此外计算值还是高度优化过的,所以尽可能的多使用它们。

class Store {
  @observable price=10
  @observable num=5

  @computed get totalPice(){
    return this.price*this.num
  }
}

   <p>总价格:{store.totalPice}</p>
    <p>总价格:{store.totalPice}</p>
    <p>总价格:{store.totalPice}</p>

3.3.3 action

3.3.3.1基本使用

需要改变observable中的数据,最好使用action修改,尽可能避免使用store.的方式直接修改

import React from 'react';
import ReactDOM from 'react-dom';
import {observable,action,makeAutoObservable, autorun,computed} from 'mobx'
import {observer}from 'mobx-react'

//1.初始化mobx仓库
class Store {
  @observable count = 0
  //一定要加这一行
  constructor(){
      makeAutoObservable(this)
  }
  @action 
  increment=()=>{
    this.count++
  }
  foo='bar'

  @observable price=10
  @observable num=5

  @computed get totalPice(){
    return this.price*this.num
  }

  @action change(){
      this.count=10
      this.foo='hello'
      this.foo='word'
  }
}

const store=new Store()
autorun(()=>{
  //这里只触发了一次
  console.log(store.foo,store.count,'fffrrrf');
})
store.count=20
store.foo='ttt'
store.change()
@observer
class App extends React.Component{
  render(){
    const {store}=this.props
    return(
      <div>
        <h1>我是一个仓库</h1>
        <h2>{store.count}</h2>
        <button onClick={store.increment}>增加</button>
        <p>总价格:{store.price*store.num}</p>
      </div>
    )
  }
}
ReactDOM.render(
    <App store={new Store()}/>,
  document.getElementById('root')
);

3.3.4 异步action

import React from "react";
import ReactDOM from "react-dom";
import {
  observable,
  action,
  makeAutoObservable,
  autorun,
  configure,
  runInAction
} from "mobx";
import { observer } from "mobx-react";
configure({
  enforceActions: "observed",
});
//初始化mobx仓库
class Store {
  @observable count = 0;
  @observable num1 = 0;
  @observable num2 = 0;
  //一定要加这一行
  constructor() {
    makeAutoObservable(this);
  }
  @action
  increment = () => {
    this.count++;
  };

//1.定义一个同步函数用于修改,在异步函数中调用
  @action.bound
  changeCountAsync() {
    setTimeout(() => {
      this.shngecount()
    }, 1000);
  }

  @action.bound
  shngecount(){
    this.count=100
  }
//2.在异步函数中调用action函数,第一参数是修改函数的名称,这里使用函数自调用
  @action.bound
  changenum1Async() {
    setTimeout(() => {
      action('changnum1',()=>{
        console.log(this);
        this.num1=1002
      })()
    }, 1000);
  }
//3.使用runInAction
@action.bound
changenum2Async() {
  setTimeout(() => {
    runInAction(()=>{
      console.log(this);
      this.num2=1003
    })
  }, 1000);
}
}

const store = new Store();;
autorun(() => {
  //当store.count发生改变。他会自动触发,需要给数据成员加上@observable
  console.log( store.count, "fffrrrf");
  console.log( store.num1, "num1ffrrrf");
  console.log( store.num2, "num2ffrrrf");
});
//函数调用
store.changeCountAsync()
store.changenum1Async()
store.changenum2Async()
@observer
class App extends React.Component {
  render() {
    const { store } = this.props;
    return (
      <div>
        <h1>我是一个仓库</h1>
        <h2>{store.count}</h2>
        <button onClick={store.increment}>增加</button>
      </div>
    );
  }
}
ReactDOM.render(<App store={new Store()} />, document.getElementById("root"));
  • action.bound

绑定this,让当前this指向store,注意: action.bound 不要和箭头函数一起使用;箭头函数已经是绑定过的并且不能重新绑定。

3.3.5 configure

强制使用action修改数据,使用严格模式。

import {observable,action,makeAutoObservable, autorun,computed,configure} from 'mobx'
configure({
  enforceActions:'observed'
})

3.3.6 runInAction

import React from 'react';
import ReactDOM from 'react-dom';
import {observable,action,makeAutoObservable, autorun,computed,configure,runInAction} from 'mobx'
import {observer}from 'mobx-react'
configure({
  enforceActions:'observed'
})
//1.初始化mobx仓库
class Store {
  @observable count = 0
  @observable foo= 'sxsddde'
  //一定要加这一行
  constructor(){
      makeAutoObservable(this)
  }
  @action 
  increment=()=>{
    this.count++
  }
}

const store=new Store()
autorun(()=>{
  console.log(store.foo,store.count,'fffrrrf');
})
//修改数据成员,适用于不定义action
runInAction(()=>{
  store.count=10
  store.foo='hello'
})


@observer
class App extends React.Component{
  render(){
    const {store}=this.props
    return(
      <div>
        <h1>我是一个仓库</h1>
        <h2>{store.count}</h2>
        <button onClick={store.increment}>增加</button>
      </div>
    )
  }
}
ReactDOM.render(
    <App store={new Store()}/>,
  document.getElementById('root')
);

3.4 监听数据

3.4.1 autorun

auto可以从外部检测被observable修饰后数据的改变,如果你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用 mobx.autorun。 这通常是当你需要从反应式代码桥接到命令式代码的情况,例如打印日志、持久化或者更新UI的代码。

import {observable,action,makeAutoObservable, autorun} from 'mobx'
autorun(()=>{
  //当store.count发生改变。他会自动触发,需要给数据成员加上@observable
  console.log(store.count,'gggg');
  console.log(store.foo,'gggg');
}

3.4.2 when

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

//当count大于等于100的时候,只执行一次自定义逻辑
//如果条件满足一上来就会执行,只会执行一次
when(
  ()=>{
    return store.count>=100
  },
  ()=>{
    console.log('when=>',store.count);
  }
)
3.reac

3.4.3 reaction

我们可以使用该reaction函数来观察可观察变量的变化。它不会在最初创建时监视该值。reaction接受一个回调来观察值并返回我们想要的东西。第二个参数采用一个值,该值采用第一个函数的返回值,然后我们可以对其执行一些副作用。第三个参数采用一个具有多种选项的对象。

import { reaction, observable } from "mobx";
const todos = observable([  
  {  
    title: "eat"  
  },  
  {  
    title: "drink"  
  }  
]);
reaction(  
  () => todos.map(todo => todo.title),  
  titles => console.log(titles.join(", "))  
);

todos.push({ title: "play" });

3.5 Mobx与React Hooks结合使用

对于 React,我们通过mobx-react包获得官方绑定。但是对于hooks,我们需要使用另一个库mobx-react-lite。它为我们提供了自定义hooks,我们可以直接使用它在我们的组件中创建可观察对象。

import React from 'react'
import ReactDOM from 'react-dom'
import { observer, useObservable } from 'mobx-react-lite'

import TodoList from './components/TodoList'
import Footer from './components/Footer'

export const App = observer(() => {
//useObservable为我们提供了一种在一个对象中创建可观察对象、动作和计算属性的新方法。
//可以在此对象上访问所需的值,并且组件通过observer装饰器对数据变化做出反应。
  const store = useObservable({
    todos: [
      { id: 1, text: 'i am ws', completed: true },
      { id: 2, text: 'i love qinying', completed: false }
    ],
    toggleTodo(index) {
      store.todos[index].completed = !store.todos[index]
        .completed
    },
    get remainingTodos() {
      return store.todos.filter(t => !t.completed).length
    }
  })
  return (
    <div className="App">
      <h2>这是一个todolist</h2>
      <TodoList
        todos={store.todos}
        toggleTodo={store.toggleTodo}
      />
      <Footer
        remaining={store.remainingTodos}
        total={store.todos.length}
      />
    </div>
  )
})
ReactDOM.render(<App />,document.getElementById('root'))

四.记录

本人是一个菜鸟,保持着菜鸟的心态写下此文,这篇文档为参考官网以及学习过程中的一些探索,写这篇文档目的是趁着工作项目闲暇期目对mobx基础知识进行一个回顾与学习记录,不复杂,适合新手教学。