new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Vue · API reference · all subjects

reactivity-api/core

84 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

watchEffect() basic example

const count = ref(0) watchEffect(() => console.log(count.value)) // -> logs 0 count.value++ // -> logs 1

ref() signature and behavior

ref() takes an inner value and returns a reactive and mutable ref object with a single property `.value` that points to the inner value. Type signature: function ref<T>(value: T): Ref<UnwrapRef<T>>. The ref object is mutable and reactive - you can assign new values to `.value` and any read operations are tracked while write operations trigger associated effects. If an object is assigned as a ref's value, the object is made deeply reactive with reactive(). This means nested refs will be deeply unwrapped. To avoid deep conversion, use shallowRef() instead.

Ref interface

The Ref interface has a single property: value: T.

ref() example

const count = ref(0) console.log(count.value) // 0 count.value = 1 console.log(count.value) // 1

computed() read-only signature

Read-only computed: function computed<T>(getter: (oldValue: T | undefined) => T, debuggerOptions?: DebuggerOptions): Readonly<Ref<Readonly<T>>>. Takes a getter function and returns a readonly reactive ref object for the returned value from the getter.

computed() writable signature

Writable computed: function computed<T>(options: { get: (oldValue: T | undefined) => T; set: (value: T) => void }, debuggerOptions?: DebuggerOptions): Ref<T>. Takes an object with get and set functions to create a writable ref object.

computed() read-only example

const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // error

computed() writable example

const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: (val) => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0

computed() debugging example

const plusOne = computed(() => count.value + 1, { onTrack(e) { debugger }, onTrigger(e) { debugger } })

reactive() signature

reactive() returns a reactive proxy of the object. Type signature: function reactive<T extends object>(target: T): UnwrapNestedRefs<T>.

reactive() behavior and details

The reactive conversion is deep and affects all nested properties. A reactive object deeply unwraps any properties that are refs while maintaining reactivity. However, there is no ref unwrapping performed when the ref is accessed as an element of a reactive array or native collection type like Map. To avoid deep conversion, use shallowReactive() instead. The returned object and its nested objects are wrapped with ES Proxy and are not equal to the original objects. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.

reactive() basic example

const obj = reactive({ count: 0 }) obj.count++

reactive() ref assignment unwrapping example

const count = ref(1) const obj = reactive({}) obj.count = count console.log(obj.count) // 1 console.log(obj.count === count.value) // true

readonly() signature

readonly() takes an object (reactive or plain) or a ref and returns a readonly proxy to the original. Type signature: function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>.

readonly() behavior

A readonly proxy is deep: any nested property accessed will be readonly as well. It has the same ref-unwrapping behavior as reactive(), except the unwrapped values will also be made readonly. To avoid deep conversion, use shallowReadonly() instead.

readonly() example

