wip: effectScope

This commit is contained in:
Evan You 2022-05-29 14:17:08 +08:00
parent b4c511da8f
commit 9fb4f7d070
24 changed files with 522 additions and 113 deletions

View File

@ -56,7 +56,7 @@ export function updateComponentListeners(
target = undefined
}
export function eventsMixin(Vue: Component) {
export function eventsMixin(Vue: typeof Component) {
const hookRE = /^hook:/
Vue.prototype.$on = function (
event: string | Array<string>,

View File

@ -9,10 +9,11 @@ import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
import type { Component } from 'typescript/component'
import type { InternalComponentOptions } from 'typescript/options'
import { EffectScope } from 'v3'
let uid = 0
export function initMixin(Vue: Component) {
export function initMixin(Vue: typeof Component) {
Vue.prototype._init = function (options?: Record<string, any>) {
const vm: Component = this
// a uid
@ -31,6 +32,8 @@ export function initMixin(Vue: Component) {
vm._isVue = true
// avoid instances from being observed
vm.__v_skip = true
// effect scope
vm._scope = new EffectScope(true /* detached */)
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
@ -96,7 +99,7 @@ export function initInternalComponent(
}
}
export function resolveConstructorOptions(Ctor: Component) {
export function resolveConstructorOptions(Ctor: typeof Component) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
@ -120,7 +123,9 @@ export function resolveConstructorOptions(Ctor: Component) {
return options
}
function resolveModifiedOptions(Ctor: Component): Record<string, any> | null {
function resolveModifiedOptions(
Ctor: typeof Component
): Record<string, any> | null {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions

View File

@ -57,7 +57,7 @@ export function initLifecycle(vm: Component) {
vm._isBeingDestroyed = false
}
export function lifecycleMixin(Vue: Component) {
export function lifecycleMixin(Vue: typeof Component) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
@ -108,14 +108,9 @@ export function lifecycleMixin(Vue: Component) {
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// teardown scope. this includes both the render watcher and other
// watchers created
vm._scope.stop()
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {

View File

@ -86,11 +86,11 @@ export function setCurrentRenderingInstance(vm: Component) {
currentRenderingInstance = vm
}
export function renderMixin(Vue: Component) {
export function renderMixin(Vue: typeof Component) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
Vue.prototype.$nextTick = function (fn: (...args: any[]) => any) {
return nextTick(fn, this)
}

View File

@ -29,7 +29,7 @@ import {
invokeWithErrorHandling,
isFunction
} from '../util/index'
import type { Component } from '../../../typescript/component'
import type { Component } from 'typescript/component'
const sharedPropertyDefinition = {
enumerable: true,
@ -49,7 +49,6 @@ export function proxy(target: Object, sourceKey: string, key: string) {
}
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
@ -310,7 +309,7 @@ function initWatch(vm: Component, watch: Object) {
function createWatcher(
vm: Component,
expOrFn: string | Function,
expOrFn: string | (() => any),
handler: any,
options?: Object
) {
@ -324,7 +323,7 @@ function createWatcher(
return vm.$watch(expOrFn, handler, options)
}
export function stateMixin(Vue: Component) {
export function stateMixin(Vue: typeof Component) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.

View File

@ -16,6 +16,10 @@ import Dep, { pushTarget, popTarget, DepTarget } from './dep'
import type { SimpleSet } from '../util/index'
import type { Component } from 'typescript/component'
import {
activeEffectScope,
recordEffectScope
} from '../../v3/reactivity/effectScope'
let uid = 0
@ -59,11 +63,11 @@ export default class Watcher implements DepTarget {
} | null,
isRenderWatcher?: boolean
) {
recordEffectScope(this, activeEffectScope || (vm ? vm._scope : undefined))
if ((this.vm = vm)) {
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
}
// options
if (options) {
@ -237,13 +241,10 @@ export default class Watcher implements DepTarget {
* Remove self from all dependencies' subscriber list.
*/
teardown() {
if (this.vm && !this.vm._isBeingDestroyed) {
remove(this.vm._scope.effects, this)
}
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (this.vm && !this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)

View File

@ -85,7 +85,9 @@ if (typeof Promise !== 'undefined' && isNative(Promise)) {
}
}
export function nextTick(cb?: Function, ctx?: Object) {
export function nextTick(): Promise<void>
export function nextTick(cb: (...args: any[]) => any, ctx?: object): void
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
let _resolve
callbacks.push(() => {
if (cb) {

View File

@ -26,7 +26,10 @@ import type {
VNodeWithData
} from 'typescript/vnode'
import type { Component } from 'typescript/component'
import type { InternalComponentOptions } from 'typescript/options'
import type {
ComponentOptions,
InternalComponentOptions
} from 'typescript/options'
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
@ -95,7 +98,7 @@ const componentVNodeHooks = {
const hooksToMerge = Object.keys(componentVNodeHooks)
export function createComponent(
Ctor: Component | Function | Object | void,
Ctor: typeof Component | Function | ComponentOptions | void,
data: VNodeData | undefined,
context: Component,
children?: Array<VNode>,
@ -109,7 +112,7 @@ export function createComponent(
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
Ctor = baseCtor.extend(Ctor as typeof Component)
}
// if at this stage it's not a constructor or an async component factory,
@ -139,7 +142,7 @@ export function createComponent(
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor as Component)
resolveConstructorOptions(Ctor as typeof Component)
// transform component v-model data into props & events
if (isDef(data.model)) {
@ -155,7 +158,7 @@ export function createComponent(
// @ts-expect-error
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(
Ctor as Component,
Ctor as typeof Component,
propsData,
data,
context,

View File

@ -126,7 +126,7 @@ export function _createElement(
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
vnode = createComponent(tag as any, data, context, children)
}
if (isArray(vnode)) {
return vnode

View File

@ -23,7 +23,7 @@ export function FunctionalRenderContext(
props: Object,
children: Array<VNode> | undefined,
parent: Component,
Ctor: Component
Ctor: typeof Component
) {
const options = Ctor.options
// ensure the createElement function in functional components
@ -31,14 +31,13 @@ export function FunctionalRenderContext(
let contextVm
if (hasOwn(parent, '_uid')) {
contextVm = Object.create(parent)
// $flow-disable-line
contextVm._original = parent
} else {
// the context vm passed in is a functional context as well.
// in this case we want to make sure we are able to get a hold to the
// real context instance.
contextVm = parent
// $flow-disable-line
// @ts-ignore
parent = parent._original
}
const isCompiled = isTrue(options._compiled)
@ -94,7 +93,7 @@ export function FunctionalRenderContext(
installRenderHelpers(FunctionalRenderContext.prototype)
export function createFunctionalComponent(
Ctor: Component,
Ctor: typeof Component,
propsData: Object | undefined,
data: VNodeData,
contextVm: Component,

View File

@ -11,7 +11,7 @@ import type { VNodeData } from 'typescript/vnode'
export function extractPropsFromVNodeData(
data: VNodeData,
Ctor: Component,
Ctor: typeof Component,
tag?: string
): object | undefined {
// we are only extracting raw values here.

View File

@ -37,8 +37,8 @@ export function createAsyncPlaceholder(
export function resolveAsyncComponent(
factory: { (...args: any[]): any; [keye: string]: any },
baseCtor: Component
): Component | void {
baseCtor: typeof Component
): typeof Component | void {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}

View File

@ -10,15 +10,7 @@ export function initSetup(vm: Component) {
const options = vm.$options
const setup = options.setup
if (setup) {
const ctx = {
get attrs() {
return initAttrsProxy(vm)
},
get slots() {
return initSlotsProxy(vm)
},
emit: bind(vm.$emit, vm) as any
}
const ctx = (vm._setupContext = createSetupContext(vm))
setCurrentInstance(vm)
const setupResult = invokeWithErrorHandling(
@ -73,6 +65,18 @@ function proxySetupProperty(
})
}
function createSetupContext(vm: Component) {
return {
get attrs() {
return initAttrsProxy(vm)
},
get slots() {
return initSlotsProxy(vm)
},
emit: bind(vm.$emit, vm) as any
}
}
function initAttrsProxy(vm: Component) {
if (!vm._attrsProxy) {
const proxy = (vm._attrsProxy = {})
@ -146,5 +150,6 @@ function getContext(): SetupContext {
if (__DEV__ && !currentInstance) {
warn(`useContext() called without active instance.`)
}
return currentInstance!.setupContext
const vm = currentInstance!
return vm._setupContext || (vm._setupContext = createSetupContext(vm))
}

View File

@ -245,7 +245,7 @@ function doWatch(
} else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
if (instance && instance._isDestroyed) {
return
}
if (cleanup) {

View File

@ -17,5 +17,7 @@ export function getCurrentInstance(): { proxy: Component } | null {
* @private
*/
export function setCurrentInstance(vm: Component | null = null) {
if (!vm) currentInstance && currentInstance._scope.off()
currentInstance = vm
vm && vm._scope.on()
}

View File

@ -57,10 +57,18 @@ export {
DebuggerEvent
} from './apiWatch'
export {
EffectScope,
effectScope,
onScopeDispose,
getCurrentScope
} from './reactivity/effectScope'
export { TrackOpTypes, TriggerOpTypes } from './reactivity/operations'
export { h } from './h'
export { getCurrentInstance } from './currentInstance'
export { useSlots, useAttrs } from './apiSetup'
export { nextTick } from 'core/util'
export * from './apiLifecycle'

View File

@ -0,0 +1,131 @@
import Watcher from 'core/observer/watcher'
import { warn } from 'core/util'
export let activeEffectScope: EffectScope | undefined
export class EffectScope {
/**
* @internal
*/
active = true
/**
* @internal
*/
effects: Watcher[] = []
/**
* @internal
*/
cleanups: (() => void)[] = []
/**
* only assigned by undetached scope
* @internal
*/
parent: EffectScope | undefined
/**
* record undetached scopes
* @internal
*/
scopes: EffectScope[] | undefined
/**
* track a child scope's index in its parent's scopes array for optimized
* removal
* @internal
*/
private index: number | undefined
constructor(detached = false) {
if (!detached && activeEffectScope) {
this.parent = activeEffectScope
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this
) - 1
}
}
run<T>(fn: () => T): T | undefined {
if (this.active) {
const currentEffectScope = activeEffectScope
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = currentEffectScope
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
/**
* This should only be called on non-detached scopes
* @internal
*/
on() {
activeEffectScope = this
}
/**
* This should only be called on non-detached scopes
* @internal
*/
off() {
activeEffectScope = this.parent
}
stop(fromParent?: boolean) {
if (this.active) {
let i, l
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].teardown()
}
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true)
}
}
// nested scope, dereference from parent to avoid memory leaks
if (this.parent && !fromParent) {
// optimized O(1) removal
const last = this.parent.scopes!.pop()
if (last && last !== this) {
this.parent.scopes![this.index!] = last
last.index = this.index!
}
}
this.active = false
}
}
}
export function effectScope(detached?: boolean) {
return new EffectScope(detached)
}
export function recordEffectScope(
effect: Watcher,
scope: EffectScope | undefined = activeEffectScope
) {
if (scope && scope.active) {
scope.effects.push(effect)
}
}
export function getCurrentScope() {
return activeEffectScope
}
export function onScopeDispose(fn: () => void) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn)
} else if (__DEV__) {
warn(
`onScopeDispose() is called when there is no active effect scope` +
` to be associated with.`
)
}
}

View File

@ -107,7 +107,7 @@ describe('Instance methods lifecycle', () => {
vm.$watch('a', () => {})
vm.$destroy()
expect(vm._watcher.active).toBe(false)
expect(vm._watchers.every(w => !w.active)).toBe(true)
expect(vm._scope.effects.every(w => !w.active)).toBe(true)
})
it('remove self from data observer', () => {

View File

@ -10,10 +10,13 @@ import {
triggerRef,
shallowRef,
h,
onMounted
onMounted,
getCurrentInstance,
effectScope
} from 'v3'
import { nextTick } from 'core/util'
import { set } from 'core/observer'
import { Component } from 'typescript/component'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
@ -1008,35 +1011,6 @@ describe('api: watch', () => {
expect(cb).toHaveBeenCalledTimes(1)
})
test('watching keypath', async () => {
const spy = vi.fn()
const Comp = {
render() {},
data() {
return {
a: {
b: 1
}
}
},
watch: {
'a.b': spy
},
created(this: any) {
this.$watch('a.b', spy)
},
mounted(this: any) {
this.a.b++
}
}
const root = document.createElement('div')
new Vue(Comp).$mount(root)
await nextTick()
expect(spy).toHaveBeenCalledTimes(2)
})
it('watching sources: ref<any[]>', async () => {
const foo = ref([1])
const spy = vi.fn()
@ -1062,25 +1036,24 @@ describe('api: watch', () => {
})
// vuejs/core#4158
// TODO
// test.skip('watch should not register in owner component if created inside detached scope', () => {
// let instance: Component
// const Comp = {
// setup() {
// instance = getCurrentInstance()!.proxy
// effectScope(true).run(() => {
// watch(
// () => 1,
// () => {}
// )
// })
// return () => ''
// }
// }
// const root = document.createElement('div')
// new Vue(Comp).$mount(root)
// // should not record watcher in detached scope and only the instance's
// // own update effect
// expect(instance!.scope.effects.length).toBe(1)
// })
test('watch should not register in owner component if created inside detached scope', () => {
let instance: Component
const Comp = {
setup() {
instance = getCurrentInstance()!.proxy
effectScope(true).run(() => {
watch(
() => 1,
() => {}
)
})
return () => ''
}
}
const root = document.createElement('div')
new Vue(Comp).$mount(root)
// should not record watcher in detached scope and only the instance's
// own update effect
expect(instance!._scope.effects.length).toBe(1)
})
})

View File

@ -0,0 +1,282 @@
import { nextTick } from 'core/util'
import {
watch,
watchEffect,
reactive,
computed,
ref,
ComputedRef,
EffectScope,
onScopeDispose,
getCurrentScope
} from 'v3/index'
import { effect } from 'v3/reactivity/effect'
describe('reactivity/effectScope', () => {
it('should run', () => {
const fnSpy = vi.fn(() => {})
new EffectScope().run(fnSpy)
expect(fnSpy).toHaveBeenCalledTimes(1)
})
it('should accept zero argument', () => {
const scope = new EffectScope()
expect(scope.effects.length).toBe(0)
})
it('should return run value', () => {
expect(new EffectScope().run(() => 1)).toBe(1)
})
it('should collect the effects', () => {
const scope = new EffectScope()
scope.run(() => {
let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
})
expect(scope.effects.length).toBe(1)
})
it('stop', () => {
let dummy, doubled
const counter = reactive({ num: 0 })
const scope = new EffectScope()
scope.run(() => {
effect(() => (dummy = counter.num))
effect(() => (doubled = counter.num * 2))
})
expect(scope.effects.length).toBe(2)
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
expect(doubled).toBe(14)
scope.stop()
counter.num = 6
expect(dummy).toBe(7)
expect(doubled).toBe(14)
})
it('should collect nested scope', () => {
let dummy, doubled
const counter = reactive({ num: 0 })
const scope = new EffectScope()
scope.run(() => {
effect(() => (dummy = counter.num))
// nested scope
new EffectScope().run(() => {
effect(() => (doubled = counter.num * 2))
})
})
expect(scope.effects.length).toBe(1)
expect(scope.scopes!.length).toBe(1)
expect(scope.scopes![0]).toBeInstanceOf(EffectScope)
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
expect(doubled).toBe(14)
// stop the nested scope as well
scope.stop()
counter.num = 6
expect(dummy).toBe(7)
expect(doubled).toBe(14)
})
it('nested scope can be escaped', () => {
let dummy, doubled
const counter = reactive({ num: 0 })
const scope = new EffectScope()
scope.run(() => {
effect(() => (dummy = counter.num))
// nested scope
new EffectScope(true).run(() => {
effect(() => (doubled = counter.num * 2))
})
})
expect(scope.effects.length).toBe(1)
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
expect(doubled).toBe(14)
scope.stop()
counter.num = 6
expect(dummy).toBe(7)
// nested scope should not be stopped
expect(doubled).toBe(12)
})
it('able to run the scope', () => {
let dummy, doubled
const counter = reactive({ num: 0 })
const scope = new EffectScope()
scope.run(() => {
effect(() => (dummy = counter.num))
})
expect(scope.effects.length).toBe(1)
scope.run(() => {
effect(() => (doubled = counter.num * 2))
})
expect(scope.effects.length).toBe(2)
counter.num = 7
expect(dummy).toBe(7)
expect(doubled).toBe(14)
scope.stop()
})
it('can not run an inactive scope', () => {
let dummy, doubled
const counter = reactive({ num: 0 })
const scope = new EffectScope()
scope.run(() => {
effect(() => (dummy = counter.num))
})
expect(scope.effects.length).toBe(1)
scope.stop()
scope.run(() => {
effect(() => (doubled = counter.num * 2))
})
expect('cannot run an inactive effect scope.').toHaveBeenWarned()
expect(scope.effects.length).toBe(1)
counter.num = 7
expect(dummy).toBe(0)
expect(doubled).toBe(undefined)
})
it('should fire onScopeDispose hook', () => {
let dummy = 0
const scope = new EffectScope()
scope.run(() => {
onScopeDispose(() => (dummy += 1))
onScopeDispose(() => (dummy += 2))
})
scope.run(() => {
onScopeDispose(() => (dummy += 4))
})
expect(dummy).toBe(0)
scope.stop()
expect(dummy).toBe(7)
})
it('should warn onScopeDispose() is called when there is no active effect scope', () => {
const spy = vi.fn()
const scope = new EffectScope()
scope.run(() => {
onScopeDispose(spy)
})
expect(spy).toHaveBeenCalledTimes(0)
onScopeDispose(spy)
expect(
'onScopeDispose() is called when there is no active effect scope to be associated with.'
).toHaveBeenWarned()
scope.stop()
expect(spy).toHaveBeenCalledTimes(1)
})
it('should dereference child scope from parent scope after stopping child scope (no memleaks)', () => {
const parent = new EffectScope()
const child = parent.run(() => new EffectScope())!
expect(parent.scopes!.includes(child)).toBe(true)
child.stop()
expect(parent.scopes!.includes(child)).toBe(false)
})
it('test with higher level APIs', async () => {
const r = ref(1)
const computedSpy = vi.fn()
const watchSpy = vi.fn()
const watchEffectSpy = vi.fn()
let c: ComputedRef
const scope = new EffectScope()
scope.run(() => {
c = computed(() => {
computedSpy()
return r.value + 1
})
watch(r, watchSpy)
watchEffect(() => {
watchEffectSpy()
r.value
})
})
c!.value // computed is lazy so trigger collection
expect(computedSpy).toHaveBeenCalledTimes(1)
expect(watchSpy).toHaveBeenCalledTimes(0)
expect(watchEffectSpy).toHaveBeenCalledTimes(1)
r.value++
c!.value
await nextTick()
expect(computedSpy).toHaveBeenCalledTimes(2)
expect(watchSpy).toHaveBeenCalledTimes(1)
expect(watchEffectSpy).toHaveBeenCalledTimes(2)
scope.stop()
r.value++
c!.value
await nextTick()
// should not trigger anymore
expect(computedSpy).toHaveBeenCalledTimes(2)
expect(watchSpy).toHaveBeenCalledTimes(1)
expect(watchEffectSpy).toHaveBeenCalledTimes(2)
})
it('getCurrentScope() stays valid when running a detached nested EffectScope', () => {
const parentScope = new EffectScope()
parentScope.run(() => {
const currentScope = getCurrentScope()
expect(currentScope).toBeDefined()
const detachedScope = new EffectScope(true)
detachedScope.run(() => {})
expect(getCurrentScope()).toBe(currentScope)
})
})
})

View File

@ -1,8 +1,9 @@
import type VNode from '../src/core/vdom/vnode'
import type Watcher from '../src/core/observer/watcher'
import { ComponentOptions } from './options'
import { ComponentOptions, SetupContext } from './options'
import { ScopedSlotsData, VNodeChildren, VNodeData } from './vnode'
import { GlobalAPI } from './global-api'
import { EffectScope } from 'v3'
// TODO this should be using the same as /component/
@ -16,7 +17,7 @@ export declare class Component {
static superOptions: Record<string, any>
static extendOptions: Record<string, any>
static sealedOptions: Record<string, any>
static super: Component
static super: typeof Component
// assets
static directive: GlobalAPI['directive']
static component: GlobalAPI['component']
@ -58,7 +59,7 @@ export declare class Component {
key: string | number
) => void
$watch: (
expOrFn: string | Function,
expOrFn: string | (() => any),
cb: Function,
options?: Record<string, any>
) => Function
@ -66,7 +67,7 @@ export declare class Component {
$once: (event: string, fn: Function) => Component
$off: (event?: string | Array<string>, fn?: Function) => Component
$emit: (event: string, ...args: Array<any>) => Component
$nextTick: (fn: Function) => void | Promise<any>
$nextTick: (fn: (...args: any[]) => any) => void | Promise<any>
$createElement: (
tag?: string | Component,
data?: Record<string, any>,
@ -77,11 +78,12 @@ export declare class Component {
_uid: number | string
_name: string // this only exists in dev mode
_isVue: true
__v_skip: true
_self: Component
_renderProxy: Component
_renderContext?: Component
_watcher: Watcher | null
_watchers: Array<Watcher>
_scope: EffectScope
_computedWatchers: { [key: string]: Watcher }
_data: Record<string, any>
_props: Record<string, any>
@ -99,6 +101,7 @@ export declare class Component {
// @v3
_setupState?: Record<string, any>
_setupContext?: SetupContext
_attrsProxy?: Record<string, any>
_slotsProxy?: Record<string, () => VNode[]>
_preWatchers?: Watcher[]
@ -193,8 +196,8 @@ export declare class Component {
_ssrAttrs: Function
_ssrDOMProps: Function
_ssrClass: Function
_ssrStyle: Function;
_ssrStyle: Function
// allow dynamic method registration
[key: string]: any
// [key: string]: any
}

View File

@ -1,5 +1,6 @@
import { Config } from '../src/core/config'
import { Component } from './component'
import { ComponentOptions } from './options'
declare interface GlobalAPI {
// new(options?: any): Component
@ -9,7 +10,7 @@ declare interface GlobalAPI {
config: Config
util: Object
extend: (options: Object) => typeof Component
extend: (options: typeof Component | ComponentOptions) => typeof Component
set: <T>(target: Object | Array<T>, key: string | number, value: T) => T
delete: <T>(target: Object | Array<T>, key: string | number) => void
nextTick: (fn: Function, context?: Object) => void | Promise<any>

View File

@ -100,7 +100,7 @@ declare type ComponentOptions = {
_renderChildren?: Array<VNode> | null
_componentTag: string | null
_scopeId: string | null
_base: Component
_base: typeof Component
}
declare type PropOptions = {

View File

@ -6,7 +6,7 @@ declare type VNodeChildren =
| string
declare type VNodeComponentOptions = {
Ctor: Component
Ctor: typeof Component
propsData?: Object
listeners?: Record<string, Function | Function[]>
children?: Array<VNode>