mobx使用概览

src/index.js

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import Router from "./router/router";
  4. import { Provider } from "mobx-react";
  5. import stores from "./store";
  6. ReactDOM.render(
  7. <Provider {...stores}>
  8. <Router />
  9. </Provider>,
  10. document.getElementById("root")
  11. );

src/router/router.jsx

  1. import React, { Component, Fragment } from "react";
  2. import { HashRouter,Route } from "react-router-dom";
  3. import Home from '../page/home';
  4. import One from '../page/one';
  5. class Router extends Component {
  6. render() {
  7. return (
  8. <HashRouter>
  9. <Fragment>
  10. <Route exact path={`/`} component={Home} />
  11. <Route path={`/one`} component={One} />
  12. </Fragment>
  13. </HashRouter>
  14. );
  15. }
  16. }
  17. export default Router;

src/store/homeStore.js

  1. import { observable, action, runInAction, useStrict, autorun, computed } from 'mobx';
  2. class HomeStore {
  3. @observable homeNum = 0;
  4. @observable PageTagName = "阿道夫舒服";
  5. @computed get total() {
  6. return `当前页码:${this.homeNum * 10}`;
  7. }
  8. @action setHomeNum() {
  9. this.homeNum += 1;
  10. }
  11. @action setPageTagName(pageName) {
  12. this.PageTagName = pageName;
  13. };
  14. @action setLessNum() {
  15. if (this.homeNum < 1) return;
  16. this.homeNum -= 1;
  17. }
  18. }
  19. export default HomeStore;

src/store/pageTag.js

  1. import { observable, action } from "mobx";
  2. class pageTag {
  3. constructor() {
  4. this.cityName = observable.box("observable.box(),会创建一个对象,接收任何值并把值存储到箱子中")
  5. //输出上面设置的值: observable.box(),会创建一个IObservableValue对象,接收任何值并把值存储到箱子中
  6. console.log(this.cityName.get())
  7. //回调 当 this.cityName.set() 时触发
  8. this.cityName.observe(function ({ oldValue, newValue }) {
  9. console.log(oldValue, "->", newValue, "使用 .set(newValue) 可以更新值。使用它的 .observe 方法注册回调,以监听对存储值的更改");
  10. });
  11. this.cityName.set("Amsterdam")
  12. }
  13. }
  14. export default pageTag;

src/store/index.js

  1. import HomeStore from "./homeStore";
  2. import PageTag from './pageTag'
  3. let homeStore = new HomeStore();
  4. let pageTag = new PageTag();
  5. const stores = {
  6. homeStore,
  7. pageTag
  8. };
  9. /// 默认导出接口
  10. export default stores;

src/page/home.jsx

  1. import React, { Component } from "react";
  2. import { observer, inject } from "mobx-react";
  3. import { observable, action } from 'mobx'
  4. @inject("homeStore")
  5. @inject("pageTag")
  6. @observer
  7. class Home extends Component {
  8. constructor(props) {
  9. super(props);
  10. this.state = { CurrentReactStateAliasName: "这是this.state的数据" };
  11. this.props.homeStore.setPageTagName("首页")
  12. }
  13. @action
  14. handleInputChange = e => {
  15. this.kim = e.target.value;
  16. };
  17. @observable CurrentMoxbAliasName = "这是mobx的数据";
  18. render() {
  19. return (
  20. <div>
  21. <div>this.props.pageTag.cityName.get()获取的值 ==> {this.props.pageTag.cityName.get()}</div>
  22. <h1>{this.props.pageTag.PageTagName}</h1>
  23. <div>
  24. <span style={{ marginRight: 10 }}>{this.state.CurrentReactStateAliasName}</span>
  25. <span>{this.CurrentMoxbAliasName}</span></div>
  26. <input type="text" onChange={(e) => {
  27. this.setState({ CurrentReactStateAliasName: "修改了this.state的数据" + '==>' + e.target.value })
  28. this.CurrentMoxbAliasName = "修改mobx的数据" + '==>' + e.target.value
  29. }} />
  30. <h1>首页数据源的number为:{this.props.homeStore.homeNum}</h1>
  31. {/* <h1>oneStore的number为:{this.props.oneStore.oneNum}</h1> */}
  32. <button
  33. onClick={() => {
  34. this.props.homeStore.setHomeNum();
  35. }}
  36. >
  37. 点击添加
  38. </button>
  39. <button
  40. onClick={() => {
  41. this.props.homeStore.setLessNum();
  42. }}
  43. >
  44. 点击删除
  45. </button>
  46. <button
  47. onClick={() => {
  48. this.props.history.push("/one");
  49. }}
  50. >
  51. 跳转到第一个页面
  52. </button>
  53. <div>{this.props.homeStore.total}</div>
  54. </div>
  55. );
  56. }
  57. }
  58. export default Home;

