mobx快速入门

1 mobx介绍

mobx是啥:是一个用来管理状态的库,如果被观测组件发生改变,会自动渲染有关页面,告别setState。

mbox编程的3个重点:

  1. observer观测器:带有观测器的react组件或者属性被mobx实时观测
  2. observable可观测对象:由mobx建立的可观测对象
  3. action更新事件:标识观测对象的改变事件

Actions:一段可以改变观测变量状态的代码,类似于setState,严格模式下,只有在动作中才可以修改状态

mobx编程的4个概念:

  1. State:状态:相当于有数据的表格
  2. Derivations:驱动
  3. computed value:计算值
  4. reactions:反应

计算值和反应的区别:计算值会产生新的可观测量,反应为所有变量包括可观测变量的一系列运算,并不产生新的可观测量

computed value可以看作一个包含各种计算的变量,计算属性本质是方法,只是在使用这些计算属性的时候,把他们的名称直接当作属性来使用,并不会把计算属性当作方法去调用,不需要加小括号()调用。

此计算属性的方法内部所用到的任何data中的数据,依赖响应属性只要发生改变,就会立即重新计算,即触发这个计算属性的重新求值;否则不会重新计算求值。

计算属性的求值结果会被缓存起来,方便下次直接使用(多次调用只要内部数据不改变就不会重新求值,改变了也只会计算一次,虽然有多个地方引用此属性)。

计算属性的方法内部无论如何都要return出去一个值。

在这里插入图片描述

2 快速开始

mobx 4.+ 5.+
使用装饰器,需要开启支持,如下

若使用create-react-app工具创建的工程,首先需要npm run eject,然后在package.json中输入:

"babel": {
    "plugins": [
      [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ]
    ],
    "presets": [
      "react-app"
    ]
  }

以支持装饰器语法。

导入的包:mobx和mobx-react,可以使用npm install mobx,mobx-react --save来导入,支持函数式组建和类装饰器

如果只使用函数组建可以导入:mobx和mobx-react-lite,需要mobx6+

参考1:

import React from 'react';
import ReactDOM from 'react-dom'
import {observable,action} from 'mobx';
import {observer} from 'mobx-react';
var appState = observable({
    timer: 0
});
appState.resetTimer = action(function reset() {
  appState.timer = 0;
});
setInterval(action(function tick() {
  appState.timer += 1;
}), 1000);

@observer
class TimerView extends React.Component {
    render() {
        return (
            <button onClick={this.onReset.bind(this)}>
                Seconds passed: {this.props.appState.timer}
            </button>
        );
    }
    onReset() {
        this.props.appState.resetTimer();
    }
};
ReactDOM.render(<TimerView appState={appState} />, document.body);

参考2:
使用ES6语法:(有很多解释,主要看这个即可)

// store
import { observable, action, computed } from "mobx"; 

class CountStore {
  // constructor() {
  //   makeObservable(this); // 注意:对于6.0以上版本mobx,需要添加该构造函数才可以正常运行,记得导入方法
  // }
  @observable a = 1;
  b = 2; // 注意此处b为普通属性,这种属性只要没有【被观测属性】发生改变,是不会被观测到改变的!

  @action  // 注意如果使用普通函数,则必须写成:@action.bound,函数中的this才可以正确指向
  add = () => {
    this.a++; // 如果下面一行注释掉,则a被正常观测
    this.b++; // 如果上面一行注释掉,则b无法被观测
    // 如果两个都存在,则ab都被观测到
  };

  @computed get ab() {
    return this.a + this.b;
  }
}

const countStore = new CountStore();
export default countStore;

// 组件
import { observer } from "mobx-react";
import { action } from "mobx";
import React from "react";
import "./styles.css";

@observer
class App extends React.Component {
  // 此处使用action来定义一个外部的action去改变store值,必须要使用action去改变否则无法被观测到
  add2 = action(() => {
    this.props.store.a += 111; // 此处同上,仅改变普通属性的话是无法被观测到的
    this.props.store.b += 111;
  });

  render() {
    console.log(this.props);
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Edit to see some magic happen!</h2>
        <div>{this.props.store.a}</div>
        <div>{this.props.store.b}</div>
        <div>{this.props.store.ab}</div>
        <button onClick={this.props.store.add}>按钮</button>
        <button onClick={this.add2}>按钮</button>
      </div>
    );
  }
}

export default App;

对于函数组件可以这样:

import { observer,inject } from "mobx-react";
import React from "react";
import "./styles.css";

