gismanli / blog

0 stars 0 forks source link

MobX 源码学习:Observable #2

Open gismanli opened 7 years ago

gismanli commented 7 years ago

前言

Observable 是 MobX 的重要构成部分,其包含:

也许我们之前会有这样的疑问:为什么我们更改数据时就会触发响应更新呢?

让我们来看一下是具体是如何实现的。

Atom

顾名思义,atom的意思就是原子,在 mobx 中任何可以存储状态的都是一个 Atom。

Atom 有两个重要的职责:

来分析下源代码:

先看下 interface IObservable

export interface IDepTreeNode {
    name: string;
    observing?: IObservable[];
}

export enum IDerivationState {
    NOT_TRACKING = -1,
    UP_TO_DATE = 0,
    POSSIBLY_STALE = 1,
    STALE = 2
}

export interface IDerivation extends IDepTreeNode {
    observing: IObservable[];
    newObserving: IObservable[];
    dependenciesState: IDerivationState;
    runId: number;
    unboundDepsCount: number;
    __mapid: string;
    onBecomeStale();
    recoverFromError(); // TODO: revisit implementation of error handling
}

export interface IObservable extends IDepTreeNode {
    diffValue: number;
    lastAccessedBy: number;
    lowestObserverState: IDerivationState;
    isPendingUnobservation: boolean;
    observers: IDerivation[]; 
    observersIndexes: {}; 
    onBecomeUnobserved();
}

这里预定义了一堆属性,继续看 Atom 定义:

export interface IAtom extends IObservable {
}

export class BaseAtom implements IAtom {
    isPendingUnobservation = true; // for effective unobserving. BaseAtom has true, for extra optimization, so it's onBecomeUnobserved never get's called, because it's not needed
    observers = [];
    observersIndexes = {};

    diffValue = 0;
    lastAccessedBy = 0;
    lowestObserverState = IDerivationState.NOT_TRACKING;

    constructor(public name = "Atom@" + getNextId()) { }

    public onBecomeUnobserved() {
        // noop
    }

    public reportObserved() {
        reportObserved(this);
    }

    public reportChanged() {
        transactionStart("propagatingAtomChange", null, false);
        propagateChanged(this);
        transactionEnd(false);
    }

    toString() {
        return this.name;
    }
}

这里主要是定义了 reportObservedreportChanged 方法:

ObservableValue

首先看下类 ObservableValue 的定义:

export type IUNCHANGED = {};

export const UNCHANGED: IUNCHANGED = {};

export interface IObservableValue<T> {
    get(): T;
    set(value: T): void;
    intercept(handler: IInterceptor<IValueWillChange<T>>): Lambda;
    observe(listener: (newValue: T, oldValue: T) => void, fireImmediately?: boolean): Lambda;
}

export class ObservableValue<T> extends BaseAtom implements IObservableValue<T> {
    hasUnreportedChange = false;
    protected value: T = undefined;

    constructor(value: T, protected mode: ValueMode, name = "ObservableValue@" + getNextId()) {
        super(name);
        const [childmode, unwrappedValue] = getValueModeFromValue(value, ValueMode.Recursive);
        // If the value mode is recursive, modifiers like 'structure', 'reference', or 'flat' could apply
        if (this.mode === ValueMode.Recursive)
            this.mode = childmode;
        this.value = makeChildObservable(unwrappedValue, this.mode, this.name);
    }

    public set(newValue: T) {
        const oldValue = this.value;
        newValue = this.prepareNewValue(newValue) as any;
        if (newValue !== UNCHANGED) {
            this.setNewValue(newValue);
        }
    }

    prepareNewValue(newValue): T | IUNCHANGED {
        checkIfStateModificationsAreAllowed();
        const changed = valueDidChange(this.mode === ValueMode.Structure, this.value, newValue);
        if (changed)
            return makeChildObservable(newValue, this.mode, this.name);
        return UNCHANGED;
    }

    setNewValue(newValue: T) {
        const oldValue = this.value;
        this.value = newValue;
        this.reportChanged();
    }

    public get(): T {
        this.reportObserved();
        return this.value;
    }

    public observe(listener: (newValue: T, oldValue: T) => void, fireImmediately?: boolean): Lambda {
        if (fireImmediately)
            listener(this.value, undefined);
        return registerListener(this, listener);
    }

    toJSON() {
        return this.get();
    }

    toString() {
        return `${this.name}[${this.value}]`;
    }
}