src/page/one.jsx

  1. import React, { Component } from "react";
  2. import { observer, inject } from "mobx-react";
  3. @inject("homeStore")
  4. @inject("pageTag")
  5. @observer
  6. class One extends Component {
  7. constructor(props) {
  8. super(props);
  9. this.state = {};
  10. this.props.homeStore.setPageTagName("子页面")
  11. }
  12. render() {
  13. return (
  14. <div>
  15. <h1>{this.props.pageTag.PageTagName}</h1>
  16. <h1>首页数据源的number为:{this.props.homeStore.homeNum}</h1>
  17. {/* <h1>oneStore的number为:{this.props.oneStore.oneNum}</h1> */}
  18. <button onClick={() => { this.props.history.push("/"); }}>
  19. 跳转到首页
  20. </button>
  21. <button onClick={()=>{
  22. this.props.pageTag.cityName.set("调用了this.props.pageTag.cityName.set()")
  23. }}>改变home的值</button>
  24. </div>
  25. );
  26. }
  27. }
  28. export default One

mobx 全局状态管理简单案例.zip

Mobx 思想的实现原理

Mobx 最关键的函数在于 autoRun,举个例子,它可以达到这样的效果:

  1. import { observable, autoRun } from 'mobx';
  2. var appState = observable({
  3. timer: 0,
  4. name: "一缕清风"
  5. });
  6. autoRun(() => {
  7. console.log(appState.name)
  8. })
  9. appState.timer = Date.now() // 什么都没有发生
  10. appState.name = "Ken" // observable 函数的回调触发了,控制台输出:Ken

我们发现这个函数非常智能,用到了什么属性,就会和这个属性挂上钩,从此一旦这个属性发生了改变,就会触发回调,通知你可以拿到新值了。没有用到的属性,无论你怎么修改,它都不会触发回调,这就是神奇的地方。
autoRun 的用途 使用 autoRun 实现 mobx-react 非常简单,核心思想是将组件外面包上 autoRun,这样代码中用到的所有属性都会像上面 Demo 一样,与当前组件绑定,一旦任何值发生了修改,就直接 forceUpdate,而且精确命中,效率最高。

依赖收集

autoRun 的专业名词叫做依赖收集,也就是通过自然的使用,来收集依赖,当变量改变时,根据收集的依赖来判断是否需要更新。

实现步骤拆解

为了兼容,Mobx 使用了 Object.defineProperty 拦截 getter 和 setter,但是无法拦截未定义的变量,为了方便,我们使用 proxy 来讲解,而且可以监听未定义的变量哦。

步骤一 存储结构

众所周知,事件监听是需要预先存储的,autoRun 也一样,为了知道当变量修改后,哪些方法应该被触发,我们需要一个存储结构。
首先,我们需要存储所有的代理对象,让我们无论拿到原始对象,还是代理对象,都能快速的找出是否有对应的代理对象存在,这个功能用在判断代理是否存在,是否合法,以及同一个对象不会生成两个代理。
代码如下:

  1. const proxies = new WeakMap()
  2. function isObservable<T extends object>(obj: T) {
  3. return (proxies.get(obj) === obj)
  4. }

重点来了,第二个要存储的是最重要的部分,也就是所有监听!当任何对象被改变的时候,我们需要知道它每一个 key 对应着哪些监听(这些监听由 autoRun 注册),也就是,最终会存在多个对象,每个对象的每个 key 都可能与多个 autoRun 绑定,这样在更新某个 key 时,直接触发与其绑定的所有 autoRun 即可。
代码如下:

  1. const observers = new WeakMap<object, Map<PropertyKey, Set<Observer>>>()

第三个存储结构就是待观察队列,为了使同一个调用栈多次赋值仅执行一次 autoRun,所有待执行的都会放在这个队列中,在下一时刻统一执行队列并清空,执行的时候,当前所有 autoRun 都是在同一时刻触发的,所以让相同的 autoRun 不用触发多次即可实现性能优化。

  1. const queuedObservers = new Set()

我们还要再存储两个全局变量,分别是是否在队列执行中,以及当前执行到的 autoRun。
代码如下:

  1. let queued = false
  2. let currentObserver: Observer = null

步骤二 将对象加工可观察

这一步讲解的是 observable 做了哪些事,首先第一件就是,如果已经存在代理对象了,就直接返回。

  1. function observable<T extends object>(obj: T = {} as T): T {
  2. return proxies.get(obj) || toObservable(obj)
  3. }