const App = inject('store')(observer((props) => {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Edit to see some magic happen!</h2>
      <div>{props.store.a}</div>
      <div>{props.store.b}</div>
      <div>{props.store.ab}</div>
      <button onClick={props.store.add}>按钮1</button>
      <button onClick={props.store.add2}>按钮2</button>
    </div>
  );
}));

export default App;

3 mobx声明

1. 使用函数封装:

类型:Array/Object/Map:

  • 声明:var arr = observable([1,2,3])
  • 取值:arr[0]
  • 赋值:arr[0]=4

类型:其他需要使用box封装:

  • 声明:var num = observable.box(20)
  • 取值:num.get()
  • 赋值:num.set(4)

注意:只会对声明observable时的已有属性进行观察,新加入的属性不会监视,需要用extendObservable()来处理

建议的最佳实践:在程序初始化时就声明出所有会被使用的属性

2. 使用修饰器

修饰器只能修饰类和类属性,修饰器统一使用@observable来修饰,无需使用box

如:

class Store{

    @observable array=[];

    @observable obj={};

    @observable map=new Map();

    @observable string='abc';

    @observable number=20;

    @observable bool=false;

}

注意:获取和赋值都与正常一样,但是在取值时取得值是mobx的observable类型,可以使用mobx提供的toJS()方法来将该类型转换为普通的js类型。

3. 使用自动配置(mobx6+)

class Store{

	constructor(){
		makeAutoObservable(this)
		// 或
		// makeObservable(this,{
		//	array:observable,
		//	obj:observable
		//	find:action
		})
	}

    array=[];

    obj={};

    find(){}

}

4 函数、注解

1 computed

前提:

class Store{
    @observable string='a';
    @observable num=12;
}
var store=new Store();

computed:组合观察数据形成一个新观测值,接受一个无参数的函数,必须有返回值,且使用get()方法来获取计算值

使用函数:

// 注入store
// 定义foo变量,表示string和num的计算值
var foo=computed(function(){return store.string+'/'+store.num}); 
// 当foo变化时调用指定的函数执行操作 foo.observe(函数)
foo.observe(function(change){console.log(change);}) //可以用来观察监测值,如果有改动就调用函数

使用注解:

// 若使用注解,则必须在store类中事先定义好计算值
@computed
get mixed(){return store.string+'/'+store.num}}; 
// 调用autorun方法,接受一个函数,当函数内部的可观测数据变化时执行函数,此处由于没有使用mixed计算值,则string或者num变化都会输出
autorun(()=>{console.log(return store.string+'/'+store.num);});
// 使用mixed计算值,只有该值变化才输出
autorun(()=>{console.log(store.mixed);})

compute接收参数,可用于控制比较的结果方式
参考文档:https://zh.mobx.js.org/computeds.html
这里解释一下comparer的四个值的含义:

comparer.identity 使用全等 (===)运算符确定两个值是否相同。(不解释,太简单)
comparer.default 与 comparer.identity 相同,但是其认为 NaN 等于 NaN。(多了一个NaN===NaN,无伤大雅)
comparer.structural 执行深层的结构比较以确定两个值是否相同。(会一层一层判断基础值是否相等)
comparer.shallow 执行浅层的结构比较以确定两个值是否相同。(只往下搂一层,然后使用===进行比较)

例子:
m={a:1,b:{x:1}} n={a:1,b:{x:1}}
深层的话相等
浅层的话不等

2 autorun

autorun中的可观察数据如果发生改变,则自动运行一次,注意:第一次定义autorun也会运行一次,接收一个无参函数

3 when

接收两个函数作为参数,当第一个函数返回true,就会执行一次第二个函数,且全局只执行一次,不再被触发

when(()=>stroe.bool,()=>console.log('it is true'));

注意:第一个函数必须根据可观察数据来计算返回值,不能是普通不可观察变量

4 reaction

接收两个函数作为参数,第一个函数中的可观察值变化时执行第二个函数,相当于when的循环版

5 action

该装饰器保证在其内部修改的变量会等待函数运行结束后再同时触发反应。使用方式:

action("xxx",()=>{})

action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应!

这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。可以使用runInAction解决。

@action:修饰的函数必须注意,如果其内部调用了非**@action.bound修饰的异步回调函数,会报错,因为回调函数不在当前上下文环境中,导致this指向错误!可以使用action或者runInAction**包裹代码来解决。

@action.bound:该装饰器将函数上下文绑定了,可以放在其他上下文中依然正确执行,这种写法在将方法传给callback时非常有用

var run = dStore.run
run()

6 runInAction

