Skip to content

Commit 9d11761

Browse files
committed
fix: avoid extended mixin missing own options (fix #467)
1 parent 22dce0b commit 9d11761

File tree

3 files changed

+107
-53
lines changed

3 files changed

+107
-53
lines changed

src/helpers.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export function Options<V extends Vue>(
66
options: ComponentOptions & ThisType<V>
77
): <VC extends VueConstructor<VueBase>>(target: VC) => VC {
88
return (Component) => {
9-
Component.__vccBase = options
9+
Component.__o = options
1010
return Component
1111
}
1212
}
@@ -34,13 +34,13 @@ export function createDecorator(
3434
typeof target === 'function'
3535
? target
3636
: (target.constructor as VueConstructor)
37-
if (!Ctor.__vccDecorators) {
38-
Ctor.__vccDecorators = []
37+
if (!Ctor.__d) {
38+
Ctor.__d = []
3939
}
4040
if (typeof index !== 'number') {
4141
index = undefined
4242
}
43-
Ctor.__vccDecorators.push((options) => factory(options, key, index))
43+
Ctor.__d.push((options) => factory(options, key, index))
4444
}
4545
}
4646

@@ -59,8 +59,8 @@ export type MixedVueBase<Mixins extends VueMixin[]> = Mixins extends (infer T)[]
5959
export function mixins<T extends VueMixin[]>(...Ctors: T): MixedVueBase<T>
6060
export function mixins(...Ctors: VueMixin[]): VueConstructor {
6161
return class MixedVue extends Vue {
62-
static __vccExtend(options: ComponentOptions) {
63-
Ctors.forEach((Ctor) => Ctor.__vccExtend(options))
62+
static __b: ComponentOptions = {
63+
mixins: Ctors.map((Ctor) => Ctor.__vccOpts),
6464
}
6565

6666
constructor(...args: any[]) {

src/vue.ts

+62-47
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,52 @@ function getSuper(Ctor: typeof VueImpl): typeof VueImpl | undefined {
4646
return superProto.constructor as typeof VueImpl
4747
}
4848

49+
function getOwn<T extends Object, K extends keyof T>(
50+
value: T,
51+
key: K
52+
): T[K] | undefined {
53+
return value.hasOwnProperty(key) ? value[key] : undefined
54+
}
55+
4956
export interface VueStatic {
5057
// -- Class component configs
5158

52-
/** @internal */
53-
__vccCache?: ComponentOptions
54-
55-
/** @internal */
56-
__vccBase?: ComponentOptions
57-
58-
/** @internal */
59-
__vccDecorators?: ((options: ComponentOptions) => void)[]
60-
61-
/** @internal */
62-
__vccExtend: (options: ComponentOptions) => void
63-
64-
/** @internal */
65-
__vccHooks: string[]
66-
67-
/** @internal */
59+
/**
60+
* @internal
61+
* The cache of __vccOpts
62+
*/
63+
__c?: ComponentOptions
64+
65+
/**
66+
* @internal
67+
* The base options specified to this class.
68+
*/
69+
__b?: ComponentOptions
70+
71+
/**
72+
* @internal
73+
* Component options specified with `@Options` decorator
74+
*/
75+
__o?: ComponentOptions
76+
77+
/**
78+
* @internal
79+
* Decorators applied to this class.
80+
*/
81+
__d?: ((options: ComponentOptions) => void)[]
82+
83+
/**
84+
* @internal
85+
* Registered (lifecycle) hooks that will be ported from class methods
86+
* into component options.
87+
*/
88+
__h: string[]
89+
90+
/**
91+
* @internal
92+
* Final component options object that Vue core processes.
93+
* The name must be __vccOpts since it is the contract with the Vue core.
94+
*/
6895
__vccOpts: ComponentOptions
6996

7097
// --- Vue Loader etc injections
@@ -147,17 +174,7 @@ export interface VueConstructor<V extends VueBase = Vue> extends VueMixin<V> {
147174
}
148175

149176
class VueImpl {
150-
/** @internal */
151-
static __vccCache?: ComponentOptions
152-
153-
/** @internal */
154-
static __vccBase?: ComponentOptions
155-
156-
/** @internal */
157-
static __vccDecorators?: ((options: ComponentOptions) => void)[]
158-
159-
/** @internal */
160-
static __vccHooks = [
177+
static __h = [
161178
'data',
162179
'beforeCreate',
163180
'created',
@@ -174,35 +191,34 @@ class VueImpl {
174191
'serverPrefetch',
175192
]
176193

177-
/** @internal */
178-
static __vccExtend(options: ComponentOptions) {
179-
options.mixins = options.mixins || []
180-
options.mixins.push(this.__vccOpts)
181-
}
182-
183-
/** @internal */
184194
static get __vccOpts(): ComponentOptions {
185195
// Early return if `this` is base class as it does not have any options
186196
if (this === Vue) {
187197
return {}
188198
}
189199

190-
const cache = this.hasOwnProperty('__vccCache') && this.__vccCache
200+
const Ctor = this as VueConstructor
201+
202+
const cache = getOwn(Ctor, '__c')
191203
if (cache) {
192204
return cache
193205
}
194206

195-
const Ctor = this
196-
197207
// If the options are provided via decorator use it as a base
198-
const options = (this.__vccCache = this.hasOwnProperty('__vccBase')
199-
? { ...this.__vccBase }
200-
: {})
208+
const options: ComponentOptions = { ...getOwn(Ctor, '__o') }
209+
Ctor.__c = options
201210

202211
// Handle super class options
203212
const Super = getSuper(Ctor)
204213
if (Super) {
205-
Super.__vccExtend(options)
214+
options.extends = Super.__vccOpts
215+
}
216+
217+
// Inject base options as a mixin
218+
const base = getOwn(Ctor, '__b')
219+
if (base) {
220+
options.mixins = options.mixins || []
221+
options.mixins.unshift(base)
206222
}
207223

208224
options.methods = { ...options.methods }
@@ -215,7 +231,7 @@ class VueImpl {
215231
}
216232

217233
// hooks
218-
if (Ctor.__vccHooks.indexOf(key) > -1) {
234+
if (Ctor.__h.indexOf(key) > -1) {
219235
;(options as any)[key] = (proto as any)[key]
220236
return
221237
}
@@ -280,8 +296,7 @@ class VueImpl {
280296
return promise ?? plainData
281297
}
282298

283-
const decorators =
284-
this.hasOwnProperty('__vccDecorators') && this.__vccDecorators
299+
const decorators = getOwn(Ctor, '__d')
285300
if (decorators) {
286301
decorators.forEach((fn) => fn(options))
287302
}
@@ -305,7 +320,7 @@ class VueImpl {
305320
}
306321

307322
static registerHooks(keys: string[]): void {
308-
this.__vccHooks.push(...keys)
323+
this.__h.push(...keys)
309324
}
310325

311326
static props(Props: { new (): unknown }): VueConstructor {
@@ -318,8 +333,8 @@ class VueImpl {
318333
})
319334

320335
class PropsMixin extends this {
321-
static __vccExtend(options: ComponentOptions) {
322-
options.props = props
336+
static __b: ComponentOptions = {
337+
props,
323338
}
324339
}
325340
return PropsMixin as VueConstructor

test/specs/test.spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,45 @@ describe('vue-class-component', () => {
348348
expect(root.valueB).toBe(456)
349349
})
350350

351+
it('nested mixins', () => {
352+
class GrandParentA extends Vue {
353+
get a() {
354+
return 'Hello'
355+
}
356+
}
357+
358+
class GrandParentB extends Vue {
359+
get b() {
360+
return 'World'
361+
}
362+
}
363+
364+
class ParentA extends mixins(GrandParentA, GrandParentB) {
365+
get helloWorld() {
366+
return this.a + this.b
367+
}
368+
}
369+
370+
class ParentB extends Vue {
371+
get c() {
372+
return 'Foobar'
373+
}
374+
}
375+
376+
class App extends mixins(ParentA, ParentB) {
377+
get d() {
378+
return 'Test'
379+
}
380+
}
381+
382+
const { root } = mount(App)
383+
expect(root.a).toBe('Hello')
384+
expect(root.b).toBe('World')
385+
expect(root.helloWorld).toBe('HelloWorld')
386+
expect(root.c).toBe('Foobar')
387+
expect(root.d).toBe('Test')
388+
})
389+
351390
it('props class', () => {
352391
class Props {
353392
foo?: string

0 commit comments

Comments
 (0)