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 ☕️