携手创作,共同成长!这是我参与「掘金日新计划 · 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构造的新对象a2 代理空对象,指向
a3 将 观察对象 里的 属性依次遍历 赋予
a的values属性上,values赋值和设置的 是new ObservableObjectAdministration。做代理,当访问a的属性时,会指向values
# 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的effect1 构造
Reaction,将全局的globalState.trackingDerivation赋值Reaction2 执行
view函数,在其中的访问到代理对象属性时,利用代理拦截,在对象属性那里的ObservableValue拿到 全局的Reaction,也就是当前执行的view函数,进行绑定,Reaction.observing.push当前属性,事件绑定到当前的对象属性;Reaction.observing里的属性再挂上当前的Reaction,对象属性绑定到当前的Reaction,成功双向绑定3 当修改 代理对象属性值时,触发
set,去执行属性绑定的事件们即可
# 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];
}