apiSetupHelpers.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. import {
  2. isArray,
  3. isPromise,
  4. isFunction,
  5. Prettify,
  6. UnionToIntersection,
  7. extend
  8. } from '@vue/shared'
  9. import {
  10. getCurrentInstance,
  11. setCurrentInstance,
  12. SetupContext,
  13. createSetupContext,
  14. unsetCurrentInstance
  15. } from './component'
  16. import { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
  17. import {
  18. ComponentOptionsMixin,
  19. ComponentOptionsWithoutProps,
  20. ComputedOptions,
  21. MethodOptions
  22. } from './componentOptions'
  23. import {
  24. ComponentPropsOptions,
  25. ComponentObjectPropsOptions,
  26. ExtractPropTypes,
  27. NormalizedProps,
  28. PropOptions
  29. } from './componentProps'
  30. import { warn } from './warning'
  31. import { SlotsType, StrictUnwrapSlotsType } from './componentSlots'
  32. import { Ref, ref } from '@vue/reactivity'
  33. import { watch } from './apiWatch'
  34. // dev only
  35. const warnRuntimeUsage = (method: string) =>
  36. warn(
  37. `${method}() is a compiler-hint helper that is only usable inside ` +
  38. `<script setup> of a single file component. Its arguments should be ` +
  39. `compiled away and passing it at runtime has no effect.`
  40. )
  41. /**
  42. * Vue `<script setup>` compiler macro for declaring component props. The
  43. * expected argument is the same as the component `props` option.
  44. *
  45. * Example runtime declaration:
  46. * ```js
  47. * // using Array syntax
  48. * const props = defineProps(['foo', 'bar'])
  49. * // using Object syntax
  50. * const props = defineProps({
  51. * foo: String,
  52. * bar: {
  53. * type: Number,
  54. * required: true
  55. * }
  56. * })
  57. * ```
  58. *
  59. * Equivalent type-based declaration:
  60. * ```ts
  61. * // will be compiled into equivalent runtime declarations
  62. * const props = defineProps<{
  63. * foo?: string
  64. * bar: number
  65. * }>()
  66. *
  67. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
  68. * ```
  69. *
  70. * This is only usable inside `<script setup>`, is compiled away in the
  71. * output and should **not** be actually called at runtime.
  72. */
  73. // overload 1: runtime props w/ array
  74. export function defineProps<PropNames extends string = string>(
  75. props: PropNames[]
  76. ): Prettify<Readonly<{ [key in PropNames]?: any }>>
  77. // overload 2: runtime props w/ object
  78. export function defineProps<
  79. PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions
  80. >(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
  81. // overload 3: typed-based declaration
  82. export function defineProps<TypeProps>(): DefineProps<TypeProps>
  83. // implementation
  84. export function defineProps() {
  85. if (__DEV__) {
  86. warnRuntimeUsage(`defineProps`)
  87. }
  88. return null as any
  89. }
  90. type DefineProps<T> = Readonly<T> & {
  91. readonly [K in BooleanKey<T>]-?: boolean
  92. }
  93. type BooleanKey<T, K extends keyof T = keyof T> = K extends any
  94. ? [T[K]] extends [boolean | undefined]
  95. ? K
  96. : never
  97. : never
  98. /**
  99. * Vue `<script setup>` compiler macro for declaring a component's emitted
  100. * events. The expected argument is the same as the component `emits` option.
  101. *
  102. * Example runtime declaration:
  103. * ```js
  104. * const emit = defineEmits(['change', 'update'])
  105. * ```
  106. *
  107. * Example type-based declaration:
  108. * ```ts
  109. * const emit = defineEmits<{
  110. * (event: 'change'): void
  111. * (event: 'update', id: number): void
  112. * }>()
  113. *
  114. * emit('change')
  115. * emit('update', 1)
  116. * ```
  117. *
  118. * This is only usable inside `<script setup>`, is compiled away in the
  119. * output and should **not** be actually called at runtime.
  120. *
  121. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
  122. */
  123. // overload 1: runtime emits w/ array
  124. export function defineEmits<EE extends string = string>(
  125. emitOptions: EE[]
  126. ): EmitFn<EE[]>
  127. export function defineEmits<E extends EmitsOptions = EmitsOptions>(
  128. emitOptions: E
  129. ): EmitFn<E>
  130. export function defineEmits<
  131. T extends ((...args: any[]) => any) | Record<string, any[]>
  132. >(): T extends (...args: any[]) => any ? T : ShortEmits<T>
  133. // implementation
  134. export function defineEmits() {
  135. if (__DEV__) {
  136. warnRuntimeUsage(`defineEmits`)
  137. }
  138. return null as any
  139. }
  140. type RecordToUnion<T extends Record<string, any>> = T[keyof T]
  141. type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
  142. RecordToUnion<{
  143. [K in keyof T]: (evt: K, ...args: T[K]) => void
  144. }>
  145. >
  146. /**
  147. * Vue `<script setup>` compiler macro for declaring a component's exposed
  148. * instance properties when it is accessed by a parent component via template
  149. * refs.
  150. *
  151. * `<script setup>` components are closed by default - i.e. variables inside
  152. * the `<script setup>` scope is not exposed to parent unless explicitly exposed
  153. * via `defineExpose`.
  154. *
  155. * This is only usable inside `<script setup>`, is compiled away in the
  156. * output and should **not** be actually called at runtime.
  157. *
  158. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineexpose}
  159. */
  160. export function defineExpose<
  161. Exposed extends Record<string, any> = Record<string, any>
  162. >(exposed?: Exposed) {
  163. if (__DEV__) {
  164. warnRuntimeUsage(`defineExpose`)
  165. }
  166. }
  167. /**
  168. * Vue `<script setup>` compiler macro for declaring a component's additional
  169. * options. This should be used only for options that cannot be expressed via
  170. * Composition API - e.g. `inhertiAttrs`.
  171. *
  172. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineoptions}
  173. */
  174. export function defineOptions<
  175. RawBindings = {},
  176. D = {},
  177. C extends ComputedOptions = {},
  178. M extends MethodOptions = {},
  179. Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  180. Extends extends ComponentOptionsMixin = ComponentOptionsMixin
  181. >(
  182. options?: ComponentOptionsWithoutProps<
  183. {},
  184. RawBindings,
  185. D,
  186. C,
  187. M,
  188. Mixin,
  189. Extends
  190. > & { emits?: undefined; expose?: undefined; slots?: undefined }
  191. ): void {
  192. if (__DEV__) {
  193. warnRuntimeUsage(`defineOptions`)
  194. }
  195. }
  196. export function defineSlots<
  197. S extends Record<string, any> = Record<string, any>
  198. >(): StrictUnwrapSlotsType<SlotsType<S>> {
  199. if (__DEV__) {
  200. warnRuntimeUsage(`defineSlots`)
  201. }
  202. return null as any
  203. }
  204. /**
  205. * (**Experimental**) Vue `<script setup>` compiler macro for declaring a
  206. * two-way binding prop that can be consumed via `v-model` from the parent
  207. * component. This will declare a prop with the same name and a corresponding
  208. * `update:propName` event.
  209. *
  210. * If the first argument is a string, it will be used as the prop name;
  211. * Otherwise the prop name will default to "modelValue". In both cases, you
  212. * can also pass an additional object which will be used as the prop's options.
  213. *
  214. * The options object can also specify an additional option, `local`. When set
  215. * to `true`, the ref can be locally mutated even if the parent did not pass
  216. * the matching `v-model`.
  217. *
  218. * @example
  219. * ```ts
  220. * // default model (consumed via `v-model`)
  221. * const modelValue = defineModel<string>()
  222. * modelValue.value = "hello"
  223. *
  224. * // default model with options
  225. * const modelValue = defineModel<stirng>({ required: true })
  226. *
  227. * // with specified name (consumed via `v-model:count`)
  228. * const count = defineModel<number>('count')
  229. * count.value++
  230. *
  231. * // with specified name and default value
  232. * const count = defineModel<number>('count', { default: 0 })
  233. *
  234. * // local mutable model, can be mutated locally
  235. * // even if the parent did not pass the matching `v-model`.
  236. * const count = defineModel<number>('count', { local: true, default: 0 })
  237. * ```
  238. */
  239. export function defineModel<T>(
  240. options: { required: true } & PropOptions<T> & DefineModelOptions
  241. ): Ref<T>
  242. export function defineModel<T>(
  243. options: { default: any } & PropOptions<T> & DefineModelOptions
  244. ): Ref<T>
  245. export function defineModel<T>(
  246. options?: PropOptions<T> & DefineModelOptions
  247. ): Ref<T | undefined>
  248. export function defineModel<T>(
  249. name: string,
  250. options: { required: true } & PropOptions<T> & DefineModelOptions
  251. ): Ref<T>
  252. export function defineModel<T>(
  253. name: string,
  254. options: { default: any } & PropOptions<T> & DefineModelOptions
  255. ): Ref<T>
  256. export function defineModel<T>(
  257. name: string,
  258. options?: PropOptions<T> & DefineModelOptions
  259. ): Ref<T | undefined>
  260. export function defineModel(): any {
  261. if (__DEV__) {
  262. warnRuntimeUsage('defineModel')
  263. }
  264. }
  265. interface DefineModelOptions {
  266. local?: boolean
  267. }
  268. type NotUndefined<T> = T extends undefined ? never : T
  269. type InferDefaults<T> = {
  270. [K in keyof T]?: InferDefault<T, NotUndefined<T[K]>>
  271. }
  272. type InferDefault<P, T> = T extends
  273. | null
  274. | number
  275. | string
  276. | boolean
  277. | symbol
  278. | Function
  279. ? T | ((props: P) => T)
  280. : (props: P) => T
  281. type PropsWithDefaults<Base, Defaults> = Base & {
  282. [K in keyof Defaults]: K extends keyof Base
  283. ? Defaults[K] extends undefined
  284. ? Base[K]
  285. : NotUndefined<Base[K]>
  286. : never
  287. }
  288. /**
  289. * Vue `<script setup>` compiler macro for providing props default values when
  290. * using type-based `defineProps` declaration.
  291. *
  292. * Example usage:
  293. * ```ts
  294. * withDefaults(defineProps<{
  295. * size?: number
  296. * labels?: string[]
  297. * }>(), {
  298. * size: 3,
  299. * labels: () => ['default label']
  300. * })
  301. * ```
  302. *
  303. * This is only usable inside `<script setup>`, is compiled away in the output
  304. * and should **not** be actually called at runtime.
  305. *
  306. * @see {@link https://vuejs.org/guide/typescript/composition-api.html#typing-component-props}
  307. */
  308. export function withDefaults<Props, Defaults extends InferDefaults<Props>>(
  309. props: Props,
  310. defaults: Defaults
  311. ): PropsWithDefaults<Props, Defaults> {
  312. if (__DEV__) {
  313. warnRuntimeUsage(`withDefaults`)
  314. }
  315. return null as any
  316. }
  317. export function useSlots(): SetupContext['slots'] {
  318. return getContext().slots
  319. }
  320. export function useAttrs(): SetupContext['attrs'] {
  321. return getContext().attrs
  322. }
  323. export function useModel<T extends Record<string, any>, K extends keyof T>(
  324. props: T,
  325. name: K,
  326. options?: { local?: boolean }
  327. ): Ref<T[K]>
  328. export function useModel(
  329. props: Record<string, any>,
  330. name: string,
  331. options?: { local?: boolean }
  332. ): Ref {
  333. const i = getCurrentInstance()!
  334. if (__DEV__ && !i) {
  335. warn(`useModel() called without active instance.`)
  336. return ref() as any
  337. }
  338. if (__DEV__ && !(i.propsOptions[0] as NormalizedProps)[name]) {
  339. warn(`useModel() called with prop "${name}" which is not declared.`)
  340. return ref() as any
  341. }
  342. if (options && options.local) {
  343. const proxy = ref<any>(props[name])
  344. watch(
  345. () => props[name],
  346. v => (proxy.value = v)
  347. )
  348. watch(proxy, value => {
  349. if (value !== props[name]) {
  350. i.emit(`update:${name}`, value)
  351. }
  352. })
  353. return proxy
  354. } else {
  355. return {
  356. __v_isRef: true,
  357. get value() {
  358. return props[name]
  359. },
  360. set value(value) {
  361. i.emit(`update:${name}`, value)
  362. }
  363. } as any
  364. }
  365. }
  366. function getContext(): SetupContext {
  367. const i = getCurrentInstance()!
  368. if (__DEV__ && !i) {
  369. warn(`useContext() called without active instance.`)
  370. }
  371. return i.setupContext || (i.setupContext = createSetupContext(i))
  372. }
  373. /**
  374. * @internal
  375. */
  376. export function normalizePropsOrEmits(
  377. props: ComponentPropsOptions | EmitsOptions
  378. ) {
  379. return isArray(props)
  380. ? props.reduce(
  381. (normalized, p) => ((normalized[p] = null), normalized),
  382. {} as ComponentObjectPropsOptions | ObjectEmitsOptions
  383. )
  384. : props
  385. }
  386. /**
  387. * Runtime helper for merging default declarations. Imported by compiled code
  388. * only.
  389. * @internal
  390. */
  391. export function mergeDefaults(
  392. raw: ComponentPropsOptions,
  393. defaults: Record<string, any>
  394. ): ComponentObjectPropsOptions {
  395. const props = normalizePropsOrEmits(raw)
  396. for (const key in defaults) {
  397. if (key.startsWith('__skip')) continue
  398. let opt = props[key]
  399. if (opt) {
  400. if (isArray(opt) || isFunction(opt)) {
  401. opt = props[key] = { type: opt, default: defaults[key] }
  402. } else {
  403. opt.default = defaults[key]
  404. }
  405. } else if (opt === null) {
  406. opt = props[key] = { default: defaults[key] }
  407. } else if (__DEV__) {
  408. warn(`props default key "${key}" has no corresponding declaration.`)
  409. }
  410. if (opt && defaults[`__skip_${key}`]) {
  411. opt.skipFactory = true
  412. }
  413. }
  414. return props
  415. }
  416. /**
  417. * Runtime helper for merging model declarations.
  418. * Imported by compiled code only.
  419. * @internal
  420. */
  421. export function mergeModels(
  422. a: ComponentPropsOptions | EmitsOptions,
  423. b: ComponentPropsOptions | EmitsOptions
  424. ) {
  425. if (!a || !b) return a || b
  426. if (isArray(a) && isArray(b)) return a.concat(b)
  427. return extend({}, normalizePropsOrEmits(a), normalizePropsOrEmits(b))
  428. }
  429. /**
  430. * Used to create a proxy for the rest element when destructuring props with
  431. * defineProps().
  432. * @internal
  433. */
  434. export function createPropsRestProxy(
  435. props: any,
  436. excludedKeys: string[]
  437. ): Record<string, any> {
  438. const ret: Record<string, any> = {}
  439. for (const key in props) {
  440. if (!excludedKeys.includes(key)) {
  441. Object.defineProperty(ret, key, {
  442. enumerable: true,
  443. get: () => props[key]
  444. })
  445. }
  446. }
  447. return ret
  448. }
  449. /**
  450. * `<script setup>` helper for persisting the current instance context over
  451. * async/await flows.
  452. *
  453. * `@vue/compiler-sfc` converts the following:
  454. *
  455. * ```ts
  456. * const x = await foo()
  457. * ```
  458. *
  459. * into:
  460. *
  461. * ```ts
  462. * let __temp, __restore
  463. * const x = (([__temp, __restore] = withAsyncContext(() => foo())),__temp=await __temp,__restore(),__temp)
  464. * ```
  465. * @internal
  466. */
  467. export function withAsyncContext(getAwaitable: () => any) {
  468. const ctx = getCurrentInstance()!
  469. if (__DEV__ && !ctx) {
  470. warn(
  471. `withAsyncContext called without active current instance. ` +
  472. `This is likely a bug.`
  473. )
  474. }
  475. let awaitable = getAwaitable()
  476. unsetCurrentInstance()
  477. if (isPromise(awaitable)) {
  478. awaitable = awaitable.catch(e => {
  479. setCurrentInstance(ctx)
  480. throw e
  481. })
  482. }
  483. return [awaitable, () => setCurrentInstance(ctx)]
  484. }