diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 34631eec..dbe4d8e4 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -137,8 +137,16 @@ class InternalComponent implements PublicInstanceMethods { _isVue: boolean = true _inactiveRoot: boolean = false - constructor() { - initializeComponentInstance(this as any) + constructor(props?: object) { + if (props === void 0) { + initializeComponentInstance(this as any) + } else { + // the presence of the props argument indicates that this class is being + // instantiated as a mixin, and should expose the props on itself + // so that the extended class constructor (and property initializers) can + // access $props. + this.$props = props + } } // to be set by renderer during mount diff --git a/packages/core/src/componentState.ts b/packages/core/src/componentState.ts index 594d8b3d..7e74bb1a 100644 --- a/packages/core/src/componentState.ts +++ b/packages/core/src/componentState.ts @@ -6,12 +6,21 @@ const internalRE = /^_|^\$/ export function initializeState(instance: ComponentInstance) { const { data } = instance.$options const rawData = (instance._rawData = (data ? data.call(instance) : {}) as any) + extractInitializers(instance, rawData) + instance.$data = observable(rawData || {}) +} + +// extract properties initialized in a component's constructor +export function extractInitializers( + instance: ComponentInstance, + data: any = {} +): any { const keys = Object.keys(instance) for (let i = 0; i < keys.length; i++) { const key = keys[i] if (!internalRE.test(key)) { - rawData[key] = (instance as any)[key] + data[key] = (instance as any)[key] } } - instance.$data = observable(rawData || {}) + return data } diff --git a/packages/core/src/optional/mixin.ts b/packages/core/src/optional/mixin.ts index f6de2861..cf2b2c09 100644 --- a/packages/core/src/optional/mixin.ts +++ b/packages/core/src/optional/mixin.ts @@ -6,6 +6,7 @@ import { mergeComponentOptions } from '../componentOptions' import { normalizePropsOptions } from '../componentProps' +import { extractInitializers } from '../componentState' import { isFunction } from '@vue/shared' interface ComponentConstructor { @@ -36,14 +37,25 @@ export function mixins(...args: any[]): any { let options: ComponentOptions = {} args.forEach(mixin => { if (isFunction(mixin)) { - options = mergeComponentOptions( - options, - resolveComponentOptionsFromClass(mixin) - ) + const Class = mixin + mixin = resolveComponentOptionsFromClass(Class) + // in order to extract properties initialized in the mixin's constructor, + // we create an instance of it and pass in the actual props - this + // short-circuits the normal component initialization and allows us to + // relatively-cheaply extract the properties added in the constructor. + function extractData() { + return extractInitializers(new Class(this.$props)) + } + const { data } = mixin + mixin.data = data + ? function() { + return Object.assign(data.call(this), extractData.call(this)) + } + : extractData } else { mixin.props = normalizePropsOptions(mixin.props) - options = mergeComponentOptions(options, mixin) } + options = mergeComponentOptions(options, mixin) }) return createComponentClassFromOptions(options) }