apiOptions.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. import {
  2. ComponentInternalInstance,
  3. Data,
  4. Component,
  5. SetupContext,
  6. RenderFunction,
  7. SFCInternalOptions
  8. } from './component'
  9. import {
  10. isFunction,
  11. extend,
  12. isString,
  13. isObject,
  14. isArray,
  15. EMPTY_OBJ,
  16. NOOP
  17. } from '@vue/shared'
  18. import { computed } from './apiReactivity'
  19. import { watch, WatchOptions, WatchCallback } from './apiWatch'
  20. import { provide, inject } from './apiInject'
  21. import {
  22. onBeforeMount,
  23. onMounted,
  24. onBeforeUpdate,
  25. onUpdated,
  26. onErrorCaptured,
  27. onRenderTracked,
  28. onBeforeUnmount,
  29. onUnmounted,
  30. onActivated,
  31. onDeactivated,
  32. onRenderTriggered,
  33. DebuggerHook,
  34. ErrorCapturedHook
  35. } from './apiLifecycle'
  36. import {
  37. reactive,
  38. ComputedGetter,
  39. WritableComputedOptions
  40. } from '@vue/reactivity'
  41. import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
  42. import { Directive } from './directives'
  43. import { ComponentPublicInstance } from './componentProxy'
  44. import { warn } from './warning'
  45. export interface ComponentOptionsBase<
  46. Props,
  47. RawBindings,
  48. D,
  49. C extends ComputedOptions,
  50. M extends MethodOptions
  51. > extends LegacyOptions<Props, RawBindings, D, C, M>, SFCInternalOptions {
  52. setup?: (
  53. this: void,
  54. props: Props,
  55. ctx: SetupContext
  56. ) => RawBindings | RenderFunction | void
  57. name?: string
  58. template?: string | object // can be a direct DOM node
  59. // Note: we are intentionally using the signature-less `Function` type here
  60. // since any type with signature will cause the whole inference to fail when
  61. // the return expression contains reference to `this`.
  62. // Luckily `render()` doesn't need any arguments nor does it care about return
  63. // type.
  64. render?: Function
  65. // SSR only. This is produced by compiler-ssr and attached in compiler-sfc
  66. // not user facing, so the typing is lax and for test only.
  67. ssrRender?: (
  68. ctx: any,
  69. push: (item: any) => void,
  70. parentInstance: ComponentInternalInstance
  71. ) => void
  72. components?: Record<
  73. string,
  74. Component | { new (): ComponentPublicInstance<any, any, any, any, any> }
  75. >
  76. directives?: Record<string, Directive>
  77. inheritAttrs?: boolean
  78. // type-only differentiator to separate OptionWithoutProps from a constructor
  79. // type returned by defineComponent() or FunctionalComponent
  80. call?: never
  81. // type-only differentiators for built-in Vnode types
  82. __isFragment?: never
  83. __isPortal?: never
  84. __isSuspense?: never
  85. }
  86. export type ComponentOptionsWithoutProps<
  87. Props = {},
  88. RawBindings = {},
  89. D = {},
  90. C extends ComputedOptions = {},
  91. M extends MethodOptions = {}
  92. > = ComponentOptionsBase<Readonly<Props>, RawBindings, D, C, M> & {
  93. props?: undefined
  94. } & ThisType<ComponentPublicInstance<{}, RawBindings, D, C, M, Readonly<Props>>>
  95. export type ComponentOptionsWithArrayProps<
  96. PropNames extends string = string,
  97. RawBindings = {},
  98. D = {},
  99. C extends ComputedOptions = {},
  100. M extends MethodOptions = {},
  101. Props = Readonly<{ [key in PropNames]?: any }>
  102. > = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
  103. props: PropNames[]
  104. } & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>
  105. export type ComponentOptionsWithObjectProps<
  106. PropsOptions = ComponentObjectPropsOptions,
  107. RawBindings = {},
  108. D = {},
  109. C extends ComputedOptions = {},
  110. M extends MethodOptions = {},
  111. Props = Readonly<ExtractPropTypes<PropsOptions>>
  112. > = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
  113. props: PropsOptions
  114. } & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>
  115. export type ComponentOptions =
  116. | ComponentOptionsWithoutProps
  117. | ComponentOptionsWithObjectProps
  118. | ComponentOptionsWithArrayProps
  119. // TODO legacy component definition also supports constructors with .options
  120. type LegacyComponent = ComponentOptions
  121. export type ComputedOptions = Record<
  122. string,
  123. ComputedGetter<any> | WritableComputedOptions<any>
  124. >
  125. export interface MethodOptions {
  126. [key: string]: Function
  127. }
  128. export type ExtractComputedReturns<T extends any> = {
  129. [key in keyof T]: T[key] extends { get: Function }
  130. ? ReturnType<T[key]['get']>
  131. : ReturnType<T[key]>
  132. }
  133. type WatchOptionItem =
  134. | string
  135. | WatchCallback
  136. | { handler: WatchCallback } & WatchOptions
  137. type ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[]
  138. type ComponentWatchOptions = Record<string, ComponentWatchOptionItem>
  139. type ComponentInjectOptions =
  140. | string[]
  141. | Record<
  142. string | symbol,
  143. string | symbol | { from: string | symbol; default?: unknown }
  144. >
  145. export interface LegacyOptions<
  146. Props,
  147. RawBindings,
  148. D,
  149. C extends ComputedOptions,
  150. M extends MethodOptions
  151. > {
  152. el?: any
  153. // state
  154. // Limitation: we cannot expose RawBindings on the `this` context for data
  155. // since that leads to some sort of circular inference and breaks ThisType
  156. // for the entire component.
  157. data?: D | ((this: ComponentPublicInstance<Props>) => D)
  158. computed?: C
  159. methods?: M
  160. watch?: ComponentWatchOptions
  161. provide?: Data | Function
  162. inject?: ComponentInjectOptions
  163. // composition
  164. mixins?: LegacyComponent[]
  165. extends?: LegacyComponent
  166. // lifecycle
  167. beforeCreate?(): void
  168. created?(): void
  169. beforeMount?(): void
  170. mounted?(): void
  171. beforeUpdate?(): void
  172. updated?(): void
  173. activated?(): void
  174. deactivated?(): void
  175. beforeUnmount?(): void
  176. unmounted?(): void
  177. renderTracked?: DebuggerHook
  178. renderTriggered?: DebuggerHook
  179. errorCaptured?: ErrorCapturedHook
  180. }
  181. const enum OptionTypes {
  182. PROPS = 'Props',
  183. DATA = 'Data',
  184. COMPUTED = 'Computed',
  185. METHODS = 'Methods',
  186. INJECT = 'Inject'
  187. }
  188. function createDuplicateChecker() {
  189. const cache = Object.create(null)
  190. return (type: OptionTypes, key: string) => {
  191. if (cache[key]) {
  192. warn(`${type} property "${key}" is already defined in ${cache[key]}.`)
  193. } else {
  194. cache[key] = type
  195. }
  196. }
  197. }
  198. export function applyOptions(
  199. instance: ComponentInternalInstance,
  200. options: ComponentOptions,
  201. asMixin: boolean = false
  202. ) {
  203. const ctx = instance.proxy!
  204. const {
  205. // composition
  206. mixins,
  207. extends: extendsOptions,
  208. // state
  209. props: propsOptions,
  210. data: dataOptions,
  211. computed: computedOptions,
  212. methods,
  213. watch: watchOptions,
  214. provide: provideOptions,
  215. inject: injectOptions,
  216. // assets
  217. components,
  218. directives,
  219. // lifecycle
  220. beforeMount,
  221. mounted,
  222. beforeUpdate,
  223. updated,
  224. activated,
  225. deactivated,
  226. beforeUnmount,
  227. unmounted,
  228. renderTracked,
  229. renderTriggered,
  230. errorCaptured
  231. } = options
  232. const renderContext =
  233. instance.renderContext === EMPTY_OBJ
  234. ? (instance.renderContext = {})
  235. : instance.renderContext
  236. const globalMixins = instance.appContext.mixins
  237. // call it only during dev
  238. const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
  239. // applyOptions is called non-as-mixin once per instance
  240. if (!asMixin) {
  241. callSyncHook('beforeCreate', options, ctx, globalMixins)
  242. // global mixins are applied first
  243. applyMixins(instance, globalMixins)
  244. }
  245. // extending a base component...
  246. if (extendsOptions) {
  247. applyOptions(instance, extendsOptions, true)
  248. }
  249. // local mixins
  250. if (mixins) {
  251. applyMixins(instance, mixins)
  252. }
  253. if (__DEV__ && propsOptions) {
  254. for (const key in propsOptions) {
  255. checkDuplicateProperties!(OptionTypes.PROPS, key)
  256. }
  257. }
  258. // state options
  259. if (dataOptions) {
  260. const data = isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions
  261. if (!isObject(data)) {
  262. __DEV__ && warn(`data() should return an object.`)
  263. } else if (instance.data === EMPTY_OBJ) {
  264. if (__DEV__) {
  265. for (const key in data) {
  266. checkDuplicateProperties!(OptionTypes.DATA, key)
  267. }
  268. }
  269. instance.data = reactive(data)
  270. } else {
  271. // existing data: this is a mixin or extends.
  272. extend(instance.data, data)
  273. }
  274. }
  275. if (computedOptions) {
  276. for (const key in computedOptions) {
  277. const opt = (computedOptions as ComputedOptions)[key]
  278. __DEV__ && checkDuplicateProperties!(OptionTypes.COMPUTED, key)
  279. if (isFunction(opt)) {
  280. renderContext[key] = computed(opt.bind(ctx))
  281. } else {
  282. const { get, set } = opt
  283. if (isFunction(get)) {
  284. renderContext[key] = computed({
  285. get: get.bind(ctx),
  286. set: isFunction(set)
  287. ? set.bind(ctx)
  288. : __DEV__
  289. ? () => {
  290. warn(
  291. `Computed property "${key}" was assigned to but it has no setter.`
  292. )
  293. }
  294. : NOOP
  295. })
  296. } else if (__DEV__) {
  297. warn(`Computed property "${key}" has no getter.`)
  298. }
  299. }
  300. }
  301. }
  302. if (methods) {
  303. for (const key in methods) {
  304. const methodHandler = (methods as MethodOptions)[key]
  305. if (isFunction(methodHandler)) {
  306. __DEV__ && checkDuplicateProperties!(OptionTypes.METHODS, key)
  307. renderContext[key] = methodHandler.bind(ctx)
  308. } else if (__DEV__) {
  309. warn(
  310. `Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
  311. `Did you reference the function correctly?`
  312. )
  313. }
  314. }
  315. }
  316. if (watchOptions) {
  317. for (const key in watchOptions) {
  318. createWatcher(watchOptions[key], renderContext, ctx, key)
  319. }
  320. }
  321. if (provideOptions) {
  322. const provides = isFunction(provideOptions)
  323. ? provideOptions.call(ctx)
  324. : provideOptions
  325. for (const key in provides) {
  326. provide(key, provides[key])
  327. }
  328. }
  329. if (injectOptions) {
  330. if (isArray(injectOptions)) {
  331. for (let i = 0; i < injectOptions.length; i++) {
  332. const key = injectOptions[i]
  333. __DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
  334. renderContext[key] = inject(key)
  335. }
  336. } else {
  337. for (const key in injectOptions) {
  338. __DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
  339. const opt = injectOptions[key]
  340. if (isObject(opt)) {
  341. renderContext[key] = inject(opt.from, opt.default)
  342. } else {
  343. renderContext[key] = inject(opt)
  344. }
  345. }
  346. }
  347. }
  348. // asset options
  349. if (components) {
  350. extend(instance.components, components)
  351. }
  352. if (directives) {
  353. extend(instance.directives, directives)
  354. }
  355. // lifecycle options
  356. if (!asMixin) {
  357. callSyncHook('created', options, ctx, globalMixins)
  358. }
  359. if (beforeMount) {
  360. onBeforeMount(beforeMount.bind(ctx))
  361. }
  362. if (mounted) {
  363. onMounted(mounted.bind(ctx))
  364. }
  365. if (beforeUpdate) {
  366. onBeforeUpdate(beforeUpdate.bind(ctx))
  367. }
  368. if (updated) {
  369. onUpdated(updated.bind(ctx))
  370. }
  371. if (activated) {
  372. onActivated(activated.bind(ctx))
  373. }
  374. if (deactivated) {
  375. onDeactivated(deactivated.bind(ctx))
  376. }
  377. if (errorCaptured) {
  378. onErrorCaptured(errorCaptured.bind(ctx))
  379. }
  380. if (renderTracked) {
  381. onRenderTracked(renderTracked.bind(ctx))
  382. }
  383. if (renderTriggered) {
  384. onRenderTriggered(renderTriggered.bind(ctx))
  385. }
  386. if (beforeUnmount) {
  387. onBeforeUnmount(beforeUnmount.bind(ctx))
  388. }
  389. if (unmounted) {
  390. onUnmounted(unmounted.bind(ctx))
  391. }
  392. }
  393. function callSyncHook(
  394. name: 'beforeCreate' | 'created',
  395. options: ComponentOptions,
  396. ctx: ComponentPublicInstance,
  397. globalMixins: ComponentOptions[]
  398. ) {
  399. callHookFromMixins(name, globalMixins, ctx)
  400. const baseHook = options.extends && options.extends[name]
  401. if (baseHook) {
  402. baseHook.call(ctx)
  403. }
  404. const mixins = options.mixins
  405. if (mixins) {
  406. callHookFromMixins(name, mixins, ctx)
  407. }
  408. const selfHook = options[name]
  409. if (selfHook) {
  410. selfHook.call(ctx)
  411. }
  412. }
  413. function callHookFromMixins(
  414. name: 'beforeCreate' | 'created',
  415. mixins: ComponentOptions[],
  416. ctx: ComponentPublicInstance
  417. ) {
  418. for (let i = 0; i < mixins.length; i++) {
  419. const fn = mixins[i][name]
  420. if (fn) {
  421. fn.call(ctx)
  422. }
  423. }
  424. }
  425. function applyMixins(
  426. instance: ComponentInternalInstance,
  427. mixins: ComponentOptions[]
  428. ) {
  429. for (let i = 0; i < mixins.length; i++) {
  430. applyOptions(instance, mixins[i], true)
  431. }
  432. }
  433. function createWatcher(
  434. raw: ComponentWatchOptionItem,
  435. renderContext: Data,
  436. ctx: ComponentPublicInstance,
  437. key: string
  438. ) {
  439. const getter = () => (ctx as Data)[key]
  440. if (isString(raw)) {
  441. const handler = renderContext[raw]
  442. if (isFunction(handler)) {
  443. watch(getter, handler as WatchCallback)
  444. } else if (__DEV__) {
  445. warn(`Invalid watch handler specified by key "${raw}"`, handler)
  446. }
  447. } else if (isFunction(raw)) {
  448. watch(getter, raw.bind(ctx))
  449. } else if (isObject(raw)) {
  450. if (isArray(raw)) {
  451. raw.forEach(r => createWatcher(r, renderContext, ctx, key))
  452. } else {
  453. watch(getter, raw.handler.bind(ctx), raw)
  454. }
  455. } else if (__DEV__) {
  456. warn(`Invalid watch option: "${key}"`)
  457. }
  458. }