mobx探究(三)

365 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

主要讲mobx的源码实现,粗略认识一下observable、autorun、reaction等api的实现,

源码实现

mobx 任何可以从应用状态中派生出来的值都应该呗自动派生出来

通过运用透明的函数式响应编程使状态管理变得简单和可扩展

安装依赖

pnpm create vite

mobx mobx-react

装饰器

mobx 在版本6以前鼓励使用装饰器,但是装饰器还是没入es标准

使用在mobx6 放弃了他们,改成函数式

配置

pnpm install @babel/core @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
# vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
  plugins: [react({
    babel: {
      plugins: [
          // 装饰器支持
        ["@babel/plugin-proposal-decorators", { legacy: true }],
        ["@babel/plugin-proposal-class-properties", { loose: true }],
      ],
    },
  })]
})

架构

# 使用时,有点像vue3的effect和reactive
import {observable} from 'mobx'
let o = observable({a:'b'}) // 观察对象
autoRun(()=>{console.log(1)}) // 观察对象触发更改时执行
o.a=c //改动观察对象
# .mobx/index.js
export { default as observable } from './observable';
# .mobx/observable.js
import { isObject } from './utils';
import { object } from './observableobject';
function observable(v) {
    // 如果是对象,就对其变化更改成可观察的对象,具体看下文
  if (isObject(v)) {
    return object(v);
  }
}
export default observable;
# mobx/utils
export function isObject(value) {
  return value !== null && typeof value === 'object';
}

observable

简单来说

1 创建一个空的对象,其$mobx 属性 指向 ObservableObjectAdministration构造的新对象a

2 代理空对象,指向 a

3 将 观察对象 里的 属性依次遍历 赋予 avalues属性上,values赋值和设置的 是 new ObservableObjectAdministration。做代理,当访问 a的属性时,会指向values

image.png image.png

# utils
export function getAdm(target) {
  return target[$mobx];
}
// 这种引入,cjs是做不到的,只有mjs可以
let mobxGuid = 0;
export function getNextId() {
  return ++mobxGuid;
}
export function addHiddenProp(obj, propName, value) {
  Object.defineProperty(obj, propName, {
    enumerable: false,
    writable: true,
    configurable: false,
    value
  });
}
# observableobject.js
import { getNextId, addHiddenProp, $mobx, getAdm, globalState } from "./utils";
class ObservableValue {
  constructor(value) {
    this.value = value;

  }
  get() {

    return this.value;
  }
  setNewValue(newValue) {
    this.value = newValue;

  }
}

class ObservableObjectAdministration {
  constructor(target, values, name) {
    this.target = target;
    this.values = values; //存放属性的信息
    this.name = name;
  }
  get(key) {
    return this.target[key];
  }
  set(key, value) {
    return this.target[key]=value
  }
  extend(key, descriptor) {
    // 给values赋值target的属性们
    this.defineObservableProperty(key, descriptor.value); //name,1
  }
  setObservablePropValue(key, value) {
    // 给key对应的ObservableValue设值
    const observableValue = this.values.get(key);
    observableValue.setNewValue(value);
    return true;
  }
  getObservablePropValue(key) {
    // 获取key对应的ObservableValue里的值
    return this.values.get(key).get();
  }
  defineObservableProperty(key, value) {
    const descriptor = {
      configurable: true,
      enumerable: true,
      get() {
        return this[$mobx].getObservablePropValue(key);
      },
      set() {
        return this[$mobx].setObservablePropValue(key, value);
      },
    };
    // 给对象做代理,访问key属性时,执行descriptor的get、set方法
    Object.defineProperty(this.target, key, descriptor);
    // 将属性的信息存放到values中,values的每个key对应一个new ObservableValue(value)
    // 蛮繁琐的
    this.values.set(key, new ObservableValue(value));
  }
}

