docs: comments on reactivity functions (fixes #4832) (#4836)

close #4832
This commit is contained in:
Roland Hummel 2023-03-31 11:06:10 +02:00 committed by GitHub
parent 5261085113
commit c346af2b6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 408 additions and 28 deletions

View File

@ -30,7 +30,7 @@ export function parseUrl(url: string): UrlWithStringQuery {
/**
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
* @param urlString an url as a string
* @param urlString - an url as a string
*/
function parseUriParts(urlString: string): UrlWithStringQuery {
// A TypeError is thrown if urlString is not a string

View File

@ -68,6 +68,39 @@ export class ComputedRefImpl<T> {
}
}
/**
* 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.
*
* @example
* ```js
* // Creating a readonly computed ref:
* const count = ref(1)
* const plusOne = computed(() => count.value + 1)
*
* console.log(plusOne.value) // 2
* plusOne.value++ // error
* ```
*
* ```js
* // 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
* ```
*
* @param getter - Function that produces the next value.
* @param debugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.
* @see {@link https://vuejs.org/api/reactivity-core.html#computed}
*/
export function computed<T>(
getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions

View File

@ -167,6 +167,16 @@ export interface ReactiveEffectRunner<T = any> {
effect: ReactiveEffect
}
/**
* Registers the given function to track reactive updates.
*
* The given function will be run once immediately. Every time any reactive
* property that's accessed within it gets updated, the function will run again.
*
* @param fn - The function that will track reactive updates.
* @param options - Allows to control the effect's behaviour.
* @returns A runner that can be used to control the effect after creation.
*/
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
@ -188,6 +198,11 @@ export function effect<T = any>(
return runner
}
/**
* Stops the effect associated with the given runner.
*
* @param runner - Association with the effect to stop tracking.
*/
export function stop(runner: ReactiveEffectRunner) {
runner.effect.stop()
}
@ -195,21 +210,40 @@ export function stop(runner: ReactiveEffectRunner) {
export let shouldTrack = true
const trackStack: boolean[] = []
/**
* Temporarily pauses tracking.
*/
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
/**
* Re-enables effect tracking (if it was paused).
*/
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
/**
* Resets the previous global effect tracking state.
*/
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
/**
* Tracks access to a reactive property.
*
* This will check which effect is running at the moment and record it as dep
* which records all effects that depend on the reactive property.
*
* @param target - Object holding the reactive property.
* @param type - Defines the type of access to the reactive property.
* @param key - Identifier of the reactive property to track.
*/
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
@ -260,6 +294,14 @@ export function trackEffects(
}
}
/**
* Finds all deps associated with the target (or a specific property) and
* triggers the effects stored within.
*
* @param target - The reactive object.
* @param type - Defines the type of the operation that needs to trigger effects.
* @param key - Can be used to target a specific reactive property in the target object.
*/
export function trigger(
target: object,
type: TriggerOpTypes,

View File

@ -107,6 +107,15 @@ export class EffectScope {
}
}
/**
* Creates an effect scope object which can capture the reactive effects (i.e.
* computed and watchers) created within it so that these effects can be
* disposed together. For detailed use cases of this API, please consult its
* corresponding {@link https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md | RFC}.
*
* @param detached - Can be used to create a "detached" effect scope.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#effectscope}
*/
export function effectScope(detached?: boolean) {
return new EffectScope(detached)
}
@ -120,10 +129,22 @@ export function recordEffectScope(
}
}
/**
* Returns the current active effect scope if there is one.
*
* @see {@link https://vuejs.org/api/reactivity-advanced.html#getcurrentscope}
*/
export function getCurrentScope() {
return activeEffectScope
}
/**
* Registers a dispose callback on the current active effect scope. The
* callback will be invoked when the associated effect scope is stopped.
*
* @param fn - The callback function to attach to the scope's cleanup.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#onscopedispose}
*/
export function onScopeDispose(fn: () => void) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn)

View File

@ -65,26 +65,19 @@ function getTargetType(value: Target) {
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
/**
* Creates a reactive copy of the original object.
* Returns a reactive proxy of the object.
*
* The reactive conversion is "deep"it affects all nested properties. In the
* ES2015 Proxy based implementation, the returned proxy is **not** equal to the
* original object. It is recommended to work exclusively with the reactive
* proxy and avoid relying on the original object.
*
* A reactive object also automatically unwraps refs contained in it, so you
* don't need to use `.value` when accessing and mutating their value:
* The reactive conversion is "deep": it affects all nested properties. A
* reactive object also deeply unwraps any properties that are refs while
* maintaining reactivity.
*
* @example
* ```js
* const count = ref(0)
* const obj = reactive({
* count
* })
*
* obj.count++
* obj.count // -> 1
* count.value // -> 1
* const obj = reactive({ count: 0 })
* ```
*
* @param target - The source object.
* @see {@link https://vuejs.org/api/reactivity-core.html#reactive}
*/
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
@ -106,9 +99,34 @@ export declare const ShallowReactiveMarker: unique symbol
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }
/**
* Return a shallowly-reactive copy of the original object, where only the root
* level properties are reactive. It also does not auto-unwrap refs (even at the
* root level).
* Shallow version of {@link reactive()}.
*
* Unlike {@link reactive()}, there is no deep conversion: only root-level
* properties are reactive for a shallow reactive object. Property values are
* stored and exposed as-is - this also means properties with ref values will
* not be automatically unwrapped.
*
* @example
* ```js
* const state = shallowReactive({
* foo: 1,
* nested: {
* bar: 2
* }
* })
*
* // mutating state's own properties is reactive
* state.foo++
*
* // ...but does not convert nested objects
* isReactive(state.nested) // false
*
* // NOT reactive
* state.nested.bar++
* ```
*
* @param target - The source object.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreactive}
*/
export function shallowReactive<T extends object>(
target: T
@ -147,8 +165,33 @@ export type DeepReadonly<T> = T extends Builtin
: Readonly<T>
/**
* Creates a readonly copy of the original object. Note the returned copy is not
* made reactive, but `readonly` can be called on an already reactive object.
* Takes an object (reactive or plain) or a ref and returns a readonly proxy to
* the original.
*
* A readonly proxy is deep: any nested property accessed will be readonly as
* well. It also has the same ref-unwrapping behavior as {@link reactive()},
* except the unwrapped values will also be made readonly.
*
* @example
* ```js
* 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!
* ```
*
* @param target - The source object.
* @see {@link https://vuejs.org/api/reactivity-core.html#readonly}
*/
export function readonly<T extends object>(
target: T
@ -163,10 +206,34 @@ export function readonly<T extends object>(
}
/**
* Returns a reactive-copy of the original object, where only the root level
* properties are readonly, and does NOT unwrap refs nor recursively convert
* returned properties.
* This is used for creating the props proxy object for stateful components.
* Shallow version of {@link readonly()}.
*
* Unlike {@link readonly()}, there is no deep conversion: only root-level
* properties are made readonly. Property values are stored and exposed as-is -
* this also means properties with ref values will not be automatically
* unwrapped.
*
* @example
* ```js
* const state = shallowReadonly({
* foo: 1,
* nested: {
* bar: 2
* }
* })
*
* // mutating state's own properties will fail
* state.foo++
*
* // ...but works on nested objects
* isReadonly(state.nested) // false
*
* // works
* state.nested.bar++
* ```
*
* @param target - The source object.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreadonly}
*/
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
return createReactiveObject(
@ -217,6 +284,24 @@ function createReactiveObject(
return proxy
}
/**
* Checks if an object is a proxy created by {@link reactive()} or
* {@link shallowReactive()} (or {@link ref()} in some cases).
*
* @example
* ```js
* isReactive(reactive({})) // => true
* isReactive(readonly(reactive({}))) // => true
* isReactive(ref({}).value) // => true
* isReactive(readonly(ref({})).value) // => true
* isReactive(ref(true)) // => false
* isReactive(shallowRef({}).value) // => false
* isReactive(shallowReactive({})) // => true
* ```
*
* @param value - The value to check.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isreactive}
*/
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
@ -224,6 +309,17 @@ export function isReactive(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
/**
* Checks whether the passed value is a readonly object. The properties of a
* readonly object can change, but they can't be assigned directly via the
* passed object.
*
* The proxies created by {@link readonly()} and {@link shallowReadonly()} are
* both considered readonly, as is a computed ref without a set function.
*
* @param value - The value to check.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isreadonly}
*/
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
@ -232,10 +328,40 @@ export function isShallow(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}
/**
* Checks if an object is a proxy created by {@link reactive},
* {@link readonly}, {@link shallowReactive} or {@link shallowReadonly()}.
*
* @param value - The value to check.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
*/
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
/**
* Returns the raw, original object of a Vue-created proxy.
*
* `toRaw()` can return the original object from proxies created by
* {@link reactive()}, {@link readonly()}, {@link shallowReactive()} or
* {@link shallowReadonly()}.
*
* This is an escape hatch that can be used to temporarily read without
* incurring proxy access / tracking overhead or write without triggering
* changes. It is **not** recommended to hold a persistent reference to the
* original object. Use with caution.
*
* @example
* ```js
* const foo = {}
* const reactiveFoo = reactive(foo)
*
* console.log(toRaw(reactiveFoo) === foo) // true
* ```
*
* @param observed - The object for which the "raw" value is requested.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#toraw}
*/
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
@ -243,13 +369,49 @@ export function toRaw<T>(observed: T): T {
export type Raw<T> = T & { [RawSymbol]?: true }
/**
* Marks an object so that it will never be converted to a proxy. Returns the
* object itself.
*
* @example
* ```js
* const foo = markRaw({})
* console.log(isReactive(reactive(foo))) // false
*
* // also works when nested inside other reactive objects
* const bar = reactive({ foo })
* console.log(isReactive(bar.foo)) // false
* ```
*
* **Warning:** `markRaw()` together with the shallow APIs such as
* {@link shallowReactive()} allow you to selectively opt-out of the default
* deep reactive/readonly conversion and embed raw, non-proxied objects in your
* state graph.
*
* @param value - The object to be marked as "raw".
* @see {@link https://vuejs.org/api/reactivity-advanced.html#markraw}
*/
export function markRaw<T extends object>(value: T): Raw<T> {
def(value, ReactiveFlags.SKIP, true)
return value
}
/**
* Returns a reactive proxy of the given value (if possible).
*
* If the given value is not an object, the original value itself is returned.
*
* @param value - The value for which a reactive proxy shall be created.
*/
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
/**
* Returns a readonly proxy of the given value (if possible).
*
* If the given value is not an object, the original value itself is returned.
*
* @param value - The value for which a readonly proxy shall be created.
*/
export const toReadonly = <T extends unknown>(value: T): T =>
isObject(value) ? readonly(value as Record<any, any>) : value

View File

@ -69,11 +69,24 @@ export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
}
}
/**
* Checks if a value is a ref object.
*
* @param r - The value to inspect.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isref}
*/
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}
/**
* Takes an inner value and returns a reactive and mutable ref object, which
* has a single property `.value` that points to the inner value.
*
* @param value - The object to wrap in the ref.
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
*/
export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
@ -87,6 +100,23 @@ declare const ShallowRefMarker: unique symbol
export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
/**
* Shallow version of {@link ref()}.
*
* @example
* ```js
* const state = shallowRef({ count: 1 })
*
* // does NOT trigger change
* state.value.count = 2
*
* // does trigger change
* state.value = { count: 2 }
* ```
*
* @param value - The "inner value" for the shallow ref.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
*/
export function shallowRef<T extends object>(
value: T
): T extends Ref ? T : ShallowRef<T>
@ -132,10 +162,51 @@ class RefImpl<T> {
}
}
/**
* Force trigger effects that depends on a shallow ref. This is typically used
* after making deep mutations to the inner value of a shallow ref.
*
* @example
* ```js
* const shallow = shallowRef({
* greet: 'Hello, world'
* })
*
* // Logs "Hello, world" once for the first run-through
* watchEffect(() => {
* console.log(shallow.value.greet)
* })
*
* // This won't trigger the effect because the ref is shallow
* shallow.value.greet = 'Hello, universe'
*
* // Logs "Hello, universe"
* triggerRef(shallow)
* ```
*
* @param ref - The ref whose tied effects shall be executed.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
*/
export function triggerRef(ref: Ref) {
triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}
/**
* Returns the inner value if the argument is a ref, otherwise return the
* argument itself. This is a sugar function for
* `val = isRef(val) ? val.value : val`.
*
* @example
* ```js
* function useFoo(x: number | Ref<number>) {
* const unwrapped = unref(x)
* // unwrapped is guaranteed to be number now
* }
* ```
*
* @param ref - Ref or plain value to be converted into the plain value.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#unref}
*/
export function unref<T>(ref: T | Ref<T>): T {
return isRef(ref) ? (ref.value as any) : ref
}
@ -153,6 +224,16 @@ const shallowUnwrapHandlers: ProxyHandler<any> = {
}
}
/**
* Returns a reactive proxy for the given object.
*
* If the object already is reactive, it's returned as-is. If not, a new
* reactive proxy is created. Direct child properties that are refs are properly
* handled, as well.
*
* @param objectWithRefs - Either an already-reactive object or a simple object
* that contains refs.
*/
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
@ -195,6 +276,13 @@ class CustomRefImpl<T> {
}
}
/**
* Creates a customized ref with explicit control over its dependency tracking
* and updates triggering.
*
* @param factory - The function that receives the `track` and `trigger` callbacks.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#customref}
*/
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}
@ -202,6 +290,15 @@ export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
export type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]>
}
/**
* Converts a reactive object to a plain object where each property of the
* resulting object is a ref pointing to the corresponding property of the
* original object. Each individual ref is created using {@link toRef()}.
*
* @param object - Reactive object to be made into an object of linked refs.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
*/
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
@ -238,17 +335,42 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
/**
* Can be used to create a ref for a property on a source reactive object. The
* created ref is synced with its source property: mutating the source property
* will update the ref, and vice-versa.
*
* @example
* ```js
* const state = reactive({
* foo: 1,
* bar: 2
* })
*
* const fooRef = toRef(state, 'foo')
*
* // mutating the ref updates the original
* fooRef.value++
* console.log(state.foo) // 2
*
* // mutating the original also updates the ref
* state.foo++
* console.log(fooRef.value) // 3
* ```
*
* @param object - The reactive object containing the desired property.
* @param key - Name of the property in the reactive object.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
*/
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,