apiSetupHelpers.ts 13 KB

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