This is the fourth day of my participation in the August Text Challenge.More challenges in August”

preface

I’ve been using the Mobx library for a long time. I’ve had time to read some of the mobx API source code recently. Today, I’m going to share some of the internal principles to see how to connect to the React view

start

Today we will implement some of the core apis in MOBx and Mobx-React

1. Observable comes from MOBx. The main principle is to use Object.defineProperty, which is very similar to Vue2’s responsive system

The observer comes from mobx-React. The observer comes from mobx-react lite, which only applies to hooks. Mobx-react lite is also based on mobx-React. So today we only implement the Observer in Mobx-React because this is universal in class and hooks

The preparatory work

1. Creat-react-app is needed to generate a scaffold (Baidu can do this) for our case experiment

2. Creating a list.jsx to display our results page is simple, and the last thing we can do is initialize reactive variables and update them smoothly

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

const List = observer(() = > {
    return <>Likes: {store.count}<button onClick={Store.setCount}>Update the likes</button>
    </>
})

export default List

Copy the code

If you don’t want to try it out for yourself I can take a screenshot and see what it looks like

3. Create a store. js file to Store data

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

const state = observable({
    count: 1
})

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

export default state
Copy the code

In the code

There are only three ways to connect the mobx-React and react views: useObserver, Observer, and observer

1.useObserver

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

const List = () = > {
    return useObserver(() = ><>Likes: {store.count}<button onClick={Store.setCount}>Update the likes</button>
</>)}export default List
Copy the code

2.observer

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

const List = observer(() = > {
    return <>Likes: {store.count}<button onClick={Store.setCount}>Update the likes</button>
    </>
})

export default List
Copy the code

3. The Observer is wrapped as a component

const List = () = > {
    return <Observer>{() = ><>Likes: {store.count}<button onClick={Store.setCount}>Update the likes</button></>}
    </Observer>
}

export default List
Copy the code

Tip: here we are, in turn, to realize these three ways, because it was already finished the verified, so in the process of realization of complete not in check, and just realize basic update initialization function (after all the source code so much 😂), most of the comments will be written in the code, if you have any question can be discussed in the comments section where

Create a new lHC-mox-react. Js file

UseObserver implementation

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

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

/ * * *@fn Callback * executed@baseComponentName Component identification@options Other operations */
function useObserver(fn, baseComponentName = 'observed', options = {}) {

    // Create a force-update API
    const [, forceUpdate] = useReducer(x= > x + 1.0)

    // Create ref to store reaction
    const reactionTrackRef = useRef(null)

    if(! reactionTrackRef.current) { reactionTrackRef.current = {reaction: new Reaction(
                observerComponentNameFor(baseComponentName),
                () = > {
                    // It will be executed when data updates are detected
                    forceUpdate()
                }
            )
        }
    }

    const { reaction } = reactionTrackRef.current

    let rendering = null

    reaction.track(() = > {
        // Listen for updates to re-execute the passed method
        rendering = fn()
    })

    useEffect(() = > {
        return () = > {
            // The component is destroyed after execution
            reaction.dispose()
            reactionTrackRef.current = null}}, [])return rendering
}

export {
    useObserver
}
Copy the code

The Observer implementation uses several approaches

observer

function observer(component, options) {
    / / forwardRef processing
    if (component["$$typeof"= = =Symbol.for("react.forward_ref")) {
        const baseRender = component["render"];
        return React.forwardRef(function () {
            const args = arguments;
            // The Observer used here is the third implementation described above, shown below
            return <Observer>{() => baseRender.apply(undefined, args)}</Observer>;
        });
    }

    if (
        // Handle the function component logic
        (typeof component === "function"&&! component.prototype) || ! component.prototype.render ) {// observerLite is an observer of Mobx-react-Lite
        return observerLite(component, options);
    }
    // Process the class component
    return makeClassComponentObserver(component);
}

export {
    useObserver,
    observer
}
Copy the code

observerLite

function observerLite(baseComponent, options = {}) {
    let realOptions = {
        forwardRef: false. options };const useWrappedComponent = (props, ref) = > {
        // Connect baseComponent to data
        return useObserver(() = > baseComponent(props, ref));
    };
    let memoComponent;
    if (realOptions.forwardRef) {
        / / forwardRef processing
        memoComponent = memo(forwardRef(useWrappedComponent));
    } else {
        // The internal observer of mobx-react-Lite has the react. memo behavior by default
        memoComponent = memo(useWrappedComponent);
    }
    return memoComponent;
}
Copy the code

makeClassComponentObserver

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

    // Get the virtual DOM of the class
    const baseRender = target.render;
    target.render = function () {
        // Handle render in the class
        return makeComponentReactive.call(this, baseRender);
    };
    return componentClass;
}
Copy the code

makeComponentReactive

function makeComponentReactive(render) {
    const baseRender = render.bind(this);
    let isRenderingPending = false;
    / / listen to render
    const reaction = new Reaction(`The ${this.constructor.name}.render`.() = > {
        if(! isRenderingPending) { isRenderingPending =true;
            // Force a view update when data changes are detected
            Component.prototype.forceUpdate.call(this); }});this.render = reactiveRender;

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

        return rendering;
    }

    return reactiveRender.call(this);
}

export {
    useObserver,
    observer,
    Observer
}
Copy the code

The Observer to realize the

function Observer({children, render}) {
    const component = children || render;
    return useObserver(component);
}
Copy the code

An Observable in Mobx creates a responsive data action that updates the data using either the @ decorator method or the functional method

Let’s take a look at a few utility functions that we’ll use when writing Observables and actions

Create utils. Js

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

export const isObject = (value) = > {
    returnvalue ! = =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

Copy the code

observable

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

const createObservable = (v) = > {
    // How to determine if it is an object
    if (isPlaneObject(v)) {
        return observable.object(v)
    }
}


const observableFactories = {
    object: props= > {
        return extendObservable({}, props)
    }
}
 
export const observable = Object.assign(createObservable, observableFactories)
Copy the code

extendObservable

// Add responsive objects
const extendObservable = (target, props) = > {
    // Change target to observable mode on return
    const adm = asObservableObject(target)

    // Iterate over all properties to become responsive
    Object.keys(props).forEach(key= > {
        adm._addObservableProp(key, props[key])
    })

    return target
}

Copy the code

asObservableObject

const asObservableObject = (target) = > {

    if (hasProp(target, $mobx)) {
        // Return if this attribute has been added
        return target[$mobx]
    }

    const adm = new ObserverableObjectAdministration(target)

    // Calling the utility function becomes responsive
    addHiddenProp(target, $mobx, adm)

    return adm
}
Copy the code

ObserverableObjectAdministration

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

    // Add attributes
    _addObservableProp(propName, newValue) {
        this._target[propName] = newValue
    }
}
Copy the code

To here the core source code has been almost realized, will continue to implement the principle of action in the updated article, and finally I wish you a happy National Day in advance ☕️