您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
让你熟悉又陌生的vue-property-decorator
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
让你熟悉又陌生的vue-property-decorator
自猿其说Tech
2022-01-10
IP归属:未知
96440浏览
Vue
### 1 背景 我们在Vue+ts开发的时候一般都会引入vue-property-decorator扩展库,它所提供的装饰器和函数让我们的开发变得更加方便,快捷。那么这个扩展库支持哪些功能,它帮我们做了哪些事情呢? ### 2 简介 vue-property-decorator是一个Vue的扩展库,完全依赖于vue-class-component。它为使用class风格的Vue组件提供了ECMAScript / TypeScript的装饰器。 星云( http://star.jdl.cn/ )给我们提供的PC端和M端通用框架中也都集成了这两个扩展库供我们直接使用。 ### 3 版本 ![](//img1.jcloudcs.com/developer.jdcloud.com/d5649484-6930-410c-8c78-f62fe37137a420220110142204.png) 当前最新版本是9.1.2,它的发布候选版本是10.0.0-rc.3版本,由于完全依赖于vue-class-component,所依赖的vue版本也是继承过来,9.1.2版本针对vue2,10.0.0-rc.3版本针对vue3。 vue3是未来趋势,因此本文重点介绍新版本:10.0.0-rc.3的用法及源码。 ### 4 功能 #### 4.1 版本9.1.2 此版本依赖vue-class-component的7.2.3版本,给我们提供的功能是一些装饰器和一个函数(Mixins) - @Prop - @PropSync - @Model - @ModelSync - @Watch - @Provide - @Inject - @ProvideReactive - @InjectReactive - @Emit - @Ref - @VModel - @Component (provided by vue-class-component) - Mixins (the helper function named mixins provided by vue-class-component) #### 4.2 版本 10.0.0-rc.3 此发布候选版本依赖vue-class-component的8.0.0-rc.1版本,保留整合了几个常用的装饰器,以及从vue-class-component重新导出的装饰器和方法。 - @Prop - @Model - @Watch - @Provide - @Inject - @Emit - @Ref 从vue-class-component导出: - @Options - mixins - Vue 我们分别来看一下用法以及源码。熟悉用法的小伙伴可以直接跳过介绍去看源码的介绍。 ##### 4.2.1 @Prop 父子组件之间单向传递数据。 每个prop的默认值需要定义为与所示的示例代码相同。不支持定义每个默认属性,如@Prop() prop = 'default value'。 ```javascript import { Vue, Prop } from 'vue-property-decorator' export default class YourComponent extends Vue { @Prop(Number) readonly propA: number | undefined @Prop({ default: 'default value' }) readonly propB!: string @Prop([String, Boolean]) readonly propC: string | boolean | undefined } //==================================等价于================================== export default { props: { propA: Number, propB: { default: 'default value', }, propC: { type: [String, Boolean], }, }, } ``` ##### 4.2.2 @Model Vue组件提供model: {prop?: string, event?: string}让我们可以定制prop和event. 默认情况下,一个组件上的v-model 会把 value用作 prop且把 input用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop来达到不同的目的。使用model选项可以回避这些情况产生的冲突。 第一个参数是组件从它的父组件接收的prop的名称。如果将名称设置为modelValue,则组件可以使用v-model。第二个参数与@Prop装饰器的参数相同。 ```javascript import { Vue, Model } from 'vue-property-decorator' export default class YourComponent extends Vue { @Model('modelValue', { type: String, default: 'Default Value' }) readonly value!: string } //==================================等价于================================== export default { props: { modelValue: { type: String, default: 'Default Value', }, }, emits: ['update:modelValue'], computed: { value: { get() { return this.modelValue }, set(newValue) { this.$emit('update:modelValue') }, }, }, } ``` ##### 4.2.3 @Watch 侦听属性,观察和响应 Vue 实例上的数据变动。 ```javascript import { Vue, Watch } from 'vue-property-decorator' export default class YourComponent extends Vue { @Watch('child') onChildChanged(val: string, oldVal: string) {} @Watch('person', { immediate: true, deep: true }) onPersonChanged1(val: Person, oldVal: Person) {} @Watch('person') onPersonChanged2(val: Person, oldVal: Person) {} } //==================================等价于================================== export default { watch: { child: [ { handler: 'onChildChanged', }, ], person: [ { handler: 'onPersonChanged1', immediate: true, deep: true, }, { handler: 'onPersonChanged2', }, ], }, methods: { onChildChanged(val, oldVal) {}, onPersonChanged1(val, oldVal) {}, onPersonChanged2(val, oldVal) {}, }, } ``` ##### 4.2.4 @Provide 祖先组件通过provide提供变量。 如果将reactive设置为true,则所提供的值将用computed函数包装。带有响应式选项的值将其新值分派给子组件。 ```javascript import { Vue, Provide } from 'vue-property-decorator' const symbolKey = Symbol() export class MyComponent extends Vue { @Provide() foo = 'foo' @Provide({ to: 'bar' }) baz = 'bar' @Provide({ to: symbolKey }) nice = 'nice' @Provide({ reactive: true }) age = 30 } //==================================等价于================================== import { computed } from 'vue' const symbolKey = Symbol() export default { data() { return { foo: 'foo', baz: 'bar', nice: 'nice', age: 30, } }, provide() { return { foo: this.key, bar: this.baz, [symbolKey]: this.nice, age: computed(() => this.age), } }, } ``` ##### 4.2.5 @Inject 后代组件通过inject注入变量。 ```javascript import { Vue, Inject } from 'vue-property-decorator' export class MyComponent extends Vue { @Inject() foo!: string @Inject({ from: 'bar' }) baz!: string @Inject({ default: '' }) nice!: string } //==================================等价于================================== export default { inject: { foo: 'foo', baz: { from: 'bar', }, nice: { default: '', }, }, } ``` ##### 4.2.6 @Emit 子组件触发父组件的自定义方法并传递数据。 由@Emit $修饰的函数会发出它们的返回值和它们的原始参数。如果返回值是一个promise,则在发出之前解析它。 如果事件的名称没有通过event参数提供,则使用函数名。在这种情况下,驼峰命名将被转换为中划线命名。 ```javascript import { Vue, Emit } from 'vue-property-decorator' export default class YourComponent extends Vue { count = 0 @Emit() addToCount(n: number) { this.count += n } @Emit('reset') resetCount() { this.count = 0 } @Emit() returnValue() { return 10 } @Emit() onInputChange(e) { return e.target.value } @Emit() promise() { return new Promise((resolve) => { setTimeout(() => { resolve(20) }, 0) }) } } //==================================等价于================================== export default { data() { return { count: 0, } }, methods: { addToCount(n) { this.count += n this.$emit('add-to-count', n) }, resetCount() { this.count = 0 this.$emit('reset') }, returnValue() { this.$emit('return-value', 10) }, onInputChange(e) { this.$emit('on-input-change', e.target.value, e) }, promise() { const promise = new Promise((resolve) => { setTimeout(() => { resolve(20) }, 0) }) promise.then((value) => { this.$emit('promise', value) }) }, }, } ``` ##### 4.2.7 @Ref 接收一个可选参数,用来指向元素或子组件的引用信息。如果没有提供这个参数,会使用装饰器后面的属性名充当参数。 ```javascript import { Vue, Ref } from 'vue-property-decorator' import AnotherComponent from '@/path/to/another-component.vue' export default class YourComponent extends Vue { @Ref() readonly anotherComponent!: AnotherComponent @Ref('aButton') readonly button!: HTMLButtonElement } //==================================等价于================================== export default { computed() { anotherComponent: { cache: false, get() { return this.$refs.anotherComponent as AnotherComponent } }, button: { cache: false, get() { return this.$refs.aButton as HTMLButtonElement } } } } ``` ### 5 源码 #### 5.1 vue-property-decorator(v10.0.0-rc.3) 从git上下载vue-property-decorator的源码,切换到v10.0.0-rc.3版本,代码结构如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/c2895b2b-1a63-4a41-81f2-e19fb1713ccf20220110142822.png) 其中src中的代码就是提供给我们的一些装饰器的源码。 ##### 5.1.1 index.ts 统一导出装饰器及方法,所有提供的方法统一出口。 ```javascript /** vue-property-decorator MIT LICENSE copyright 2020 kaorun343 */ export { mixins, Options, Vue } from 'vue-class-component' export { Emit } from './decorators/Emit' export { Inject } from './decorators/Inject' export { Model } from './decorators/Model' export { Prop } from './decorators/Prop' export { Provide } from './decorators/Provide' export { Ref } from './decorators/Ref' export { Watch } from './decorators/Watch' ``` ##### 5.1.2 Prop.ts 装饰器@Prop的源码:将传入的参数挂载到componentOptions组件的props上。 ```javascript import { createDecorator, PropOptions, VueDecorator } from 'vue-class-component' type Constructor = (new () => any) | SymbolConstructor /** * Decorator for prop options * @param propOptions the options for the prop */ //把当前属性的propOptions赋值给类组件的props属性 export function Prop( propOptions?: Constructor | Constructor[] | PropOptions, ): VueDecorator { return createDecorator((componentOptions, key) => { componentOptions.props ||= Object.create(null) componentOptions.props[key] = propOptions }) } ``` ##### 5.1.3 Model.ts 装饰器@Model的源码:将传入的参数挂载到componentOptions组件的props上,emits属性绑定传入的事件名,computed计算属性中设置对于传入的propName属性的双向数据绑定。 ```javascript import { createDecorator, PropOptions, Vue, VueDecorator, } from 'vue-class-component' type Constructor = (new () => any) | SymbolConstructor /** * Decorator for v-model * @param propName e.g. `modelValue` * @param propOptions the options for the prop */ // 绑定传入的事件名及把当前属性的propOptions赋值给类组件的props属性 export function Model( propName: string, propOptions?: Constructor | Constructor[] | PropOptions, ): VueDecorator { return createDecorator((componentOptions, key) => { const eventName = `update:${propName}` componentOptions.props ||= Object.create(null) componentOptions.props[propName] = propOptions componentOptions.emits ||= [] componentOptions.emits.push(eventName) componentOptions.computed ||= Object.create(null) componentOptions.computed[key] = { get(this: any) { return this[propName] }, set(this: Vue, newValue: any) { this.$emit(eventName, newValue) }, } }) } ``` ##### 5.1.4 Watch.ts 装饰器@Watch的源码:通过判断不同类型的参数处理成数组添加到对应的path参数中。 ```javascript import { WatchOptions } from 'vue' import { createDecorator, VueDecorator } from 'vue-class-component' /** * Decorator for watch options * @param path the path or the expression to observe * @param watchOptions */ // 接收要监听的path参数,以及需要设置的watchOptions对象,赋值给类组件的watch属性 export function Watch(path: string, watchOptions?: WatchOptions): VueDecorator { return createDecorator((componentOptions, handler) => { componentOptions.watch ||= Object.create(null) const watch: any = componentOptions.watch // 当前类组件上path值的watch已经存在并且是一个对象的话赋值成一个数组,不存在则赋值为空数组 if (typeof watch[path] === 'object' && !Array.isArray(watch[path])) { watch[path] = [watch[path]] } else if (typeof watch[path] === 'undefined') { watch[path] = [] } // 向数组中添加对应watchOptions中的属性 watch[path].push({ handler, ...watchOptions }) }) } ``` ##### 5.1.5 Provide.ts 装饰器@Provide的源码:将传递过来的不同类型的参数处理绑定到componentOptions组件的provide上。 ```javascript import { computed } from 'vue' import { createDecorator, VueDecorator } from 'vue-class-component' // 将9.1.2版本中的@ProvideReactive装饰器通过传参做了结合 export type ProvideOption = { to?: string | symbol reactive?: boolean } /** * Decorator for provide options */ // options对象中只接收to和reactive两个参数 export function Provide(options?: ProvideOption): VueDecorator { return createDecorator((componentOptions, key) => { const originalProvide = componentOptions.provide componentOptions.provide = function (this: any) { const providedValue = // 判断是函数则改变this指向赋值给providedValue typeof originalProvide === 'function' ? originalProvide.call(this) : originalProvide // 最终抛出当前的providedValue以及传入的to的值,响应式判断 return { ...providedValue, [options?.to || key]: options?.reactive ? computed(() => this[key]) : this[key], } } }) } ``` ##### 5.1.6 Inject.ts 装饰器@Inject的源码:接收options中的from和default属性注入到类组件的setup函数中。 ```javascript import { inject, InjectionKey } from 'vue' import { createDecorator, VueDecorator } from 'vue-class-component' export type InjectOptions = { from?: string | InjectionKey<any> default?: any } /** * Decorator for inject options * @param options the options for the injected value */ // from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol) // default 属性是降级情况下使用的 value export function Inject( options: InjectOptions = Object.create(null), ): VueDecorator { return createDecorator((componentOptions, key) => { const originalSetup = componentOptions.setup componentOptions.setup = (props, ctx) => { const result = originalSetup?.(props, ctx) const injectedValue = inject(options.from || key, options.default) return { ...result, [key]: injectedValue, } } }) } ``` ##### 5.1.7 Emit.ts 装饰器@Emit的源码:处理传入的不同类型的event事件绑定到类组件的methods中。 ```javascript import { createDecorator, VueDecorator } from 'vue-class-component' // Code copied from Vue/src/shared/util.js const hyphenateRE = /\B([A-Z])/g // 驼峰命名转为中划线 const hyphenate = (str: string) => str.replace(hyphenateRE, '-$1').toLowerCase() /** * Decorator of an event-emitter function * @param event The name of the event */ export function Emit(event?: string): VueDecorator { return createDecorator((componentOptions, propertyKey) => { // 可以接收一个事件名或者使用函数名 const emitName = event || hyphenate(propertyKey) componentOptions.emits ||= [] componentOptions.emits.push(emitName) const original = componentOptions.methods[propertyKey] componentOptions.methods[propertyKey] = function emitter(...args: any[]) { const emit = (returnValue: any) => { if (returnValue === undefined) { if (args.length === 0) { this.$emit(emitName) } else if (args.length === 1) { this.$emit(emitName, args[0]) } else { this.$emit(emitName, ...args) } } else { args.unshift(returnValue) this.$emit(emitName, ...args) } } const returnValue: any = original.apply(this, args) // 如果传入是Promise则返回.then后的值 if (isPromise(returnValue)) { returnValue.then(emit) } else { emit(returnValue) } return returnValue } }) } function isPromise(obj: any): obj is Promise<any> { return obj instanceof Promise || (obj && typeof obj.then === 'function') } ``` ##### 5.1.8 Ref.ts 装饰器@Ref的源码:给类组件的$refs中添加接收到的元素或子组件的引用信息。 ```javascript import { createDecorator, Vue, VueDecorator } from 'vue-class-component' /** * decorator of a ref prop * @param refKey the ref key defined in template */ // 接收一个可选参数绑定 export function Ref(refKey?: string): VueDecorator { return createDecorator((componentOptions, key) => { componentOptions.computed ||= Object.create(null) componentOptions.computed[key] = { cache: false, get(this: Vue) { return this.$refs[refKey || key] }, } }) } ``` #### 5.2 vue-class-component(8.0.0-rc.1) 其中一些方法和接口都是直接从vue-class-component导入, 感兴趣的小伙伴可以去下载vue-class-component的源码来看一下。 https://github.com/vuejs/vue-class-component.git 切换到next分支。 ##### 5.2.1 src/helpers.ts - Options 装饰器将class类转化为Vue组件 ![](//img1.jcloudcs.com/developer.jdcloud.com/fb7fa634-fa2c-4b99-b417-f6f80079979b20220110143239.png) - VueDecorator 定义Vue装饰器的接口。 ![](//img1.jcloudcs.com/developer.jdcloud.com/7e5bc367-e4b7-4e47-892d-7e7879460ac420220110143314.png) - createDecorator 提供创建自定义装饰器的方法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/1a66befa-2e7b-4395-b792-c02f789dc3ec20220110143334.png) ##### 5.2.2 src/props.ts - PropOptions 定义装饰器传参的接口。 ![](//img1.jcloudcs.com/developer.jdcloud.com/52235dca-97fe-44e4-9e45-ac3e9ea5e81120220110143353.png) ### 6 总结 本文简单介绍了一下vue-property-decorator扩展库针对vue3的新版本的几个装饰器的变化,用法以及源码实现。 装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。可以让我们的代码变得更加清晰整洁。开发过程中我们根据实际情况可以将一些公共方法用装饰器的方式去写,方便使用。因为vue-property-decorator完全依赖于vue-class-component,所以对于其中一些常用的方法更底层的一些实现可以继续下载源码研究一下。 不足之处,感谢指出,欢迎小伙伴们一起交流探讨。 参考 https://www.npmjs.com/package/vue-property-decorator https://github.com/kaorun343/vue-property-decorator https://es6.ruanyifeng.com/#docs/decorator ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 作者:马迪
原创文章,需联系作者,授权转载
上一篇:京东探索研究院NLP水平超越微软 织女Vega v1模型位居GLUE榜首
下一篇:京东云ClickHouse和ES双引擎设计在零售选品中的应用实践
相关文章
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
京东mPaaS平台之Android组件化系统私有化部署改造实践!
【技术干货】企业级扫描平台EOS-Jenkins集群进阶之路
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号