function asObservableObject(target) {
    // 空对象.$mobx = new ObservableObjectAdministration()
  const name = `ObservableObject@${getNextId()}`;
  const adm = new ObservableObjectAdministration(target, new Map(), name);
  addHiddenProp(target, $mobx, adm); //target[$mobx]=adm
  return target;
}
const objectProxyTraps = {
  // 就是访问 空对象.$mobx
  get(target, name) {
    return getAdm(target).get(name);
  },
  set(target, name, value) {
    return getAdm(target).set(name, value);
  },
};
function asDynamicObservableObject(target) {
  // 空对象.$mobx = new ObservableObjectAdministration()
  asObservableObject(target);
  // 代理 访问空对象时 会访问 ObservableObjectAdministration
  const proxy = new Proxy(target, objectProxyTraps);
  return proxy;
}
function extendObservable(proxyObject, properties) {
  // properties {name:1,age:2}
  // descriptors {name:{value:1,writable:true},age:{value:2,writable:true}}
  const descriptors = Object.getOwnPropertyDescriptors(properties);
  // adm 就是空对象.$mobx
  const adm = proxyObject[$mobx];
  // [name,age] 我感觉还不如Object.entries()
  Reflect.ownKeys(descriptors).forEach((key) => {
    adm.extend(key, descriptors[key]);
  });
  return proxyObject;
}
export function object(target) {
  // 建立空的代理对象
  const dynamicObservableObject = asDynamicObservableObject({});
  // 将对象的属性值赋值给空的代理对象
  return extendObservable(dynamicObservableObject, target);
}

为什么要建空代理对象??因为是观察对象要求深度的对象也是代理对象,防止死循环

antorun

简单来说,类似vue3的effect

1 构造 Reaction,将全局的globalState.trackingDerivation赋值 Reaction

2 执行 view函数,在其中的访问到代理对象属性时,利用代理拦截,在对象属性那里的ObservableValue 拿到 全局的Reaction,也就是当前执行的view函数,进行绑定,Reaction.observing.push当前属性,事件绑定到当前的对象属性; Reaction.observing里的属性再挂上当前的Reaction,对象属性绑定到当前的 Reaction,成功双向绑定

3 当修改 代理对象属性值时,触发set,去执行属性绑定的事件们即可

image.png

