ref.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import {
  2. activeEffect,
  3. shouldTrack,
  4. trackEffects,
  5. triggerEffects
  6. } from './effect'
  7. import { TrackOpTypes, TriggerOpTypes } from './operations'
  8. import { isArray, hasChanged, IfAny } from '@vue/shared'
  9. import { isProxy, toRaw, isReactive, toReactive } from './reactive'
  10. import type { ShallowReactiveMarker } from './reactive'
  11. import { CollectionTypes } from './collectionHandlers'
  12. import { createDep, Dep } from './dep'
  13. declare const RefSymbol: unique symbol
  14. export declare const RawSymbol: unique symbol
  15. export interface Ref<T = any> {
  16. value: T
  17. /**
  18. * Type differentiator only.
  19. * We need this to be in public d.ts but don't want it to show up in IDE
  20. * autocomplete, so we use a private Symbol instead.
  21. */
  22. [RefSymbol]: true
  23. }
  24. type RefBase<T> = {
  25. dep?: Dep
  26. value: T
  27. }
  28. export function trackRefValue(ref: RefBase<any>) {
  29. if (shouldTrack && activeEffect) {
  30. ref = toRaw(ref)
  31. if (__DEV__) {
  32. trackEffects(ref.dep || (ref.dep = createDep()), {
  33. target: ref,
  34. type: TrackOpTypes.GET,
  35. key: 'value'
  36. })
  37. } else {
  38. trackEffects(ref.dep || (ref.dep = createDep()))
  39. }
  40. }
  41. }
  42. export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  43. ref = toRaw(ref)
  44. if (ref.dep) {
  45. if (__DEV__) {
  46. triggerEffects(ref.dep, {
  47. target: ref,
  48. type: TriggerOpTypes.SET,
  49. key: 'value',
  50. newValue: newVal
  51. })
  52. } else {
  53. triggerEffects(ref.dep)
  54. }
  55. }
  56. }
  57. export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
  58. export function isRef(r: any): r is Ref {
  59. return !!(r && r.__v_isRef === true)
  60. }
  61. export function ref<T extends object>(
  62. value: T
  63. ): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
  64. export function ref<T>(value: T): Ref<UnwrapRef<T>>
  65. export function ref<T = any>(): Ref<T | undefined>
  66. export function ref(value?: unknown) {
  67. return createRef(value, false)
  68. }
  69. declare const ShallowRefMarker: unique symbol
  70. export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
  71. export function shallowRef<T extends object>(
  72. value: T
  73. ): T extends Ref ? T : ShallowRef<T>
  74. export function shallowRef<T>(value: T): ShallowRef<T>
  75. export function shallowRef<T = any>(): ShallowRef<T | undefined>
  76. export function shallowRef(value?: unknown) {
  77. return createRef(value, true)
  78. }
  79. function createRef(rawValue: unknown, shallow: boolean) {
  80. if (isRef(rawValue)) {
  81. return rawValue
  82. }
  83. return new RefImpl(rawValue, shallow)
  84. }
  85. class RefImpl<T> {
  86. private _value: T
  87. private _rawValue: T
  88. public dep?: Dep = undefined
  89. public readonly __v_isRef = true
  90. constructor(value: T, public readonly __v_isShallow: boolean) {
  91. this._rawValue = __v_isShallow ? value : toRaw(value)
  92. this._value = __v_isShallow ? value : toReactive(value)
  93. }
  94. get value() {
  95. trackRefValue(this)
  96. return this._value
  97. }
  98. set value(newVal) {
  99. newVal = this.__v_isShallow ? newVal : toRaw(newVal)
  100. if (hasChanged(newVal, this._rawValue)) {
  101. this._rawValue = newVal
  102. this._value = this.__v_isShallow ? newVal : toReactive(newVal)
  103. triggerRefValue(this, newVal)
  104. }
  105. }
  106. }
  107. export function triggerRef(ref: Ref) {
  108. triggerRefValue(ref, __DEV__ ? ref.value : void 0)
  109. }
  110. export function unref<T>(ref: T | Ref<T>): T {
  111. return isRef(ref) ? (ref.value as any) : ref
  112. }
  113. const shallowUnwrapHandlers: ProxyHandler<any> = {
  114. get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  115. set: (target, key, value, receiver) => {
  116. const oldValue = target[key]
  117. if (isRef(oldValue) && !isRef(value)) {
  118. oldValue.value = value
  119. return true
  120. } else {
  121. return Reflect.set(target, key, value, receiver)
  122. }
  123. }
  124. }
  125. export function proxyRefs<T extends object>(
  126. objectWithRefs: T
  127. ): ShallowUnwrapRef<T> {
  128. return isReactive(objectWithRefs)
  129. ? objectWithRefs
  130. : new Proxy(objectWithRefs, shallowUnwrapHandlers)
  131. }
  132. export type CustomRefFactory<T> = (
  133. track: () => void,
  134. trigger: () => void
  135. ) => {
  136. get: () => T
  137. set: (value: T) => void
  138. }
  139. class CustomRefImpl<T> {
  140. public dep?: Dep = undefined
  141. private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  142. private readonly _set: ReturnType<CustomRefFactory<T>>['set']
  143. public readonly __v_isRef = true
  144. constructor(factory: CustomRefFactory<T>) {
  145. const { get, set } = factory(
  146. () => trackRefValue(this),
  147. () => triggerRefValue(this)
  148. )
  149. this._get = get
  150. this._set = set
  151. }
  152. get value() {
  153. return this._get()
  154. }
  155. set value(newVal) {
  156. this._set(newVal)
  157. }
  158. }
  159. export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  160. return new CustomRefImpl(factory) as any
  161. }
  162. export type ToRefs<T = any> = {
  163. [K in keyof T]: ToRef<T[K]>
  164. }
  165. export function toRefs<T extends object>(object: T): ToRefs<T> {
  166. if (__DEV__ && !isProxy(object)) {
  167. console.warn(`toRefs() expects a reactive object but received a plain one.`)
  168. }
  169. const ret: any = isArray(object) ? new Array(object.length) : {}
  170. for (const key in object) {
  171. ret[key] = toRef(object, key)
  172. }
  173. return ret
  174. }
  175. class ObjectRefImpl<T extends object, K extends keyof T> {
  176. public readonly __v_isRef = true
  177. constructor(
  178. private readonly _object: T,
  179. private readonly _key: K,
  180. private readonly _defaultValue?: T[K]
  181. ) {}
  182. get value() {
  183. const val = this._object[this._key]
  184. return val === undefined ? (this._defaultValue as T[K]) : val
  185. }
  186. set value(newVal) {
  187. this._object[this._key] = newVal
  188. }
  189. }
  190. export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
  191. export function toRef<T extends object, K extends keyof T>(
  192. object: T,
  193. key: K
  194. ): ToRef<T[K]>
  195. export function toRef<T extends object, K extends keyof T>(
  196. object: T,
  197. key: K,
  198. defaultValue: T[K]
  199. ): ToRef<Exclude<T[K], undefined>>
  200. export function toRef<T extends object, K extends keyof T>(
  201. object: T,
  202. key: K,
  203. defaultValue?: T[K]
  204. ): ToRef<T[K]> {
  205. const val = object[key]
  206. return isRef(val)
  207. ? val
  208. : (new ObjectRefImpl(object, key, defaultValue) as any)
  209. }
  210. // corner case when use narrows type
  211. // Ex. type RelativePath = string & { __brand: unknown }
  212. // RelativePath extends object -> true
  213. type BaseTypes = string | number | boolean
  214. /**
  215. * This is a special exported interface for other packages to declare
  216. * additional types that should bail out for ref unwrapping. For example
  217. * \@vue/runtime-dom can declare it like so in its d.ts:
  218. *
  219. * ``` ts
  220. * declare module '@vue/reactivity' {
  221. * export interface RefUnwrapBailTypes {
  222. * runtimeDOMBailTypes: Node | Window
  223. * }
  224. * }
  225. * ```
  226. *
  227. * Note that api-extractor somehow refuses to include `declare module`
  228. * augmentations in its generated d.ts, so we have to manually append them
  229. * to the final generated d.ts in our build process.
  230. */
  231. export interface RefUnwrapBailTypes {}
  232. export type ShallowUnwrapRef<T> = {
  233. [K in keyof T]: T[K] extends Ref<infer V>
  234. ? V
  235. : // if `V` is `unknown` that means it does not extend `Ref` and is undefined
  236. T[K] extends Ref<infer V> | undefined
  237. ? unknown extends V
  238. ? undefined
  239. : V | undefined
  240. : T[K]
  241. }
  242. export type UnwrapRef<T> = T extends ShallowRef<infer V>
  243. ? V
  244. : T extends Ref<infer V>
  245. ? UnwrapRefSimple<V>
  246. : UnwrapRefSimple<T>
  247. export type UnwrapRefSimple<T> = T extends
  248. | Function
  249. | CollectionTypes
  250. | BaseTypes
  251. | Ref
  252. | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
  253. | { [RawSymbol]?: true }
  254. ? T
  255. : T extends Array<any>
  256. ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
  257. : T extends object & { [ShallowReactiveMarker]?: never }
  258. ? {
  259. [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
  260. }
  261. : T