apiSetupHelpers.ts 13 KB

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