const original = reactive({ count: 0 }) const copy = readonly(original) watchEffect(() => { // works for reactivity tracking console.log(copy.count) }) // mutating original will trigger watchers relying on the copy original.count++ // mutating the copy will fail and result in a warning copy.count++ // warning!

watchEffect() signature

function watchEffect(effect: (onCleanup: OnCleanup) => void, options?: WatchEffectOptions): WatchHandle. type OnCleanup = (cleanupFn: () => void) => void.

WatchEffectOptions interface

interface WatchEffectOptions { flush?: 'pre' | 'post' | 'sync' // default: 'pre'; onTrack?: (event: DebuggerEvent) => void; onTrigger?: (event: DebuggerEvent) => void }

WatchHandle interface

interface WatchHandle { (): void // callable, same as `stop`; pause: () => void; resume: () => void; stop: () => void }

watchEffect() behavior and details

watchEffect() runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies change. The effect function receives a function that can be used to register a cleanup callback. The cleanup callback will be called right before the next time the effect is re-run and can be used to clean up invalidated side effects like pending async requests. By default, watchers run just prior to component rendering. Setting flush: 'post' defers the watcher until after component rendering. In rare cases, flush: 'sync' can be used to trigger a watcher immediately when a reactive dependency changes (e.g., to invalidate a cache), but this should be used with caution as it can lead to performance and data consistency problems if multiple properties are being updated at the same time. The return value is a handle function that can be called to stop the effect.

WatchSource type

type WatchSource<T> = Ref<T> | (() => T) | (T extends object ? T : never)

watchEffect() stopping example

const stop = watchEffect(() => {}) // when the watcher is no longer needed: stop()

watchEffect() cleanup with onWatcherCleanup example

import { onWatcherCleanup } from 'vue' watchEffect(async () => { const { response, cancel } = doAsyncWork(newId) // `cancel` will be called if `id` changes, cancelling // the previous request if it hasn't completed yet onWatcherCleanup(cancel) data.value = await response })

watchEffect() options example

watchEffect(() => {}, { flush: 'post', onTrack(e) { debugger }, onTrigger(e) { debugger } })

watchPostEffect() is watchEffect with flush post

watchPostEffect() is an alias of watchEffect() with flush: 'post' option.

watchSyncEffect() is watchEffect with flush sync

watchSyncEffect() is an alias of watchEffect() with flush: 'sync' option.

watch() multiple sources signature

function watch<T>(sources: WatchSource<T>[], callback: WatchCallback<T[]>, options?: WatchOptions): WatchHandle

WatchCallback type

type WatchCallback<T> = (value: T, oldValue: T, onCleanup: (cleanupFn: () => void) => void) => void

WatchOptions interface

interface WatchOptions extends WatchEffectOptions { immediate?: boolean // default: false; deep?: boolean | number // default: false; flush?: 'pre' | 'post' | 'sync' // default: 'pre'; onTrack?: (event: DebuggerEvent) => void; onTrigger?: (event: DebuggerEvent) => void; once?: boolean // default: false (3.4+) }

watch() is lazy by default

watch() is lazy by default - the callback is only called when the watched source has changed.

watch() source options

The first argument to watch() is the watcher's source. The source can be a getter function that returns a value, a ref, a reactive object, or an array of these sources.

watch() callback arguments

The callback receives three arguments: the new value, the old value, and a function for registering a side effect cleanup callback. The cleanup callback will be called right before the next time the effect is re-run and can be used to clean up invalidated side effects like pending async requests.

watch() multiple sources callback

When watching multiple sources, the callback receives two arrays containing new and old values corresponding to the source array.

watch() options: immediate

immediate: boolean (default: false) - trigger the callback immediately on watcher creation. Old value will be undefined on the first call.

watch() options: deep

deep: boolean | number (default: false) - force deep traversal of the source if it is an object, so that the callback fires on deep mutations. In 3.5+, this can also be a number indicating the max traversal depth.

watch() options: flush

flush: 'pre' | 'post' | 'sync' (default: 'pre') - adjust the callback's flush timing. See Callback Flush Timing and watchEffect() documentation.

watch() options: onTrack and onTrigger

onTrack / onTrigger: debug the watcher's dependencies.

watch() options: once

once: boolean (default: false, added in 3.4+) - run the callback only once. The watcher is automatically stopped after the first callback run.

watch() advantages over watchEffect()

Compared to watchEffect(), watch() allows you to: perform the side effect lazily; be more specific about what state should trigger the watcher to re-run; access both the previous and current value of the watched state.

watch() watching a getter example

const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } )

watch() watching a ref example

const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })

watch() watching multiple sources example

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })

watch() deep mode getter example

const state = reactive({ count: 0 }) watch( () => state, (newValue, oldValue) => { // newValue === oldValue }, { deep: true } )

watch() reactive object automatic deep mode

When directly watching a reactive object, the watcher is automatically in deep mode. Example: const state = reactive({ count: 0 }); watch(state, () => { /* triggers on deep mutation to state */ })

watch() options and flush example

watch(source, callback, { flush: 'post', onTrack(e) { debugger }, onTrigger(e) { debugger } })

watch() stopping example

const stop = watch(source, callback) // when the watcher is no longer needed: stop()

watch() pause and resume example

const { stop, pause, resume } = watch(() => {}) // temporarily pause the watcher pause() // resume later resume() // stop stop()

watch() cleanup example

watch(id, async (newId, oldId, onCleanup) => { const { response, cancel } = doAsyncWork(newId) // `cancel` will be called if `id` changes, cancelling // the previous request if it hasn't completed yet onCleanup(cancel) data.value = await response })

onWatcherCleanup() signature and availability

onWatcherCleanup() is available in Vue 3.5+. Signature: function onWatcherCleanup(cleanupFn: () => void, failSilently?: boolean): void. Register a cleanup function to be executed when the current watcher is about to re-run. Can only be called during the synchronous execution of a watchEffect effect function or watch callback function (i.e. it cannot be called after an await statement in an async function).

onWatcherCleanup() example

import { watch, onWatcherCleanup } from 'vue' watch(id, (newId) => { const { response, cancel } = doAsyncWork(newId) // `cancel` will be called if `id` changes, cancelling // the previous request if it hasn't completed yet onWatcherCleanup(cancel) })

Flush modes for watchers

Vue watchers support three flush modes: 'pre' (default) runs the watcher callback just prior to component rendering, 'post' defers the watcher until after component rendering, and 'sync' runs the watcher immediately when a reactive dependency changes. The 'sync' mode should be used with caution as it can lead to performance and data consistency problems if multiple properties are being updated at the same time.

Difference between ref and reactive

ref() wraps a value in a reactive object with a .value property that must be accessed in JavaScript (though it auto-unwraps in templates and reactive objects). ref() works with any data type. reactive() directly wraps an object making all its properties reactive without requiring a .value accessor, but can only be used with objects. When a ref is assigned to a reactive object property, it becomes unwrapped and acts like a normal property. reactive() creates a deep proxy that affects all nested properties, while ref() allows you to wrap primitives and provides explicit .value access.

ref() uses getter/setter to intercept value access

Vue 3 uses getter/setters for refs. The ref() function returns an object with a 'value' property. The getter calls track(refObject, 'value') before returning the stored value. The setter calls trigger(refObject, 'value') after assigning the new value.

How Vue's reactivity tracking works: the track() function

Inside track(target, key), Vue checks if there is a currently running effect. If there is one, it looks up the subscriber effects stored in a Set for the property being tracked, and adds the effect to that Set. The data structure for effect subscriptions is a global WeakMap<target, Map<key, Set<effect>>>.

How Vue's reactivity triggering works: the trigger() function

Inside trigger(target, key), Vue looks up the subscriber effects for the property. It then invokes each effect by calling effect().

Reactive Effect and whenDepsChange() concept

A Reactive Effect is an effect that automatically tracks its dependencies and re-runs whenever a dependency changes. The whenDepsChange() function wraps the raw update function in an effect that sets itself as the current active effect before running the update, then clears the active effect after. This enables track() calls during the update to locate the current active effect.

computed() internally manages invalidation using a reactive effect

The computed() function creates a computed property that is more declarative than using watchEffect() to mutate a ref. Internally, computed() manages its invalidation and re-computation using a reactive effect.

DebuggerEvent type for reactivity debugging

The DebuggerEvent type has properties: effect (ReactiveEffect), target (object), type (TrackOpTypes | TriggerOpTypes), key (any), newValue (any, optional), oldValue (any, optional), oldTarget (Map<any, any> | Set<any>, optional). TrackOpTypes can be 'get', 'has', or 'iterate'. TriggerOpTypes can be 'set', 'add', 'delete', or 'clear'.

computed() debugging with onTrack and onTrigger callbacks

computed() accepts a second options object with onTrack and onTrigger callbacks. onTrack is called when a reactive property or ref is tracked as a dependency. onTrigger is called when a dependency mutation triggers the computed property to update. Both callbacks receive debugger events in the same format as component debug hooks. These options only work in development mode.

Vue's reactivity is runtime-based, not compile-time

Vue's reactivity system is primarily runtime-based: tracking and triggering are performed while code runs in the browser. The pros are that it works without a build step and has fewer edge cases. The cons are that it is constrained by JavaScript's syntax limitations, leading to the need for value containers like refs.

Give your agent this brain