与mobx不一样的reaction、autorun

978 阅读1分钟
灵感起源
export interface Schedule {
    schedule: () => unknown | void;
    dependencies: Set<Set<Schedule>>;
}

const context: any[] = [];

function subscribe(schedule: Schedule, subscriptions: Set<Schedule>) {
    subscriptions.add(schedule);
    schedule.dependencies.add(subscriptions);
}

export function createSignal<T>(value: T):[() => T, (val:T) =>void] {
    const subscriptions = new Set<Schedule>();

    const read = (): T => {
        const schedule = context[context.length - 1];
        if (schedule) subscribe(schedule, subscriptions);
        return value;
    };

    const write = (nextValue: T) => {
        value = nextValue;
        for (const sub of [...subscriptions]) {
            sub.schedule();
        }
    };
    return [read, write];
}

export function cleanup(reaction: any) {
    for (const dep of reaction.dependencies) {
        dep.delete(reaction);
    }
    reaction.dependencies.clear();
}

export function createReaction(schedule: () => void | unknown) {

    function track(fn: () => void) {
        cleanup(reaction);
        context.push(reaction);
        try {
            fn();
        } finally {
            context.pop();
        }
    }

    const reaction = {
        schedule,
        dependencies: new Set<Set<Schedule>>()
    }

    return { track };
}


export const autorun = (fn:() => void | unknown) => {
    const { track } = createReaction(() => {
        track(fn)
    })
    track(fn);
}
灵感发射
type CreateReactionType = typeof createReaction;
type ReturnReaction = ReturnType<CreateReactionType>;

export function useReaction<T>(fn: () => T, reaction: (signal: T) => void): T {
    const [, forceUpdate] = useState({});
    const reactionTrackingRef = useRef<ReturnReaction | null>(null);

    if (!reactionTrackingRef.current) {
        reactionTrackingRef.current = createReaction(() => {
            forceUpdate({});
            reaction(fn());
        });
    }

    const { track } = reactionTrackingRef.current;

    let rendering!: T;
    let exception;
    track(() => {
        try {
            rendering = fn();
        } catch (e) {
            exception = e;
        }
    });

    if (exception) {
        throw exception; // re-throw any exceptions caught during rendering
    }

    return rendering;
}