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) {
if (typeof arguments[0] === "function") {
// 用法一 传入function
let grabStoresFn = arguments[0]
return (componentClass: React.ComponentClass) =>
createStoreInjector(grabStoresFn, componentClass, grabStoresFn.name, true)
} else {
// 用法二 传入storesName
return (componentClass: React.ComponentClass) =>
createStoreInjector(
grabStoresByName(storeNames),
componentClass,
storeNames.join("-"),
false
)
}
}
2,grabStoresByName 检查并获取全局通过Provider单向数据流传入的store
function grabStoresByName(
storeNames: Array
): (baseStores: IValueMap, nextProps: React.Props) => React.PropsWithRef | 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,
injectNames: string,
makeReactive: boolean
): IReactComponent {
// Support forward refs
let Injector: IReactComponent = 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(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 {() => baseRender.apply(undefined, args)}
}) 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) as T
}
return makeClassComponentObserver(component as React.ComponentClass) as T
}
2,
注意: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(fn: () => T, baseComponentName: string = "observed"): T {
if (isUsingStaticRendering()) {
return fn()
}
const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact())
const forceUpdate = useForceUpdate()
const reactionTrackingRef = React.useRef(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(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
}