vuejs / vue-class-component

ES / TypeScript decorator for class-style Vue components.
MIT License
5.81k stars 429 forks source link

[BUG]v8.0.0-rc.1,使用createDecorator创建自定义装饰器,如果用在多次继承的类里,会导致混乱 #548

Open gowiny opened 3 years ago

gowiny commented 3 years ago

代码文件:prop.ts

import { createDecorator } from 'vue-class-component'

function createProp(params:any, target: any, propertyKey: string,descriptor?: PropertyDescriptor){ createDecorator(function (options, key) { if(!options.props){ options.props = {} } const props = options.props if(descriptor && typeof params.default === 'undefined'){ params.default = function(){ return descriptor.value } if(options.methods){ delete options.methods[key] } } //添加新的prop 到 options.props里 props[key] = params })(target, propertyKey); }

export function Prop(params?:any):any; export function Prop(target: any, propertyKey: string):void; export function Prop(target: any, propertyKey: string,descriptor: PropertyDescriptor):void; export function Prop(target: any={}, propertyKey="",descriptor?: PropertyDescriptor):void | any { if(propertyKey){ createProp({},target,propertyKey,descriptor) }else{ const params = target; return function (target: any, propertyKey: string,descriptor?: PropertyDescriptor) { createProp(params,target,propertyKey,descriptor) } } }


代码文件:my-test-input.vue <template> <div></div> </template> <script lang="ts"> import { Vue} from 'vue-class-component'; import { Prop } from '@/lib-ref/vue-class'; class MyTestSuper extends Vue { @Prop({ type:String, default:'MyTestSuper' }) declare readonly label:string }

class MyTestBase1 extends MyTestSuper { @Prop({ type:String, default:'MyTestBase1' }) declare readonly label:string }

export default class MyTestInput extends MyTestSuper {

} </script>


测试文件:Test.vue

<template> <div class="test"> <my-test-input ref="testComp" ></my-test-input> <el-button @click="test">测试</el-button> </div> </template> <script lang="ts"> import { Options,Vue } from 'vue-class-component'; import { refs} from '@/utils/app-utils'; import MyTestInput from '@/components/my/my-test/my-test-input.vue'; @Options({ components:{ MyTestInput } }) export default class Test extends Vue { test(){ const testComp = refs(this,'testComp') console.log('testComp label',testComp.label) } } </script>


使用createDecorator方法定义了一个新的装饰器:Prop

MyTestSuper 类是基类 MyTestBase1 和 MyTestInput 都继承MyTestSuper 类 MyTestBase1 类里覆盖了MyTestSuper 类的 label属性定义

在测试页面点击按钮测试。 实际输出的 label 值 是 'MyTestBase1' 期望输出的 label 值应该是 MyTestInput 的父类MyTestSuper 里定义的 默认值:'MyTestSuper'


经过调试源码发现,createDecorator 方法里 是把 工厂函数 factory,添加到 类的构造函数的 d 属性里, 但因为多重继承的原因,createDecorator 里取到的 Ctor.d 都是基类 MyTestSuper 的引用,而非重新创建的数组。 所以,所有子类 使用 自定义装饰器 都 添加了一条记录到 基类的 d里了。而子类是引用的 基类 d,导致 MyTestSuper 的所有子类里定义的prop 都应用到所有子类,即便是非直接继承的类。

附上源代码文件: bug-src.zip

gowiny commented 3 years ago

我把createDecorator方法里的源代码修改了一下,请参考

原代码: export function createDecorator( factory: (options: ComponentOptions, key: string, index: number) => void ): VueDecorator { return ( target: VueBase | VueConstructor<VueBase>, key?: any, index?: any ) => { const Ctor = typeof target === 'function' ? target : (target.constructor as VueConstructor) if (!Ctor.__d) { Ctor.__d = [] } if (typeof index !== 'number') { index = undefined } Ctor.__d.push((options) => factory(options, key, index)) } }


修改后的新代码:

export function createDecorator( factory: (options: ComponentOptions, key: string, index: number) => void ): VueDecorator { return ( target: VueBase | VueConstructor<VueBase>, key?: any, index?: any ) => { const Ctor:any = typeof target === 'function' ? target : (target.constructor as VueConstructor) if (!Ctor.__d) { Ctor.__d = [] Ctor.__d.__n = Ctor.name }else if(Ctor.__d.__n !== Ctor.name){ Ctor.__d = [].concat(Ctor.__d) Ctor.__d.__n = Ctor.name } if (typeof index !== 'number') { index = undefined } Ctor.__d.push((options:any) => factory(options, key, index)) } }

就是把原来的 if (!Ctor.d) { Ctor.d = [] }

改为了

if (!Ctor.d) { Ctor.d = [] Ctor.d.n = Ctor.name }else if(Ctor.d.n !== Ctor.name){ Ctor.d = [].concat(Ctor.d) Ctor.d.n = Ctor.name }

Niekvdm commented 2 years ago

Had to use google translate to understand what is going on but this does solve the problem with inherited classes. @gowiny Perhaps you can make a PR on the main branch?

nseb commented 2 years ago

There is a release final date for support vue 3 ?

gowiny commented 2 years ago

I don't know how to PR

df257 commented 2 years ago

这里说中文,外国人看到懂吗。。。