我们继续看 toObservable 函数,它做的事情是,实例化代理,并拦截 get set 等方法。
我们先看拦截 get 的作用:先拿到当前要获取的值 result,如果这个值在代理中存在,优先返回代理对象,否则返回 result 本身(没有引用关系的基本类型)。
上面的逻辑只是简单返回取值,并没有注册这一步,我们在 currentObserver 存在时才会给对象当前 key 注册 autoRun,并且如果结果是对象,又不存在已有的代理,就调用自身 toObservable 再递归一遍,所以返回的对象一定是代理。
registerObserver 函数的作用是将 targetObj -> key -> autoRun 这个链路关系存到 observers 对象中,当对象修改的时候,可以直接找到对应 key 的 autoRun。
那么 currentObserver 是什么时候赋值的呢?首先,并不是访问到 get 就要注册 registerObserver,必须在 autoRun 里面的才符合要求,所以执行 autoRun 的时候就会将当前回调函数赋值给 currentObserver,保证了在 autoRun 函数内部所有监听对象的 get 拦截器都能访问到 currentObserver。以此类推,其他 autoRun 函数回调函数内部变量 get 拦截器中,currentObserver 也是对应的回调函数。

  1. const dynamicObject = new Proxy(obj, {
  2. // ...
  3. get(target, key, receiver) {
  4. const result = Reflect.get(target, key, receiver)
  5. // 如果取的值是对象,优先取代理对象
  6. const resultIsObject = typeof result === 'object' && result
  7. const existProxy = resultIsObject && proxies.get(result)
  8. // 将监听添加到这个 key 上
  9. if (currentObserver) {
  10. registerObserver(target, key)
  11. if (resultIsObject) {
  12. return existProxy || toObservable(result)
  13. }
  14. }
  15. return existProxy || result
  16. }),
  17. // ...
  18. })

setter 过程中,如果对象产生了变动,就会触发 queueObservers 函数执行回调函数,这些回调都在 getter 中定义好了,只需要把当前对象,以及修改的 key 传过去,直接触发对应对象,当前 key 所注册的 autoRun 即可。

  1. const dynamicObject = new Proxy(obj, {
  2. // ...
  3. set(target, key, value, receiver) {
  4. // 如果改动了 length 属性,或者新值与旧值不同,触发可观察队列任务
  5. if (key === 'length' || value !== Reflect.get(target, key, receiver)) {
  6. queueObservers<T>(target, key)
  7. }
  8. // 如果新值是对象,优先取原始对象
  9. if (typeof value === 'object' && value) {
  10. value = value.$raw || value
  11. }
  12. return Reflect.set(target, key, value, receiver)
  13. },
  14. // ...
  15. })

没错,主要逻辑已经全部说完了,新对象之所以可以检测到,是因为 proxy 的 get 会触发,这要多谢 proxy 的强大。
可能有人问 Object.defineProperty 为什么不行?
原因很简单,因为这个函数只能设置某个 key 的 getter setter
symbol proxyre flect 这三剑客能做的事还有很多很多,这仅仅是实现 Object.observe 而已,还有更强大的功能可以挖掘。

React-mobx的实现原理

mobx是基本概念

  1. Observable //被观察者
  2. Observer //观察者
  3. Reaction //响应

在被观察者和观察者之间建立依赖关系

通过一个Reaction来track一个函数,该函数中访问了Observable变量,Observable变量的get方法会被执行,此时可以进行依赖收集,将此函数加入到该Observable变量的依赖中。类似于:

  1. //伪代码
  2. reaction.track(function() {
  3. mobx.beginCollect(); //开始收集
  4. handler(); //被观察函数
  5. mobx.endCollect(); //结束收集
  6. })

触发依赖函数

上一步中Observable中,已经收集到了该函数。一旦Observable被修改,会调用set方法,就是依次执行该Observable之前收集的依赖函数,当前函数就会自动执行。
mobx底层对数据的观察,是使用 Object.defineProperty(Mobx4) 或 Proxy(Mobx5),Mobx4中Array是用类数组对象来模拟的,原始值是装箱为一个对象。

mobx-react如何更新react状态

  1. import React, { Component} from 'react';
  2. import { observer } from 'mobx-react';
  3. @observer
  4. export default MyCom extends Component {
  5. render() {
  6. // ...do something
  7. }
  8. }

observer这个装饰器,对React组件的render方法进行track。将render方法,加入到各个observable的依赖中。当observable发生变化,track方法就会执行。
track中,还是先进行依赖收集,调用 forceUpdate 去更新组件,一个mobx添加的周期componentWillReact,也会在此时执行。然后结束依赖收集。
每次都进行依赖收集的原因是,每次执行依赖可能会发生变化。