componentOptions.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. import {
  2. ComponentInternalInstance,
  3. Data,
  4. SetupContext,
  5. SFCInternalOptions,
  6. PublicAPIComponent,
  7. Component
  8. } from './component'
  9. import {
  10. isFunction,
  11. extend,
  12. isString,
  13. isObject,
  14. isArray,
  15. EMPTY_OBJ,
  16. NOOP,
  17. hasOwn,
  18. isPromise
  19. } from '@vue/shared'
  20. import { computed } from './apiComputed'
  21. import { watch, WatchOptions, WatchCallback } from './apiWatch'
  22. import { provide, inject } from './apiInject'
  23. import {
  24. onBeforeMount,
  25. onMounted,
  26. onBeforeUpdate,
  27. onUpdated,
  28. onErrorCaptured,
  29. onRenderTracked,
  30. onBeforeUnmount,
  31. onUnmounted,
  32. onActivated,
  33. onDeactivated,
  34. onRenderTriggered,
  35. DebuggerHook,
  36. ErrorCapturedHook
  37. } from './apiLifecycle'
  38. import {
  39. reactive,
  40. ComputedGetter,
  41. WritableComputedOptions,
  42. toRaw
  43. } from '@vue/reactivity'
  44. import {
  45. ComponentObjectPropsOptions,
  46. ExtractPropTypes,
  47. normalizePropsOptions
  48. } from './componentProps'
  49. import { EmitsOptions } from './componentEmits'
  50. import { Directive } from './directives'
  51. import { ComponentPublicInstance } from './componentProxy'
  52. import { warn } from './warning'
  53. import { VNodeChild } from './vnode'
  54. /**
  55. * Interface for declaring custom options.
  56. *
  57. * @example
  58. * ```ts
  59. * declare module '@vue/runtime-core' {
  60. * interface ComponentCustomOptions {
  61. * beforeRouteUpdate?(
  62. * to: Route,
  63. * from: Route,
  64. * next: () => void
  65. * ): void
  66. * }
  67. * }
  68. * ```
  69. */
  70. export interface ComponentCustomOptions {}
  71. export type RenderFunction = () => VNodeChild
  72. export interface ComponentOptionsBase<
  73. Props,
  74. RawBindings,
  75. D,
  76. C extends ComputedOptions,
  77. M extends MethodOptions,
  78. E extends EmitsOptions,
  79. EE extends string = string
  80. >
  81. extends LegacyOptions<Props, D, C, M>,
  82. SFCInternalOptions,
  83. ComponentCustomOptions {
  84. setup?: (
  85. this: void,
  86. props: Props,
  87. ctx: SetupContext<E>
  88. ) => RawBindings | RenderFunction | void
  89. name?: string
  90. template?: string | object // can be a direct DOM node
  91. // Note: we are intentionally using the signature-less `Function` type here
  92. // since any type with signature will cause the whole inference to fail when
  93. // the return expression contains reference to `this`.
  94. // Luckily `render()` doesn't need any arguments nor does it care about return
  95. // type.
  96. render?: Function
  97. components?: Record<string, PublicAPIComponent>
  98. directives?: Record<string, Directive>
  99. inheritAttrs?: boolean
  100. emits?: E | EE[]
  101. // Internal ------------------------------------------------------------------
  102. /**
  103. * SSR only. This is produced by compiler-ssr and attached in compiler-sfc
  104. * not user facing, so the typing is lax and for test only.
  105. *
  106. * @internal
  107. */
  108. ssrRender?: (
  109. ctx: any,
  110. push: (item: any) => void,
  111. parentInstance: ComponentInternalInstance
  112. ) => void
  113. /**
  114. * marker for AsyncComponentWrapper
  115. * @internal
  116. */
  117. __asyncLoader?: () => Promise<Component>
  118. /**
  119. * cache for merged $options
  120. * @internal
  121. */
  122. __merged?: ComponentOptions
  123. // Type differentiators ------------------------------------------------------
  124. // Note these are internal but need to be exposed in d.ts for type inference
  125. // to work!
  126. // type-only differentiator to separate OptionWithoutProps from a constructor
  127. // type returned by defineComponent() or FunctionalComponent
  128. call?: never
  129. // type-only differentiators for built-in Vnode types
  130. __isFragment?: never
  131. __isTeleport?: never
  132. __isSuspense?: never
  133. }
  134. export type ComponentOptionsWithoutProps<
  135. Props = {},
  136. RawBindings = {},
  137. D = {},
  138. C extends ComputedOptions = {},
  139. M extends MethodOptions = {},
  140. E extends EmitsOptions = EmitsOptions,
  141. EE extends string = string
  142. > = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
  143. props?: undefined
  144. } & ThisType<
  145. ComponentPublicInstance<{}, RawBindings, D, C, M, E, Readonly<Props>>
  146. >
  147. export type ComponentOptionsWithArrayProps<
  148. PropNames extends string = string,
  149. RawBindings = {},
  150. D = {},
  151. C extends ComputedOptions = {},
  152. M extends MethodOptions = {},
  153. E extends EmitsOptions = EmitsOptions,
  154. EE extends string = string,
  155. Props = Readonly<{ [key in PropNames]?: any }>
  156. > = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
  157. props: PropNames[]
  158. } & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>
  159. export type ComponentOptionsWithObjectProps<
  160. PropsOptions = ComponentObjectPropsOptions,
  161. RawBindings = {},
  162. D = {},
  163. C extends ComputedOptions = {},
  164. M extends MethodOptions = {},
  165. E extends EmitsOptions = EmitsOptions,
  166. EE extends string = string,
  167. Props = Readonly<ExtractPropTypes<PropsOptions>>
  168. > = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
  169. props: PropsOptions
  170. } & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>
  171. export type ComponentOptions =
  172. | ComponentOptionsWithoutProps<any, any, any, any, any>
  173. | ComponentOptionsWithObjectProps<any, any, any, any, any>
  174. | ComponentOptionsWithArrayProps<any, any, any, any, any>
  175. export type ComputedOptions = Record<
  176. string,
  177. ComputedGetter<any> | WritableComputedOptions<any>
  178. >
  179. export interface MethodOptions {
  180. [key: string]: Function
  181. }
  182. export type ExtractComputedReturns<T extends any> = {
  183. [key in keyof T]: T[key] extends { get: Function }
  184. ? ReturnType<T[key]['get']>
  185. : ReturnType<T[key]>
  186. }
  187. type WatchOptionItem =
  188. | string
  189. | WatchCallback
  190. | { handler: WatchCallback } & WatchOptions
  191. type ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[]
  192. type ComponentWatchOptions = Record<string, ComponentWatchOptionItem>
  193. type ComponentInjectOptions =
  194. | string[]
  195. | Record<
  196. string | symbol,
  197. string | symbol | { from: string | symbol; default?: unknown }
  198. >
  199. interface LegacyOptions<
  200. Props,
  201. D,
  202. C extends ComputedOptions,
  203. M extends MethodOptions
  204. > {
  205. // allow any custom options
  206. [key: string]: any
  207. // state
  208. // Limitation: we cannot expose RawBindings on the `this` context for data
  209. // since that leads to some sort of circular inference and breaks ThisType
  210. // for the entire component.
  211. data?: (
  212. this: ComponentPublicInstance<Props>,
  213. vm: ComponentPublicInstance<Props>
  214. ) => D
  215. computed?: C
  216. methods?: M
  217. watch?: ComponentWatchOptions
  218. provide?: Data | Function
  219. inject?: ComponentInjectOptions
  220. // composition
  221. mixins?: ComponentOptions[]
  222. extends?: ComponentOptions
  223. // lifecycle
  224. beforeCreate?(): void
  225. created?(): void
  226. beforeMount?(): void
  227. mounted?(): void
  228. beforeUpdate?(): void
  229. updated?(): void
  230. activated?(): void
  231. deactivated?(): void
  232. beforeUnmount?(): void
  233. unmounted?(): void
  234. renderTracked?: DebuggerHook
  235. renderTriggered?: DebuggerHook
  236. errorCaptured?: ErrorCapturedHook
  237. }
  238. const enum OptionTypes {
  239. PROPS = 'Props',
  240. DATA = 'Data',
  241. COMPUTED = 'Computed',
  242. METHODS = 'Methods',
  243. INJECT = 'Inject'
  244. }
  245. function createDuplicateChecker() {
  246. const cache = Object.create(null)
  247. return (type: OptionTypes, key: string) => {
  248. if (cache[key]) {
  249. warn(`${type} property "${key}" is already defined in ${cache[key]}.`)
  250. } else {
  251. cache[key] = type
  252. }
  253. }
  254. }
  255. type DataFn = (vm: ComponentPublicInstance) => any
  256. export function applyOptions(
  257. instance: ComponentInternalInstance,
  258. options: ComponentOptions,
  259. deferredData: DataFn[] = [],
  260. deferredWatch: ComponentWatchOptions[] = [],
  261. asMixin: boolean = false
  262. ) {
  263. const {
  264. // composition
  265. mixins,
  266. extends: extendsOptions,
  267. // state
  268. props: propsOptions,
  269. data: dataOptions,
  270. computed: computedOptions,
  271. methods,
  272. watch: watchOptions,
  273. provide: provideOptions,
  274. inject: injectOptions,
  275. // assets
  276. components,
  277. directives,
  278. // lifecycle
  279. beforeMount,
  280. mounted,
  281. beforeUpdate,
  282. updated,
  283. activated,
  284. deactivated,
  285. beforeUnmount,
  286. unmounted,
  287. renderTracked,
  288. renderTriggered,
  289. errorCaptured
  290. } = options
  291. const publicThis = instance.proxy!
  292. const ctx = instance.ctx
  293. const globalMixins = instance.appContext.mixins
  294. // call it only during dev
  295. // applyOptions is called non-as-mixin once per instance
  296. if (!asMixin) {
  297. callSyncHook('beforeCreate', options, publicThis, globalMixins)
  298. // global mixins are applied first
  299. applyMixins(instance, globalMixins, deferredData, deferredWatch)
  300. }
  301. // extending a base component...
  302. if (extendsOptions) {
  303. applyOptions(instance, extendsOptions, deferredData, deferredWatch, true)
  304. }
  305. // local mixins
  306. if (mixins) {
  307. applyMixins(instance, mixins, deferredData, deferredWatch)
  308. }
  309. const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
  310. if (__DEV__ && propsOptions) {
  311. for (const key in normalizePropsOptions(propsOptions)[0]) {
  312. checkDuplicateProperties!(OptionTypes.PROPS, key)
  313. }
  314. }
  315. // options initialization order (to be consistent with Vue 2):
  316. // - props (already done outside of this function)
  317. // - inject
  318. // - methods
  319. // - data (deferred since it relies on `this` access)
  320. // - computed
  321. // - watch (deferred since it relies on `this` access)
  322. if (injectOptions) {
  323. if (isArray(injectOptions)) {
  324. for (let i = 0; i < injectOptions.length; i++) {
  325. const key = injectOptions[i]
  326. ctx[key] = inject(key)
  327. if (__DEV__) {
  328. checkDuplicateProperties!(OptionTypes.INJECT, key)
  329. }
  330. }
  331. } else {
  332. for (const key in injectOptions) {
  333. const opt = injectOptions[key]
  334. if (isObject(opt)) {
  335. ctx[key] = inject(opt.from, opt.default)
  336. } else {
  337. ctx[key] = inject(opt)
  338. }
  339. if (__DEV__) {
  340. checkDuplicateProperties!(OptionTypes.INJECT, key)
  341. }
  342. }
  343. }
  344. }
  345. if (methods) {
  346. for (const key in methods) {
  347. const methodHandler = (methods as MethodOptions)[key]
  348. if (isFunction(methodHandler)) {
  349. ctx[key] = methodHandler.bind(publicThis)
  350. if (__DEV__) {
  351. checkDuplicateProperties!(OptionTypes.METHODS, key)
  352. }
  353. } else if (__DEV__) {
  354. warn(
  355. `Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
  356. `Did you reference the function correctly?`
  357. )
  358. }
  359. }
  360. }
  361. if (dataOptions) {
  362. if (__DEV__ && !isFunction(dataOptions)) {
  363. warn(
  364. `The data option must be a function. ` +
  365. `Plain object usage is no longer supported.`
  366. )
  367. }
  368. if (asMixin) {
  369. deferredData.push(dataOptions as DataFn)
  370. } else {
  371. resolveData(instance, dataOptions, publicThis)
  372. }
  373. }
  374. if (!asMixin) {
  375. if (deferredData.length) {
  376. deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis))
  377. }
  378. if (__DEV__) {
  379. const rawData = toRaw(instance.data)
  380. for (const key in rawData) {
  381. checkDuplicateProperties!(OptionTypes.DATA, key)
  382. // expose data on ctx during dev
  383. if (key[0] !== '$' && key[0] !== '_') {
  384. Object.defineProperty(ctx, key, {
  385. configurable: true,
  386. enumerable: true,
  387. get: () => rawData[key],
  388. set: NOOP
  389. })
  390. }
  391. }
  392. }
  393. }
  394. if (computedOptions) {
  395. for (const key in computedOptions) {
  396. const opt = (computedOptions as ComputedOptions)[key]
  397. const get = isFunction(opt)
  398. ? opt.bind(publicThis, publicThis)
  399. : isFunction(opt.get)
  400. ? opt.get.bind(publicThis, publicThis)
  401. : NOOP
  402. if (__DEV__ && get === NOOP) {
  403. warn(`Computed property "${key}" has no getter.`)
  404. }
  405. const set =
  406. !isFunction(opt) && isFunction(opt.set)
  407. ? opt.set.bind(publicThis)
  408. : __DEV__
  409. ? () => {
  410. warn(
  411. `Write operation failed: computed property "${key}" is readonly.`
  412. )
  413. }
  414. : NOOP
  415. const c = computed({
  416. get,
  417. set
  418. })
  419. Object.defineProperty(ctx, key, {
  420. enumerable: true,
  421. configurable: true,
  422. get: () => c.value,
  423. set: v => (c.value = v)
  424. })
  425. if (__DEV__) {
  426. checkDuplicateProperties!(OptionTypes.COMPUTED, key)
  427. }
  428. }
  429. }
  430. if (watchOptions) {
  431. deferredWatch.push(watchOptions)
  432. }
  433. if (!asMixin && deferredWatch.length) {
  434. deferredWatch.forEach(watchOptions => {
  435. for (const key in watchOptions) {
  436. createWatcher(watchOptions[key], ctx, publicThis, key)
  437. }
  438. })
  439. }
  440. if (provideOptions) {
  441. const provides = isFunction(provideOptions)
  442. ? provideOptions.call(publicThis)
  443. : provideOptions
  444. for (const key in provides) {
  445. provide(key, provides[key])
  446. }
  447. }
  448. // asset options
  449. if (components) {
  450. extend(instance.components, components)
  451. }
  452. if (directives) {
  453. extend(instance.directives, directives)
  454. }
  455. // lifecycle options
  456. if (!asMixin) {
  457. callSyncHook('created', options, publicThis, globalMixins)
  458. }
  459. if (beforeMount) {
  460. onBeforeMount(beforeMount.bind(publicThis))
  461. }
  462. if (mounted) {
  463. onMounted(mounted.bind(publicThis))
  464. }
  465. if (beforeUpdate) {
  466. onBeforeUpdate(beforeUpdate.bind(publicThis))
  467. }
  468. if (updated) {
  469. onUpdated(updated.bind(publicThis))
  470. }
  471. if (activated) {
  472. onActivated(activated.bind(publicThis))
  473. }
  474. if (deactivated) {
  475. onDeactivated(deactivated.bind(publicThis))
  476. }
  477. if (errorCaptured) {
  478. onErrorCaptured(errorCaptured.bind(publicThis))
  479. }
  480. if (renderTracked) {
  481. onRenderTracked(renderTracked.bind(publicThis))
  482. }
  483. if (renderTriggered) {
  484. onRenderTriggered(renderTriggered.bind(publicThis))
  485. }
  486. if (beforeUnmount) {
  487. onBeforeUnmount(beforeUnmount.bind(publicThis))
  488. }
  489. if (unmounted) {
  490. onUnmounted(unmounted.bind(publicThis))
  491. }
  492. }
  493. function callSyncHook(
  494. name: 'beforeCreate' | 'created',
  495. options: ComponentOptions,
  496. ctx: ComponentPublicInstance,
  497. globalMixins: ComponentOptions[]
  498. ) {
  499. callHookFromMixins(name, globalMixins, ctx)
  500. const baseHook = options.extends && options.extends[name]
  501. if (baseHook) {
  502. baseHook.call(ctx)
  503. }
  504. const mixins = options.mixins
  505. if (mixins) {
  506. callHookFromMixins(name, mixins, ctx)
  507. }
  508. const selfHook = options[name]
  509. if (selfHook) {
  510. selfHook.call(ctx)
  511. }
  512. }
  513. function callHookFromMixins(
  514. name: 'beforeCreate' | 'created',
  515. mixins: ComponentOptions[],
  516. ctx: ComponentPublicInstance
  517. ) {
  518. for (let i = 0; i < mixins.length; i++) {
  519. const fn = mixins[i][name]
  520. if (fn) {
  521. fn.call(ctx)
  522. }
  523. }
  524. }
  525. function applyMixins(
  526. instance: ComponentInternalInstance,
  527. mixins: ComponentOptions[],
  528. deferredData: DataFn[],
  529. deferredWatch: ComponentWatchOptions[]
  530. ) {
  531. for (let i = 0; i < mixins.length; i++) {
  532. applyOptions(instance, mixins[i], deferredData, deferredWatch, true)
  533. }
  534. }
  535. function resolveData(
  536. instance: ComponentInternalInstance,
  537. dataFn: DataFn,
  538. publicThis: ComponentPublicInstance
  539. ) {
  540. const data = dataFn.call(publicThis, publicThis)
  541. if (__DEV__ && isPromise(data)) {
  542. warn(
  543. `data() returned a Promise - note data() cannot be async; If you ` +
  544. `intend to perform data fetching before component renders, use ` +
  545. `async setup() + <Suspense>.`
  546. )
  547. }
  548. if (!isObject(data)) {
  549. __DEV__ && warn(`data() should return an object.`)
  550. } else if (instance.data === EMPTY_OBJ) {
  551. instance.data = reactive(data)
  552. } else {
  553. // existing data: this is a mixin or extends.
  554. extend(instance.data, data)
  555. }
  556. }
  557. function createWatcher(
  558. raw: ComponentWatchOptionItem,
  559. ctx: Data,
  560. publicThis: ComponentPublicInstance,
  561. key: string
  562. ) {
  563. const getter = () => (publicThis as any)[key]
  564. if (isString(raw)) {
  565. const handler = ctx[raw]
  566. if (isFunction(handler)) {
  567. watch(getter, handler as WatchCallback)
  568. } else if (__DEV__) {
  569. warn(`Invalid watch handler specified by key "${raw}"`, handler)
  570. }
  571. } else if (isFunction(raw)) {
  572. watch(getter, raw.bind(publicThis))
  573. } else if (isObject(raw)) {
  574. if (isArray(raw)) {
  575. raw.forEach(r => createWatcher(r, ctx, publicThis, key))
  576. } else {
  577. watch(getter, raw.handler.bind(publicThis), raw)
  578. }
  579. } else if (__DEV__) {
  580. warn(`Invalid watch option: "${key}"`)
  581. }
  582. }
  583. export function resolveMergedOptions(
  584. instance: ComponentInternalInstance
  585. ): ComponentOptions {
  586. const raw = instance.type as ComponentOptions
  587. const { __merged, mixins, extends: extendsOptions } = raw
  588. if (__merged) return __merged
  589. const globalMixins = instance.appContext.mixins
  590. if (!globalMixins.length && !mixins && !extendsOptions) return raw
  591. const options = {}
  592. globalMixins.forEach(m => mergeOptions(options, m, instance))
  593. extendsOptions && mergeOptions(options, extendsOptions, instance)
  594. mixins && mixins.forEach(m => mergeOptions(options, m, instance))
  595. mergeOptions(options, raw, instance)
  596. return (raw.__merged = options)
  597. }
  598. function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
  599. const strats = instance.appContext.config.optionMergeStrategies
  600. for (const key in from) {
  601. const strat = strats && strats[key]
  602. if (strat) {
  603. to[key] = strat(to[key], from[key], instance.proxy, key)
  604. } else if (!hasOwn(to, key)) {
  605. to[key] = from[key]
  606. }
  607. }
  608. }