Reactivity API: Core

ref()

Takes an inner value and returns a reactive and mutable ref object, which has a single property .value that points to the inner value.

  • Type

    function ref<T>(value: T): Ref<UnwrapRef<T>>
    
    interface Ref<T> {
      value: T
    }
  • Details

    The ref object is mutable - i.e. you can assign new values to .value. It is also reactive - i.e. any read operations to .value are tracked, and write operations will trigger associated effects.

    If an object is assigned as a ref's value, the object is made deeply reactive with reactive(). This also means if the object contains nested refs, they will be deeply unwrapped.

    To avoid the deep conversion, use shallowRef() instead.

  • Example

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

computed()

Takes a getter function and returns a readonly reactive ref object for the returned value from the getter. It can also take an object with get and set functions to create a writable ref object.

  • Type

    // read-only
    function computed<T>(
      getter: () => T,
      // see "Computed Debugging" link below
      debuggerOptions?: DebuggerOptions
    ): Readonly<Ref<Readonly<T>>>
    
    // writable
    function computed<T>(
      options: {
        get: () => T
        set: (value: T) => void
      },
      debuggerOptions?: DebuggerOptions
    ): Ref<T>
  • Example

    Creating a readonly computed ref:

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

    Creating a writable computed ref:

    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

    Debugging:

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

reactive()

Returns a reactive proxy of the object.

  • Type

    function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
  • Details

    The reactive conversion is "deep": it affects all nested properties. A reactive object also deeply unwraps any properties that are refs while maintaining reactivity.

    It should also be noted that there is no ref unwrapping performed when the ref is accessed as an element of a reactive array or a native collection type like Map.

    To avoid the deep conversion and only retain reactivity at the root level, use shallowReactive() instead.

    The returned object and its nested objects are wrapped with ES Proxy and not equal to the original objects. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.

  • Example

    Creating a reactive object:

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

    Ref unwrapping:

    const count = ref(1)
    const obj = reactive({ count })
    
    // ref will be unwrapped
    console.log(obj.count === count.value) // true
    
    // it will update `obj.count`
    count.value++
    console.log(count.value) // 2
    console.log(obj.count) // 2
    
    // it will also update `count` ref
    obj.count++
    console.log(obj.count) // 3
    console.log(count.value) // 3

    Note that refs are not unwrapped when accessed as array or collection elements:

    const books = reactive([ref('JS Guide')])
    // need .value here
    console.log(books[0].value)
    
    const map = reactive(new Map([['count', ref(0)]]))
    // need .value here
    console.log(map.get('count').value)

    When assigning a ref to a reactive property, that ref will also be automatically unwrapped:

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

readonly()

Takes an object (reactive or plain) or a ref and returns a readonly proxy to the original.

  • Type

    function readonly<T extends object>(
      target: T
    ): DeepReadonly<UnwrapNestedRefs<T>>
  • Details

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

    To avoid the deep conversion, use shallowReadonly() instead.

  • Example

    const original = reactive({ count: 0 })
    
    const copy = readonly(original)
    
    effect(() => {
      // 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!

effect()

Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.

  • Type

    function effect(fn: EffectCallback, options?: EffectOptions): Dispose
    
    type Cleanup = () => void;
    type EffectCallback = () => void | Cleanup;
    
    interface EffectOptions{
      type?: 'sync' | 'update' | 'layout'; // default sync
      onTrack?: (event: DebuggerEvent) => void
      onTrigger?: (event: DebuggerEvent) => void
    }
    
    type Dispose = () => void
  • Details

    The first argument is the effect callback function to be run. Optionally this effect callback can return a cleanup function.

    The second argument is an optional options object that can be used to adjust the effect's flush timing or to debug the effect's dependencies. By default effects run synchronously, but when they are used in the component scope "type" can be set to update or layout. When the "type" is set to "update" the effect will run after the component is updated and rendered with the latest state. When the "type" is set to "layout" the effect will run after the component is updated but before it's painted. You can relate their timings with useEffect and useLayoutEffect hooks.

    The return value is a disposer function that can be called to stop the effect from running again.

  • Example

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

    Stopping the watcher:

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

  • Cleanup effect:

    const user = ref();
    
    effect(() => {
      const { cancel, result } = fetchUser(userId.value);
    
      result.then((p) => (user.value = p));
    
      // cleanup function to cancel the previous request
      return () => {
        cancel();
      };
    });
    
  • Options:

    effect(() => {}, {
      type: 'update',
      onTrack(e) {
        debugger
      },
      onTrigger(e) {
        debugger
      }
    })

updateEffect()

Alias of effect() with type: 'update' option.

layoutEffect()

Alias of effect() with type: 'layout' option.

watch()

Watches one or more reactive data sources and invokes a callback function when the sources change.

  • Type

    function watch<T>(
      source: WatchSource, 
      clb: WatchCallback<T>, 
      options?: WatchOptions
    ): Dispose;
    
    type WatchSource = (() => unknown) | Ref<unknown> | object;
    
    type WatchCallback<T> = (newValue: T, oldValue?: T) => void;
    
    type WatchOptions = EffectOptions & {
      immediate?: boolean;
      deep?: boolean;
    };
    
    interface WatchOptions{
      type?: 'sync' | 'update' | 'layout'; // default sync
      onTrack?: (event: DebuggerEvent) => void
      onTrigger?: (event: DebuggerEvent) => void
      immediate?: boolean;
      deep?: boolean;
    }
    
    type Dispose = () => void
  • Details

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

    The first argument is the watcher's source. The source can be one of the following:

    • A getter function that returns a value

    • A ref

    • A reactive object

    The second argument is the callback that will be called when the source changes. The callback receives two arguments: the new value and the old value.

    The third optional argument is an options object that supports the following options:

    • immediate: trigger the callback immediately on watcher creation. Old value will be undefined on the first call.

    • deep: force deep traversal of the source if it is an object, so that the callback fires on deep mutations.

    • type: adjust the callback's flush timing. See effect().

    • onTrack / onTrigger: debug the watcher's dependencies.

    Compared to effect(), watch() allows us 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.

  • Example

    Watching a getter:

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

    Watching a ref:

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

    When using a getter source, the watcher only fires if the getter's return value has changed. If you want the callback to fire even on deep mutations, you need to explicitly force the watcher into deep mode with { deep: true }. Note in deep mode, the new value and the old will be the same object if the callback was triggered by a deep mutation:

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

    When directly watching a reactive object, the watcher is automatically in deep mode:

    const state = reactive({ count: 0 })
    watch(state, () => {
      /* triggers on deep mutation to state */
    })

    watch() shares the same flush timing and debugging options with effect()

    watch(source, callback, {
      type: 'update',
      onTrack(e) {
        debugger
      },
      onTrigger(e) {
        debugger
      }
    })

    Stopping the watcher:

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

Last updated