定义匿名的action然后立即运行,接收一个无参函数

注意:在异步或者回调函数中,@action范围在仅仅在代码块,直到第一个await,如果后续还有其他的await,需要使用runInAction包裹!

原因:action中,在await后的代码属于下一个事件循环,只有在当前事件循环中的代码可以批量更新,如果想在后面更新,需要用runInAction包裹为匿名action然后在下次循环中作为action调用
可以使用flow+yeild来处理异步情况

7 observer

属于mobx-react,与react结合使用,在会调用函数导致改变mobx状态的react组件上进行注解

使用方法:

  1. 注解
  2. observer函数:从最外层包裹(用于函数组件)const A = observer((props=>{return <div>1</div>},从render包裹(通用)在react组件的render中进行包裹:return useObserver(()=><div>1</div>)
  3. <Observer>标签:包裹住的值才能相应状态,<Observer>{()=><div>{person.name}</div>}</Observer>

在使用hook的函数组件中,将react组件转换成可观测的,要求mobx 6+
在mobx中使用mobx-hook,需要mobx 6+,如useObserver

参考:https://www.cnblogs.com/Grewer/p/12129391.html

8 Provider

属于mobx-react,如果当前react项目需要启用mobx管理状态,则必须在根节点上使用Provider标签包裹,同时传递注入对象

在store.js中汇总所有待管理store

import test from './test'
import mast from './mast'
const stores = {
    test,
    mast,
}
export default stores

在App.jsx中

import { Provider } from "mobx-react"
import stores from './store'
import {configure} from 'mobx'; // 开启严格模式
configure({enforceActions: true}) // 开启严格模式

class App extends Component{
    render(){
        return(
            <Provider store={...store}>
                <ToDoApp/>
            </Provider>
        )
    }
}

configure 代表开启了严格模式,因为非严格模式下,组件是直接可以通过props.action改变state数据的,当项目复杂的时候这样是非常危险的。所以要设置唯一改变state方式是通过action

9 inject

属于mobx-react,对当前的react组件进行注入store,以便访问可观察值,注入的值在this.props

@inject('todoList')
class ToDoApp extends Component{
    render(){
        return (
            <div>
                <TodoListView todoList={this.props.todoList}/>
            </div>
        );
    }
}

函数组件中的用法:export default inject("xxxStore")(observer(react组件))

mobx立足于react的context实现了inject语法,通过简洁的api,可以在任何想要使用全局状态的地方注入store。

10 flow

用于异步action的使用,可以替代async/await来简化

// bind是否需要,看使用时是直接讲该函数赋值给时间处理函数还是通过类调用

// 在mobx6以下使用
querySomething = flow(function* () {
  const result = yield new Promise((resolve) =>
    setTimeout(() => resolve("xiaopi come on!"), 2000)
  );
  console.log(result, this);
  this.msg = result;
}).bind(this);

// 在mobx6使用
constructor() {
  makeAutoObservable(this);
}
// 生成器写法
*querySomething () {
  const result = yield new Promise((resolve) =>
    setTimeout(() => resolve("xiaopi come on!"), 2000)
  );
  console.log(result, this);
  this.msg = result;
}).bind(this);
// 普通写法
async querySomething () {
  const result = await new Promise((resolve) =>
    setTimeout(() => resolve("xiaopi come on!"), 2000)
  );
  console.log(result, this);
  runInAction(()=>this.msg = result)
}).bind(this);

如果使用ts需要:const msg= await flowResult(store.querySomething ())加上flowResult函数才行

5 mobx6

辅助阅读:https://www.jianshu.com/p/c264a5a5aee2

在函数组件中,可以使用新的写法来简化mobx使用
注意:使用React.useContext(MobXProviderContext)必须确保store被托管到context中,即使用了Provider组件

// 强烈建议:新建自己的hook,去使用store
import { MobXProviderContext } from 'mobx-react'
function useStores(name) {
  return React.useContext(MobXProviderContext)[name]
}
// 定义UI组件,使用observer包裹整个组建
const UserOrderInfo = observer(() => {
  const xxxStore = useStore('xxxStore')
  return (
    <div>
      {xxxStore.username} has order {xxxStore.orderId}
    </div>
  )
})
// 或:推荐函数式组件写法,使用useObserver包裹jsx
const UserOrderInfo = () => {
  const xxxStore = useStore('xxxStore')
  return useObserver(()=>(
    <div>
      {xxxStore.username} has order {xxxStore.orderId}
    </div>
  ))
}
// 或:Observer标签接收函数
const UserOrderInfo = () => {
  const xxxStore = useStore('xxxStore')
  return <Observer>{()=>(
    <div>
      {xxxStore.username} has order {xxxStore.orderId}
    </div>
  )}</Observer>
}