# antorun.js
import { getNextId } from "./utils";
import Reaction from "./reaction";
// view 视图 函数 可能渲染组件
function autorun(view) {
  // 标识
  const name = "Autorun@" + getNextId();
  // 构造 Reaction,类似vue3构造effect
  const reaction = new Reaction(name, function () {
    this.track(view);
  });
  // 将view事件放在全局,并执行view函数
  reaction.schedule();
}
export default autorun;
# utils
export const globalState = {
  pendingReactions: [],
  trackingDerivation: null
}
# reaction.js
import { globalState } from "./utils";
export default class Reaction {
  constructor(name, onInvalidate) {
    this.name = name;
    this.onInvalidate = onInvalidate;
    this.observing = []; //表示它观察到了哪些可观察变量
  }
  // 类似vue3,执行前把事件先放全局,然后执行时利用代理将事件和对象属性其挂上联系
  track(fn) {
    //Derivation=reaction
    globalState.trackingDerivation = this;
    fn.call();
    globalState.trackingDerivation = null;
    bindDependencies(this);
  }
  // 将view事件放在全局的pendingReactions里,并执行view函数
  schedule() {
    globalState.pendingReactions.push(this);
    runReactions();
  }
  // 执行view函数
  runReaction() {
    this.onInvalidate();
  }
}
// 对象属性值 ObservableValue 绑定上当前的reaction事件,这就是双向绑定
function bindDependencies(derivation) {
  const { observing } = derivation;
  observing.forEach((observableValue) => {
    observableValue.observers.add(derivation);
  });
}
// 依次执行pendingReactions里的view函数
function runReactions() {
  const allReactions = globalState.pendingReactions;
  let reaction;
  while ((reaction = allReactions.shift())) {
    reaction.runReaction();
  }
}
# observableobject.jsx
class ObservableValue {
  constructor(value) {
    this.value = value;
    this.observers = new Set(); //此可观察值的监听者,可以说观察者
  }
  get() {
    // 获取对象属性值,将这个value绑定到事件里
    // globalState.trackingDerivation(事件).observing.push(observableValue)
    reportObserved(this);
    return this.value;
  }
  setNewValue(newValue) {
    this.value = newValue;
    // 类似vue3的trigger
    propagateChanged(this);
  }
}
// 类似vue3的trigger,执行该value对应的view事件们
function propagateChanged(observableValue) {
  const { observers } = observableValue;
  observers.forEach((observer) => {
    observer.runReaction();
  });
}
// 获取对象属性值,将这个value绑定到事件里
function reportObserved(observableValue) {
  const trackingDerivation = globalState.trackingDerivation;
  if (trackingDerivation) {
    trackingDerivation.observing.push(observableValue);
  }
}
class ObservableObjectAdministration {
...
  set(key, value) {
    if (this.values.has(key)) {
      return this.setObservablePropValue(key, value);
    }
  }
  ...

mobx-react

useObserver

函数组件

# 使用
import React from 'react';
import { makeAutoObservable } from 'mobx';
import { useObserver, Observer, observer, useLocalObservable } from './mobx-react';
class Store{
    number: 1,
    constructor(){
        makeAutoObservable(this,{},{autoBind:true})
    }
    add() {
        this.number++;
      }
} 
let store = new Store()
export default function () {
  return useObserver(() => (
    <div>
      <p>{store.number}</p>
      <button onClick={store.add}>+</button>
    </div>
  ));
}
# 源码
export function useObserver(fn) {
  //仅仅是为了得到一个强行更新组件的函数
  const [, setState] = useState({});
  const forceUpdate = () => setState({});
  // view函数 forceUpdate 强制更新子组件
  let reaction = new Reaction("observer", forceUpdate);
  let rendering;
  reaction.track(() => {
    // 类似effect,这里执行完后双向绑定,当改动数据时,触发view函数执行,更新组件
    rendering = fn();
  });
  return rendering;
}

Observer

组件

# 使用
<Observer>(()=>
    <div>
      <p>{store.number}</p>
      <button onClick={store.add}>+</button>
    </div>
</Observer>))
## 源码
export function Observer({ children }) {
  return useObserver(children);
}

observer

高阶组件

# 使用
export default observer(function () {
  return (
    <div>
      <p>{store.number}</p>
      <button onClick={store.add}>+</button>
    </div>
  )
})
# 源码
export function observer(oldComponent) {
  let observerComponent = (props) => {
    return useObserver(() => oldComponent(props));
  };
  return observerComponent;
}

类和装饰器的支持

# 使用
@observer
class Counter extends React.Component {
  render() {
    return (
      <div>
        <p>{store.number}</p>
        <button onClick={store.add}>+</button>
      </div>
    )
  }
}
# 源码
export function observer(oldComponent) {
//判断是否是类
  if (oldComponent.prototype && oldComponent.prototype.isReactComponent) {
    return makeClassComponentObserver(oldComponent);
  }
  let observerComponent = (props) => {
    return useObserver(() => oldComponent(props));
  };
  return observerComponent;
}
function makeClassComponentObserver(ClassComponent) {
  // 取得类的渲染
  const prototype = ClassComponent.prototype;
  const originalRender = prototype.render;
  prototype.render = function () {
    const boundOriginalRender = originalRender.bind(this);
    // view 重渲染
    const reaction = new Reaction("render", () =>
      React.Component.prototype.forceUpdate.call(this)
    );
    let rendering;
    reaction.track(() => {
      // reaction 绑定上渲染函数
      rendering = boundOriginalRender();
    });
    return rendering;
  };
  return ClassComponent;
}

useLocalObservable

# 使用
export default function () {
  const store = useLocalObservable(() => (
    {
      number: 1,
      add() {
        this.number++;
      }
    }
  ));
  return useObserver(() => (
    <div>
      <p>{store.number}</p>
      <button onClick={store.add}>+</button>
    </div>
  ));
}
# 源码
export function useLocalObservable(initializer) {
// 只执行一次 initializer(),得到对象,变成可观察对象,返回
  return useState(() => observable(initializer(), {}, { autoBind: true }))[0];
}