mobx 源码学习三

react-mobx 基础学习:observer 和inject

本文是 [mobx 源码初步认识] 第三篇
本文讲解react-mobx连接mobx的方式
该文章采用 react-mobx 较新版本:[v7.1.0]

技术前提

在阅读之前,希望你对以下技术有所了解,不然可能会影响你对本文的理解

1,ES6 装饰器:decorator

2,react组件和props

3,react-context的使用(可选)

4,react-forwardRef的使用(可选)

准备

一 目录结构

├── src
│   ├── types             // react有关类型包括组件,参数,高级组件等
│   ├── utils             // 工具方法
│   ├── disposeOnUnmount  // 重写关于 object,array 等类型的 api
│   ├── index.ts          // 导出封装的方法
│   ├── inject.ts         // 解析store传入到observer
│   |── observer.tsx      // 创建可观察的react组件
|   |── observerClass.ts  // 可观察react组件创建者
|   |—— Provider          // 一种hook的实现方法
└── package.json

二 inject

用于根据storename获取对应store,返回一个方法接收react组件,并且将获取到的store合并到组件的props上

1,inject Api,返回值是一个封装的闭包方法

inject 包括两种传参方式,如果传入function,后续inject中自动会调用oberser

export function inject(...storeNames: Array<any>) {
    if (typeof arguments[0] === "function") {
        // 用法一  传入function
        let grabStoresFn = arguments[0]
        return (componentClass: React.ComponentClass<any, any>) =>
            createStoreInjector(grabStoresFn, componentClass, grabStoresFn.name, true)
    } else {
        // 用法二 传入storesName
        return (componentClass: React.ComponentClass<any, any>) =>
            createStoreInjector(
                grabStoresByName(storeNames),
                componentClass,
                storeNames.join("-"),
                false
            )
    }
}

2,grabStoresByName 检查并获取全局通过Provider单向数据流传入的store

function grabStoresByName(
    storeNames: Array<string>
): (baseStores: IValueMap, nextProps: React.Props<any>) => React.PropsWithRef<any> | undefined {
    return function (baseStores, nextProps) {
        storeNames.forEach(function (storeName) {
            if (
                storeName in nextProps // prefer props over stores
            )
                return
            if (!(storeName in baseStores))
                throw new Error(
                    "MobX injector: Store '" +
                    storeName +
                    "' is not available! Make sure it is provided by some Provider"
                )
            nextProps[storeName] = baseStores[storeName]
        })
        return nextProps
    }
}

3,createStoreInjector 合并获取到的指定的Store绑定到该组件的props上,返回一个具有store的props组件对象

function createStoreInjector(
    grabStoresFn: IStoresToProps,
    component: IReactComponent<any>,
    injectNames: string,
    makeReactive: boolean
): IReactComponent<any> {
    // Support forward refs
    let Injector: IReactComponent<any> = React.forwardRef((props, ref) => {
        const newProps = { ...props }
        const context = React.useContext(MobXProviderContext)
        Object.assign(newProps, grabStoresFn(context || {}, newProps) || {})

        if (ref) {
            newProps.ref = ref
        }
        return React.createElement(component, newProps)
    })
    if (makeReactive) Injector = observer(Injector)
    Injector["isMobxInjector"] = true // assigned late to suppress observer warning
    // Static fields from component should be visible on the generated Injector
    copyStaticProperties(component, Injector)
    Injector["wrappedComponent"] = component
    Injector.displayName = getInjectName(component, injectNames)
    return Injector
}

三 observer

1,observer Api

传入带有injectot挂载过store的组件,然后使用组件Observer 组件包裹,Observer组件中调用useObserver

所以最终返回的结果是useObserver包裹的结果

export function observer<T extends IReactComponent>(component: T): T {
    if (component["isMobxInjector"] === true) {
        console.warn(
            "Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'"
        )
    }
    if (ReactForwardRefSymbol && component["$$typeof"] === ReactForwardRefSymbol) {
        const baseRender = component["render"]
        if (typeof baseRender !== "function")
            throw new Error("render property of ForwardRef was not a function")
        return React.forwardRef(function ObserverForwardRef() {
            const args = arguments
            return <Observer>{() => baseRender.apply(undefined, args)}</Observer>
        }) as T
    }

    // Function component
    if (
        typeof component === "function" &&
        (!component.prototype || !component.prototype.render) &&
        !component["isReactClass"] &&
        !Object.prototype.isPrototypeOf.call(React.Component, component)
    ) {
        return observerLite(component as React.StatelessComponent<any>) as T
    }

    return makeClassComponentObserver(component as React.ComponentClass<any, any>) as T
}

2, <Observer> 组件使用的是mobx-react-lite包中的 ObserverComponent,在ObserverComponent中解析出children,调用useObserver,所以我们应该重点关注useObserver

注意:children 是什么呢?const baseRender = component["render"]

