This is a demo project to better understand how Vue3's reactivity works internally. Some features are ignored on purpose as it doesn't make that much sense to implement those in vanilla js without having a lifecycle.
type Subscriber<T = any> = (newValue: T, oldValue: T) => void
interface Ref<T = any> {
subscribe(fn: Subscriber<T>);
unSubscribe(fn: Subscriber<T>);
value: T;
};
type ref = (v: T) => Ref<T>;A ref holds a single value which can be changed at any time and subscribed to:
const a = ref(0);
a.subscribe((value, oldValue) => console.log({ value, oldValue }))
a.value = 5; // Logs {value: 5, oldValue: 0}
a.value = 2; // Logs {value: 2, oldValue: 5}type StopEffectCallback = () => void;
type effect = (fn: () => void) => StopEffectCallback;An effect takes a function which gets called whenever the ref accessed in it changes:
const a = ref(0);
const b = ref(0);
effect(() => console.log({ a: a.value, b: b.value }));
a.value = 5; // Logs {a: 5, b: 0}
b.value = 3; // Logs {a: 5, b: 3}effect returns a function to clear it:
const a = ref(0);
const b = ref(0);
const stop = effect(() => console.log({ a: a.value, b: b.value }));
a.value = 5; // Logs {a: 5, b: 0}
stop();
a.value = 5; // Logs nothingtype ComputedRef<T> = ReadonlyRef<T>;
type computed = <T>(v: () => T) => ComputedRef<T>;Same as effect but returning a value:
const a = ref(0);
const b = ref(0);
const sum = computed(() => a.value + b.value);
sum.subscribe((value, oldValue) => console.log({ value, oldValue }))
a.value = 3; // Logs {value: 3, oldValue: 0}
b.value = 5; // Logs {value: 5, oldValue: 3}Trying to set the value of a computed value will throw an error.
interface WatchOptions {
immediate?: boolean;
};
type StopWatchCallback = () => void;
type WatchCallback<T extends Ref[] | Ref> = (args: UnwrapRefs<T>) => void;
type watch = <T extends Ref[] | Ref>(
refs: T,
cb: WatchCallback<T>,
options: WatchOptions
) => StopWatchCallback;Watches a list or a single ref.
const a = ref(6);
const b = ref(3);
const stop = watch([a, b], ([a, b]) => {
console.log({ a, b }); // {a: 6, b: 4}
});
// Trigger watch by changing "b"
b.value = 4;
// Stop watching
stop();type ReadonlyRef<T> = Ref<T>;
type Readonly = <T>(v: Ref<T>) => ReadonlyRef<T>;Wraps a ref and marks it as readonly:
const a = ref(6);
const b = readonly(a);
b.value; // 6
a.value = 7;
b.value; // 7;
b.value = 5; // Throw error