在线解析mobx实现原理

·  阅读 982
 在线解析mobx实现原理

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前言

使用了mobx库很长时间,一直想探寻一下内部更新的原理正好最近有时间阅读了mobx部分api的源码,今天来分享一下内部的原理看看是怎么做到和react视图连接的,是怎么做到更新之后能响应到视图上的

开始

今天主要会实现一下mobxmobx-react中的一些核心api

1、observable来自于mobx中,其中主要原理是使用Object.defineProperty,这一点和Vue2的响应式系统是很相似

2、observer来自于mobx-react中,在mobx-react-lite中也有observer但是只适用于hooks组件,而mobx-react-lite也是基于mobx-react,所以今天只实现mobx-react中的observer因为,这个在class类和hooks中是通用的

准备工作

1.需要使用creat-react-app生成一个脚手架(这里百度就可以),进行我们的案例实验

2.创建一个List.jsx,用于展示我们的结果页面很简单,最后做到可以初始化响应式变量,并且可以顺利更新

// import { observer } from 'mobx-react'
import Store from './Store/store'
import { useObserver, observerLite, observer } from './lhc-mobx-react'

const List = observer(() => {
    return <>
        点赞量:{Store.count}
        <button onClick={Store.setCount}>更新点赞量</button>
    </>
})

export default List

复制代码

如果你不想自己试一下那我可以给你截个图看看页面效果

3.创建一个Store.js,用来存储数据

// import { observable, action, configure } from 'mobx'
import { observable } from '../lhc-mobx'

const state = observable({
    count: 1
})

state.setCount = action(() => {
    state.count++
})

export default state
复制代码

上代码

mobx-react和react视图连接只要有三种方式,useObserver、observer、Observer

1.useObserver

// import { observer } from 'mobx-react'
import Store from './Store/store'
import { useObserver, observer } from './lhc-mobx-react'

const List = () => {
    return useObserver(()=><>
    点赞量:{Store.count}
    <button onClick={Store.setCount}>更新点赞量</button>
</>)
}

export default List
复制代码

2.observer

// import { observer } from 'mobx-react'
import Store from './Store/store'
import { useObserver, observer } from './lhc-mobx-react'

const List = observer(() => {
    return <>
        点赞量:{Store.count}
        <button onClick={Store.setCount}>更新点赞量</button>
    </>
})

export default List
复制代码

3.Observer使用组件的方式进行包装

const List = () => {
    return <Observer>
       {()=> <>
        点赞量:{Store.count}
        <button onClick={Store.setCount}>更新点赞量</button></>}
    </Observer>
}

export default List
复制代码

提示:接下来我们依次实现这三种方式,因为已经是写完验证过的了,所以在实现完成的过程中不会在进行校验,并且只是实现基础更新初始化功能(毕竟源码这么多😂),大部分注释都会写在代码中,如果哪里有问题可以在评论区讨论

新建一个lhc-mobx-react.js文件

useObserver实现

import React, { useRef, useReducer, useEffect, memo, forwardRef, Component } from 'react'
import { Reaction } from 'mobx'

function observerComponentNameFor(component) {
    return `&observer${component}`
}

/**
 * @fn 执行的回调
 * @baseComponentName 组件标识
 * @options 其他操作
*/
function useObserver(fn, baseComponentName = 'observed', options = {}) {

    //创建强制更新api
    const [, forceUpdate] = useReducer(x => x + 1, 0)

    //创建ref存储reaction
    const reactionTrackRef = useRef(null)

    if (!reactionTrackRef.current) {
        reactionTrackRef.current = {
            reaction: new Reaction(
                observerComponentNameFor(baseComponentName),
                () => {
                    // 监测到数据更新的时候会执行
                    forceUpdate()
                }
            )
        }
    }

    const { reaction } = reactionTrackRef.current

    let rendering = null

    reaction.track(() => {
        //监听更新重新执行传进来的方法
        rendering = fn()
    })

    useEffect(() => {
        return () => {
            //组件执行完毕进行销毁
            reaction.dispose()
            reactionTrackRef.current = null
        }
    }, [])

    return rendering
}

export {
    useObserver
}
复制代码

observer实现用到了几个方法

observer

function observer(component, options) {
    // 处理forwardRef
    if (component["$$typeof"] === Symbol.for("react.forward_ref")) {
        const baseRender = component["render"];
        return React.forwardRef(function () {
            const args = arguments;
            //这里使用的Observer就是上面所说的第三种实现方式在下面会展示
            return <Observer>{() => baseRender.apply(undefined, args)}</Observer>;
        });
    }

    if (
        // 处理函数组件逻辑
        (typeof component === "function" && !component.prototype) ||
        !component.prototype.render
    ) {
        // observerLite是mobx-react-lite中的observer,怕命名冲突改成了observerLite
        return observerLite(component, options);
    }
    // 处理类组件
    return makeClassComponentObserver(component);
}

