diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 967c77f8f97..ff3e7c6257e 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -20,7 +20,6 @@ import { import { currentInstance, ComponentInternalInstance, - Data, isInSSRComponentSetup, recordInstanceBoundEffect } from './component' @@ -276,9 +275,11 @@ export function instanceWatch( cb: Function, options?: WatchOptions ): StopHandle { - const ctx = this.proxy as Data - const getter = isString(source) ? () => ctx[source] : source.bind(ctx) - const stop = watch(getter, cb.bind(ctx), options) + const publicThis = this.proxy as any + const getter = isString(source) + ? () => publicThis[source] + : source.bind(publicThis) + const stop = watch(getter, cb.bind(publicThis), options) onBeforeUnmount(stop, this) return stop } diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index e8fbfad4ab3..39087e8c311 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -528,7 +528,7 @@ function createWatcher( publicThis: ComponentPublicInstance, key: string ) { - const getter = () => (publicThis as Data)[key] + const getter = () => (publicThis as any)[key] if (isString(raw)) { const handler = ctx[raw] if (isFunction(handler)) { diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index 03044d07f4e..183207e90ab 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -24,6 +24,33 @@ import { } from './componentRenderUtils' import { warn } from './warning' +/** + * Custom properties added to component instances in any way and can be accessed through `this` + * + * @example + * Here is an example of adding a property `$router` to every component instance: + * ```ts + * import { createApp } from 'vue' + * import { Router, createRouter } from 'vue-router' + * + * declare module '@vue/runtime-core' { + * interface ComponentCustomProperties { + * $router: Router + * } + * } + * + * // effectively adding the router to every component instance + * const app = createApp({}) + * const router = createRouter() + * app.config.globalProperties.$router = router + * + * const vm = app.mount('#app') + * // we can access the router from the instance + * vm.$router.push('/') + * ``` + */ +export interface ComponentCustomProperties {} + // public properties exposed on the proxy, which is used as the render context // in templates (as `this` in the render option) export type ComponentPublicInstance< @@ -53,7 +80,8 @@ export type ComponentPublicInstance< UnwrapRef & D & ExtractComputedReturns & - M + M & + ComponentCustomProperties const publicPropertiesMap: Record< string, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 934ae9731cc..f4273cc37c0 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -192,7 +192,10 @@ export { ComponentOptionsWithObjectProps as ComponentOptionsWithProps, ComponentOptionsWithArrayProps } from './componentOptions' -export { ComponentPublicInstance } from './componentProxy' +export { + ComponentPublicInstance, + ComponentCustomProperties +} from './componentProxy' export { Renderer, RendererNode, diff --git a/test-dts/componentCustomProperties.test-d.ts b/test-dts/componentCustomProperties.test-d.ts new file mode 100644 index 00000000000..60267c6b4c2 --- /dev/null +++ b/test-dts/componentCustomProperties.test-d.ts @@ -0,0 +1,20 @@ +import { expectError } from 'tsd' +import { defineComponent } from './index' + +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + state: 'stopped' | 'running' + } +} + +export const Custom = defineComponent({ + data: () => ({ counter: 0 }), + methods: { + aMethod() { + expectError(this.notExisting) + this.counter++ + this.state = 'running' + expectError((this.state = 'not valid')) + } + } +})