-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
/
Copy pathref.ts
522 lines (475 loc) · 13.5 KB
/
ref.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
import {
type IfAny,
hasChanged,
isArray,
isFunction,
isObject,
} from '@vue/shared'
import { Dep, getDepFromReactive } from './dep'
import {
type Builtin,
type ShallowReactiveMarker,
isProxy,
isReactive,
isReadonly,
isShallow,
toRaw,
toReactive,
} from './reactive'
import type { ComputedRef, WritableComputedRef } from './computed'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import { warn } from './warning'
declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol
export interface Ref<T = any, S = T> {
get value(): T
set value(_: S)
/**
* Type differentiator only.
* We need this to be in public d.ts but don't want it to show up in IDE
* autocomplete, so we use a private Symbol instead.
*/
[RefSymbol]: true
}
/**
* 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[ReactiveFlags.IS_REF] === true : false
}
/**
* 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>(
value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
declare const ShallowRefMarker: unique symbol
export type ShallowRef<T = any, S = T> = Ref<T, S> & {
[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>(
value: T,
): Ref extends T
? T extends Ref
? IfAny<T, ShallowRef<T>, T>
: ShallowRef<T>
: ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
/**
* @internal
*/
class RefImpl<T = any> {
_value: T
private _rawValue: T
dep: Dep = new Dep()
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
constructor(value: T, isShallow: boolean) {
this._rawValue = isShallow ? value : toRaw(value)
this._value = isShallow ? value : toReactive(value)
this[ReactiveFlags.IS_SHALLOW] = isShallow
}
get value() {
if (__DEV__) {
this.dep.track({
target: this,
type: TrackOpTypes.GET,
key: 'value',
})
} else {
this.dep.track()
}
return this._value
}
set value(newValue) {
const oldValue = this._rawValue
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue
this._value = useDirectValue ? newValue : toReactive(newValue)
if (__DEV__) {
this.dep.trigger({
target: this,
type: TriggerOpTypes.SET,
key: 'value',
newValue,
oldValue,
})
} else {
this.dep.trigger()
}
}
}
}
/**
* 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): void {
// ref may be an instance of ObjectRefImpl
if ((ref as unknown as RefImpl).dep) {
if (__DEV__) {
;(ref as unknown as RefImpl).dep.trigger({
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: (ref as unknown as RefImpl)._value,
})
} else {
;(ref as unknown as RefImpl).dep.trigger()
}
}
}
export type MaybeRef<T = any> =
| T
| Ref<T>
| ShallowRef<T>
| WritableComputedRef<T>
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T)
/**
* 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: MaybeRef<T> | ComputedRef<T>): T {
return isRef(ref) ? ref.value : ref
}
/**
* Normalizes values / refs / getters to values.
* This is similar to {@link unref}, except that it also normalizes getters.
* If the argument is a getter, it will be invoked and its return value will
* be returned.
*
* @example
* ```js
* toValue(1) // 1
* toValue(ref(1)) // 1
* toValue(() => 1) // 1
* ```
*
* @param source - A getter, an existing ref, or a non-function value.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
*/
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
return isFunction(source) ? source() : unref(source)
}
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) =>
key === ReactiveFlags.RAW
? target
: unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
const oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
},
}
/**
* Returns a proxy for the given object that shallowly unwraps properties that
* are refs. If the object already is reactive, it's returned as-is. If not, a
* new reactive proxy is created.
*
* @param objectWithRefs - Either an already-reactive object or a simple object
* that contains refs.
*/
export function proxyRefs<T extends object>(
objectWithRefs: T,
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void,
) => {
get: () => T
set: (value: T) => void
}
class CustomRefImpl<T> {
public dep: Dep
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly [ReactiveFlags.IS_REF] = true
public _value: T = undefined!
constructor(factory: CustomRefFactory<T>) {
const dep = (this.dep = new Dep())
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep))
this._get = get
this._set = set
}
get value() {
return (this._value = this._get())
}
set value(newVal) {
this._set(newVal)
}
}
/**
* 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
}
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)) {
warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = propertyToRef(object, key)
}
return ret
}
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly [ReactiveFlags.IS_REF] = true
public _value: T[K] = undefined!
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K],
) {}
get value() {
const val = this._object[this._key]
return (this._value = val === undefined ? this._defaultValue! : val)
}
set value(newVal) {
this._object[this._key] = newVal
}
get dep(): Dep | undefined {
return getDepFromReactive(toRaw(this._object), this._key)
}
}
class GetterRefImpl<T> {
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_READONLY] = true
public _value: T = undefined!
constructor(private readonly _getter: () => T) {}
get value() {
return (this._value = this._getter())
}
}
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
/**
* Used to normalize values / refs / getters into refs.
*
* @example
* ```js
* // returns existing refs as-is
* toRef(existingRef)
*
* // creates a ref that calls the getter on .value access
* toRef(() => props.foo)
*
* // creates normal refs from non-function values
* // equivalent to ref(1)
* toRef(1)
* ```
*
* Can also 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 source - A getter, an existing ref, a non-function value, or a
* reactive object to create a property ref from.
* @param [key] - (optional) Name of the property in the reactive object.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
*/
export function toRef<T>(
value: T,
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
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(
source: Record<string, any> | MaybeRef,
key?: string,
defaultValue?: unknown,
): Ref {
if (isRef(source)) {
return source
} else if (isFunction(source)) {
return new GetterRefImpl(source) as any
} else if (isObject(source) && arguments.length > 1) {
return propertyToRef(source, key!, defaultValue)
} else {
return ref(source)
}
}
function propertyToRef(
source: Record<string, any>,
key: string,
defaultValue?: unknown,
) {
const val = source[key]
return isRef(val)
? val
: (new ObjectRefImpl(source, key, defaultValue) as any)
}
/**
* This is a special exported interface for other packages to declare
* additional types that should bail out for ref unwrapping. For example
* \@vue/runtime-dom can declare it like so in its d.ts:
*
* ``` ts
* declare module '@vue/reactivity' {
* export interface RefUnwrapBailTypes {
* runtimeDOMBailTypes: Node | Window
* }
* }
* ```
*/
export interface RefUnwrapBailTypes {}
export type ShallowUnwrapRef<T> = {
[K in keyof T]: DistributeRef<T[K]>
}
type DistributeRef<T> = T extends Ref<infer V, unknown> ? V : T
export type UnwrapRef<T> =
T extends ShallowRef<infer V, unknown>
? V
: T extends Ref<infer V, unknown>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
export type UnwrapRefSimple<T> = T extends
| Builtin
| Ref
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
| { [RawSymbol]?: true }
? T
: T extends Map<infer K, infer V>
? Map<K, UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Map<any, any>>>
: T extends WeakMap<infer K, infer V>
? WeakMap<K, UnwrapRefSimple<V>> &
UnwrapRef<Omit<T, keyof WeakMap<any, any>>>
: T extends Set<infer V>
? Set<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Set<any>>>
: T extends WeakSet<infer V>
? WeakSet<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof WeakSet<any>>>
: T extends ReadonlyArray<any>
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
: T extends object & { [ShallowReactiveMarker]?: never }
? {
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
}
: T