export {
    useObserver,
    observer
}
复制代码

observerLite

function observerLite(baseComponent, options = {}) {
    let realOptions = {
        forwardRef: false,
        ...options
    };
    const useWrappedComponent = (props, ref) => {
        // 将baseComponent和数据连接
        return useObserver(() => baseComponent(props, ref));
    };
    let memoComponent;
    if (realOptions.forwardRef) {
        //处理forwardRef
        memoComponent = memo(forwardRef(useWrappedComponent));
    } else {
        //mobx-react-lite内部的observer默认有React.memo行为,在这里加上
        memoComponent = memo(useWrappedComponent);
    }
    return memoComponent;
}
复制代码

makeClassComponentObserver

function makeClassComponentObserver(componentClass) {
    const target = componentClass.prototype;

    //获取class类的虚拟dom
    const baseRender = target.render;
    target.render = function () {
        //处理class类中的render
        return makeComponentReactive.call(this, baseRender);
    };
    return componentClass;
}
复制代码

makeComponentReactive

function makeComponentReactive(render) {
    const baseRender = render.bind(this);
    let isRenderingPending = false;
    //监听render
    const reaction = new Reaction(`${this.constructor.name}.render`, () => {
        if (!isRenderingPending) {
            isRenderingPending = true;
            //监测到数据变化强制更新视图
            Component.prototype.forceUpdate.call(this);
        }
    });

    this.render = reactiveRender;

    function reactiveRender() {
        isRenderingPending = false;
        let rendering = undefined;
        //更新render
        reaction.track(() => {
            rendering = baseRender();
        });

        return rendering;
    }

    return reactiveRender.call(this);
}

export {
    useObserver,
    observer,
    Observer
}
复制代码

Observer实现

function Observer({children, render}) {
    const component = children || render;
    return useObserver(component);
}
复制代码

mobx中的observable创建一个响应式数据、action用来更新数据,使用过程可以用@装饰器的方法,也可以直接使用函数式的方法现在来实现一下

先看几个工具函数,之后在编写observable和action过程中会用到

创建utils.js

const plainObjectString = Object.toString()
const objectPrototype = Object.defineProperty

export const isObject = (value) => {
    return value !== null && typeof value === 'object'
}

export const isPlaneObject = (value) => {
    if (!isObject(value)) return false
    const proto = Object.getPrototypeOf(value)
    if (proto === null) return true
    return proto.constructor.toString() === plainObjectString
}

export const hasProp = (target, prop) => {
    return objectPrototype.hasOwnProperty.call(target, prop)
}

export const $mobx = Symbol('mobx administration')

export const addHiddenProp = (object, propName, value) => {
    Object.defineProperty(object, propName, {
        value,
        enumerable: false,
        writable: true,
        configurable: true,
    })
}

export const getDescriptor = Object.getOwnPropertyDescriptor

复制代码

observable

import { isPlaneObject, hasProp, $mobx, addHiddenProp } from './utils'

const createObservable = (v) => {
    //判断是否是对象的方式
    if (isPlaneObject(v)) {
        return observable.object(v)
    }
}


const observableFactories = {
    object: props => {
        return extendObservable({}, props)
    }
}
 
export const observable = Object.assign(createObservable, observableFactories)
复制代码

extendObservable

//添加响应式对象
const extendObservable = (target, props) => {
    //将target变为可观察模式 在返回
    const adm = asObservableObject(target)

    //遍历所有属性变为响应式
    Object.keys(props).forEach(key => {
        adm._addObservableProp(key, props[key])
    })

    return target
}

复制代码

asObservableObject

const asObservableObject = (target) => {

    if (hasProp(target, $mobx)) {
        //如果已经添加过这个属性直接返回
        return target[$mobx]
    }

    const adm = new ObserverableObjectAdministration(target)

    //调用工具函数变为响应式
    addHiddenProp(target, $mobx, adm)

    return adm
}
复制代码

ObserverableObjectAdministration

 class ObserverableObjectAdministration {
    constructor(_target) {
        this._target = _target
    }

    //添加属性
    _addObservableProp(propName, newValue) {
        this._target[propName] = newValue
    }
}
复制代码

到这里核心源码就已经实现的差不多了,之后会在更新文章继续实现action的原理,最后提前祝大家国庆节快乐☕️

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改