*手动实现inject方法:

import { MobXProviderContext } from 'mobx-react'
function inject(selector, baseComponent) {
  const component = ownProps => {
    const store = React.useContext(MobXProviderContext)
    return useObserver(() => baseComponent(selector({ store, ownProps })))
  }
  component.displayName = baseComponent.name
  return component
}

参考:https://blog.csdn.net/xiaohulidashabi/article/details/103531146

*5.1 useLocalStore/useLocalObservable

useLocalStore已过期,使用useLocalObservable替代

*对于没有使用Provider进行store注入的组件,可以使用const {xxx}= useLocalStore(() => stores);来局部使用mobx管理状态,同样,使用了局部或注入了相同store的组件依然能观察到该状态的变化!

// 1 局部store注入
import React from 'react';
import { observable, Observer, useLocalStore } from 'mobx-react';
import {stores} from './stores';

function Demo1() { 
    const {countStore} = useLocalStore(() => stores);
    return <Observer>{() => <div onClick={countStore.add}>{countStore.count}</div>}</Observer>
}

临时想生成仅供当前组件使用的状态,可以使用如下方法:

// 2 局部生成store,无需注入,因为内部生成
const Demo2 = observer((props)=>{ 
    const [countStore] = useState(() => observable({count:0})) // 局部生成只能放置数据
    const [countStore2] = useState(() => observable({ a: 99, b: 999 })); // 或者使用这种方式,是等效的
    const add = action(()=>countStore.count++) // 由于数据中没有方法,所以只能在外部定义了
    return <div onClick={add}>{countStore.count}</div>
}

如果需要让hook组件暴露出一些属性给父组件调用,可以参考使用:useImperativeHandle,https://blog.csdn.net/xiapi3/article/details/106357832

*5.2 make(Auto)Observable 和 observable 之间最主要的区别

observable(使用代理)与 makeObservable(不使用代理)

  1. make(Auto)Observableobservable 之间最主要的区别在于,make(Auto)Observable 会修改你作为第一个参数传入的对象,而 observable 会创建一个可观察的 副本 对象。

  2. 第二个区别是,observable 会创建一个 Proxy 对象,以便能够在你将该对象当作动态查询映射使用时捕获将要添加的属性。 如果你想把一个对象转化为可观察对象,而这个对象具有一个常规结构,其中所有的成员都是事先已知的,那么我们建议使用 makeObservable,因为非代理对象的速度稍快一些,而且它们在调试器和 console.log 中更容易检查。

  3. make(Auto)Observable仅仅支持已声明且赋值的字段

  4. makeObservable只能注解由其本身所在的类定义声明出来的属性。如果一个子类或超类引入了可观察字段,那么该子类或超类就必须自己为那些属性调用 makeObservable

因此,make(Auto)Observable 推荐在工厂函数中使用。 值得一提的是,可以将 { proxy: false } 作为 option 传入 observable 获取非代理副本。

*5.3 可观察对象转为普通对象

observable 转换回普通的 JavaScript 集合
有时有必要将可观察的数据结构转换回原生的数据结构。 例如,将可观察对象传入一个无法跟踪可观察对象的 React 组件时,或者想要获取一个不会再被更改的副本时。

要进行浅转换,用常用的 JavaScript 操作就可以做到:

const plainObject = { ...observableObject }
const plainArray = observableArray.slice()
const plainMap = new Map(observableMap)

要将数据树递归地转换为普通对象,可使用 toJS 工具函数。 对于类,建议实现一个 toJSON() 方法,因为这样会被 JSON.stringify 识别出来

6 参考示例

// mobx定义一个store
import { observable, autorun, computed, when ,reaction, action, runInAction} from 'mobx';
class DemoStore{
    @observable age = 0;
    @observable name = 'pp';
    @observable number = 0;
    @computed get lucky(){
        return this.age+this.number;
    }
    @action run(){
        this.age=111;
        this.name='gaga';
        this.number=222;
    }
    @action.bound runOut(){
        this.age=222;
        this.name='jjj';
        this.number=this.age+this.number;
    }
}

// 测试
var dStore = new DemoStore();
autorun(()=>{
    console.log("autorun:"+dStore.lucky)
})
when(()=>dStore.age>18,()=>{console.log("when:你可以看了"+dStore.age);})
reaction(()=>[dStore.age,dStore.name],()=>console.log("reaction:age+name="+dStore.age+dStore.name))
dStore.name='abc';
dStore.number=20;
dStore.age=20;
dStore.run()
var runOut=dStore.runOut;
runOut();
runOut();
runInAction('abc',()=>{
    dStore.age=9;
    dStore.name='qqq';
    dStore.number=6;
})

6.1 mobx使用案例

定义一个store

import { makeAutoObservable,action } from "mobx";

class Count {
  constructor() {
    makeAutoObservable(this,{setCount:action.bound});
  }

  count = 0;

  setCount(num) {
    this.count += num;
  }
}
const countStore = new Count();
export default countStore;

编写自定义useStore,然后注入store到context:

// 自定义useStore|useStore.js
import { MobXProviderContext } from "mobx-react";
import { useContext } from "react";

export const useStore = (name) => {
  const store = useContext(MobXProviderContext);
  return store[name];
};
// 将store注入context|App.js
import { Provider } from "mobx-react";
import countStore from "./Count";
import Test from "./Test";

export default function (props) {
  return (
    <Provider countStore={countStore}>
      <Test />
    </Provider>
  );
}

组件内使用

// 组件使用|Test.js
// 1. 使用useObserver
import { inject, observer, Observer, useObserver } from "mobx-react";
import { useStore } from "./useStore";

const Test = (props) => {
  const counStore = useStore("countStore");

  const add = () => {
    counStore.setCount(counStore.count + 1);
  };

  console.log("render");

  // 每次改变可观测对象都会re-render
  return useObserver(() => (
    <div>
      <div>{counStore.count}</div>
      <button onClick={add}>++++</button>
    </div>
  ));
};

export default Test;
//2. 使用Observer标签
import { inject, observer, Observer, useObserver } from "mobx-react";
import { useStore } from "./useStore";

const Test = (props) => {
  const counStore = useStore("countStore");

  const add = () => {
    counStore.setCount(counStore.count + 1);
  };

  console.log("render");

  // 改变可观测对象,不会re-render
  return (
    <div>
      <Observer>{() => <div>{counStore.count}</div>}</Observer>
      <button onClick={add}>++++</button>
    </div>
  );
};

export default Test;
//3. 使用inject/observer函数,
import { inject, observer, Observer, useObserver } from "mobx-react";
import { useStore } from "./useStore";

const Test = ({ countStore }) => {
  const add = () => {
    countStore.setCount(countStore.count + 1);
  };
  console.log("render");
  return (
    <div>
      <div>{counStore.count}</div>
      <button onClick={add}>++++</button>
    </div>
  );
};
// 改变可观测对象,会re-render
export default inject("countStore")(observer(Test));

第一种方式和第三种方式都会导致整个组件重新渲染,使用Observer标签包裹方式不会

useObserver已过期,建议使用<Observer>{()=>jsx}</Observer>代替

7 遇到的问题

1 mobx与react-hook一起使用,store的值改变没有被观测到

场景详细描述:
react 项目中,使用mobx, 通过action 方法修改状态后,值都能打印出来,发生了改变。但是页面没有渲染

原因:

  1. 如果使用react18的hooks,而mobx使用的是mobx4、5等版本,可以仅更新mobx-react版本到6+即可正常观察变化
  2. 上面方法无效的话,可以更新mobx到6,并且需要在构造函数中添加以下两种方式:
    mobx 6版本以前不太兼容react hook,需要更新,更新后6版本移除了装饰器
    方法一:使用makeObservable,但是需要标注一下哪些属性进行转换
    方法二:使用makeAutoObservable,自动转换所有属性,无需装饰器标注,注意:函数默认为@action,字段默认为@observable

特别注意:mobx和mobx-react版本需要6+

makeObservable用法:(可以做更精细控制)

constructor() {
    makeObservable(this, {
      todos: observable,
      pendingRequests: observable,
      completedTodosCount: computed,
      report: computed,
      addTodo: action,
    });
    autorun(() => console.log(this.report));
  }

makeAutoObservable用法:

constructor() {
  makeAutoObservable(this)
}

解决方案:
需要在构造中引入 makeObservable(可以和装饰器混合使用)

import { observable, action, makeObservable } from "mobx";

class CountStore {
  constructor() {
    makeObservable(this});
  }
  @observable a = 1;
  @observable b = 2;

  @action
  add = () => {
    this.a++;
  };
  @action
  add2 = () => {
    this.b++;
  };
}

const countStore = new CountStore();
export default countStore;

引用:https://blog.csdn.net/yangyangkl123/article/details/109506694

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值