组件中的render方法,render必须是function;否则直接返回null

因此useObserver传入的是组件的render方法,这react-mobx连接mobx和react组件的关键所在

ObserverComponent

function ObserverComponent({ children, render }: IObserverProps) {
    const component = children || render
    if (typeof component !== "function") {
        return null
    }
    return useObserver(component)
}

useObserver

export function useObserver<T>(fn: () => T, baseComponentName: string = "observed"): T {
    if (isUsingStaticRendering()) {
        return fn()
    }
    const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact())
    const forceUpdate = useForceUpdate()
    const reactionTrackingRef = React.useRef<any | null>(null)

    if (!reactionTrackingRef.current) {
        const newReaction = new Reaction(observerComponentNameFor(baseComponentName), () => {
            if (trackingData.mounted) {
                forceUpdate()
            } else {
                trackingData.changedBeforeMount = true
            }
        })

        const trackingData = addReactionToTrack(
            reactionTrackingRef,
            newReaction,
            objectRetainedByReact
        )
    }

    const { reaction } = reactionTrackingRef.current!
    React.useDebugValue(reaction, printDebugValue)

    React.useEffect(() => {
        recordReactionAsCommitted(reactionTrackingRef)
        if (reactionTrackingRef.current) {
            reactionTrackingRef.current.mounted = true
            if (reactionTrackingRef.current.changedBeforeMount) {
                reactionTrackingRef.current.changedBeforeMount = false
                forceUpdate()
            }
        } else {
            reactionTrackingRef.current = {
                reaction: new Reaction(observerComponentNameFor(baseComponentName), () => {
                    forceUpdate()
                }),
                mounted: true,
                changedBeforeMount: false,
                cleanAt: Infinity
            }
            forceUpdate()
        }

        return () => {
            reactionTrackingRef.current!.reaction.dispose()
            reactionTrackingRef.current = null
        }
    }, [])
    let rendering!: T
    let exception
    reaction.track(() => {
        try {
            rendering = fn()
        } catch (e) {
            exception = e
        }
    })

    if (exception) {
        throw exception 
    }

    return rendering
}

3 ,Reaction 对象

export class Reaction implements IDerivation, IReactionPublic {
    observing_: IObservable[] = [] 
    newObserving_: IObservable[] = []
    dependenciesState_ = IDerivationState_.NOT_TRACKING_
    diffValue_ = 0
    runId_ = 0
    unboundDepsCount_ = 0
    isDisposed_ = false
    isScheduled_ = false
    isTrackPending_ = false
    isRunning_ = false
    isTracing_: TraceMode = TraceMode.NONE

    constructor(
        public name_: string = __DEV__ ? "Reaction@" + getNextId() : "Reaction",
        private onInvalidate_: () => void,
        private errorHandler_?: (error: any, derivation: IDerivation) => void,
        public requiresObservable_ = false
    ) { }

    onBecomeStale_() {
        this.schedule_()
    }
    schedule_() {
        if (!this.isScheduled_) {
            this.isScheduled_ = true
            globalState.pendingReactions.push(this)
            runReactions()
        }
    }
    runReaction_() {
        /*xxxxx*/
    }
    track(fn: () => void) {
        /*xxxxx*/
        const prevReaction = globalState.trackingContext 
        globalState.trackingContext = this
        const result = trackDerivedFunction(this, fn, undefined)
        globalState.trackingContext = prevReaction
    }

    reportExceptionInDerivation_(error: any) {
    }
    dispose() {
        if (!this.isDisposed_) {
            this.isDisposed_ = true
            if (!this.isRunning_) {
                startBatch()
                clearObserving(this)
                endBatch()
            }
        }
    }
    getDisposer_(): IReactionDisposer {
        const r = this.dispose.bind(this) as IReactionDisposer
        r[$mobx] = this
        return r
    }
    toString() {
        return `Reaction[${this.name_}]`
    }
    trace(enterBreakPoint: boolean = false) {
        trace(this, enterBreakPoint)
    }
}

4, trackDerivedFunction

export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
    const prevAllowStateReads = allowStateReadsStart(true)
    changeDependenciesStateTo0(derivation)
    derivation.newObserving_ = new Array(derivation.observing_.length + 100)
    derivation.unboundDepsCount_ = 0
    derivation.runId_ = ++globalState.runId
    const prevTracking = globalState.trackingDerivation
    globalState.trackingDerivation = derivation
    globalState.inBatch++
    let result
    if (globalState.disableErrorBoundaries === true) {
        result = f.call(context)
    } else {
        try {
            result = f.call(context)
        } catch (e) {
            result = new CaughtException(e)
        }
    }
    globalState.inBatch--
    globalState.trackingDerivation = prevTracking
    bindDependencies(derivation)

    warnAboutDerivationWithoutDependencies(derivation)
    allowStateReadsEnd(prevAllowStateReads)
    return result
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容