vue-property-decorator是基于vue组织里vue-class-component所作的拓展,先来了解一下vue-class-componentjavascript
vue-class-component是一个Class Decorator,也就是类的装饰器,但目前装饰器在vue中只属于草案阶段.html
vue2.x只有Object一种声明组件的方式, 好比这样:vue
const App = Vue.extend({ // data data() { return { hello: 'world', }; }, computed: { world() { return this.hello + 'world'; }, }, // hooks mounted() { this.sayHello(); }, // methods methods: { sayHello() { console.log(this.hello); }, }, });
用了vue-class-component就成了如下写法:java
import Component from 'vue-class-component'; @Component({ name: 'App' }) class App extends Vue { hello = 'world'; get world() { return this.hello + 'world'; } mounted() { this.sayHello(); } sayHello() { console.log(this.hello); } }
在这个例子中,很容易发现几个疑点:webpack
@Component()
是什么?hello = 'world'
这是什么语法?疑点1:git
对装饰器的有必定了解. 装饰器种类有好几种, vue-class-component
中主要使用了类装饰器.
更多关于装饰器信息请参阅阮老师的文章: ECMAScript6入门es6
看完阮老师所写的文章已经能够解决了疑点1github
简述: @Component
就是一个修饰器, 用来修改类的行为web
疑点2:chrome
在JS语法中, class中都是须要在constructor中给属性赋值, 在chrome上像vue-class-component
中定义class是会报错的,
但vue-class-component
中却又这么作了.
而后咱们看看class经过webpack + babel-loader
解析后会变成什么样子
// 转换前 class App { hello = 'world'; sayHello() { console.log(this.hello); } } // 转换后 function App () { this.hello = 'world' } App.prototype.sayHello = function () { console.log(this.hello); }
接下来看看入口文件index.ts所作的东西:
// Component其实是既做为工厂函数,又做为装饰器函数 function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any { if (typeof options === 'function') { // 区别一下。这里的命名虽然是工厂,其实它才是真正封装装饰器逻辑的函数 return componentFactory(options) } return function (Component: VueClass<Vue>) { return componentFactory(Component, options) } }
再看看componentFactory所作的东西:
import Vue, { ComponentOptions } from 'vue' import { copyReflectionMetadata, reflectionIsSupported } from './reflect' import { VueClass, DecoratedClass } from './declarations' import { collectDataFromConstructor } from './data' import { hasProto, isPrimitive, warn } from './util' export const $internalHooks = [ 'data', 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeDestroy', 'destroyed', 'beforeUpdate', 'updated', 'activated', 'deactivated', 'render', 'errorCaptured', // 2.5 'serverPrefetch' // 2.6 ] export function componentFactory ( Component: VueClass<Vue>, options: ComponentOptions<Vue> = {} ): VueClass<Vue> { // 为component的name赋值 options.name = options.name || (Component as any)._componentTag || (Component as any).name // prototype props. // 获取原型 const proto = Component.prototype // 遍历原型 Object.getOwnPropertyNames(proto).forEach(function (key) { // 若是是constructor, 则不处理 if (key === 'constructor') { return } // hooks // 若是原型属性(方法)名是vue生命周期钩子名,则直接做为钩子函数挂载在options最外层 if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key] return } // getOwnPropertyDescriptor 返回描述对象 const descriptor = Object.getOwnPropertyDescriptor(proto, key)! // void 0 === undefined if (descriptor.value !== void 0) { // methods // 若是是方法名就挂载到methods上 if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value } else { // typescript decorated data // 把成员变量做为mixin放到options上,一个变量一个mixin,而不是直接统计好放到data或者同一个mixin中 // 由于data咱们已经做为了保留字段,能够在类中声明成员方法data()和options中声明data一样的方法声明变量 (options.mixins || (options.mixins = [])).push({ data (this: Vue) { return { [key]: descriptor.value } } }) } } else if (descriptor.get || descriptor.set) { // computed properties // 转换成计算属性的getter和setter (options.computed || (options.computed = {}))[key] = { get: descriptor.get, set: descriptor.set } } }) // add data hook to collect class properties as Vue instance's data // 这里再次添加了一个mixin,会把这个类实例化,而后把对象中的值放到mixin中 // 只有在这里咱们声明的class的constructor被调用了 ;(options.mixins || (options.mixins = [])).push({ data (this: Vue) { return collectDataFromConstructor(this, Component) } }) // decorate options // 若是这个类还有其余的装饰器,也逐个调用. vue-class-component只提供了类装饰器 // props、components、watch等特殊参数只能写在Component(options)的options参数里 // 所以咱们使用vue-property-decorator库的属性装饰器 // 经过下面这个循环应用属性装饰器就能够合并options(ps: 不明白能够看看createDecorator这个函数) const decorators = (Component as DecoratedClass).__decorators__ if (decorators) { decorators.forEach(fn => fn(options)) delete (Component as DecoratedClass).__decorators__ } // find super // 找到这个类的父类,若是父类已是继承于Vue的,就直接调用它的extend方法,不然调用Vue.extend const superProto = Object.getPrototypeOf(Component.prototype) const Super = superProto instanceof Vue ? superProto.constructor as VueClass<Vue> : Vue // 最后生成咱们要的Vue组件 const Extended = Super.extend(options) // 处理静态成员 forwardStaticMembers(Extended, Component, Super) // 若是咱们支持反射,那么也把对应的反射收集的内容绑定到Extended上 if (reflectionIsSupported) { copyReflectionMetadata(Extended, Component) } return Extended } const reservedPropertyNames = [ // Unique id 'cid', // Super Vue constructor 'super', // Component options that will be used by the component 'options', 'superOptions', 'extendOptions', 'sealedOptions', // Private assets 'component', 'directive', 'filter' ] const shouldIgnore = { prototype: true, arguments: true, callee: true, caller: true } function forwardStaticMembers ( Extended: typeof Vue, Original: typeof Vue, Super: typeof Vue ): void { // We have to use getOwnPropertyNames since Babel registers methods as non-enumerable Object.getOwnPropertyNames(Original).forEach(key => { // Skip the properties that should not be overwritten if (shouldIgnore[key]) { return } // Some browsers does not allow reconfigure built-in properties const extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key) if (extendedDescriptor && !extendedDescriptor.configurable) { return } const descriptor = Object.getOwnPropertyDescriptor(Original, key)! // If the user agent does not support `__proto__` or its family (IE <= 10), // the sub class properties may be inherited properties from the super class in TypeScript. // We need to exclude such properties to prevent to overwrite // the component options object which stored on the extended constructor (See #192). // If the value is a referenced value (object or function), // we can check equality of them and exclude it if they have the same reference. // If it is a primitive value, it will be forwarded for safety. if (!hasProto) { // Only `cid` is explicitly exluded from property forwarding // because we cannot detect whether it is a inherited property or not // on the no `__proto__` environment even though the property is reserved. if (key === 'cid') { return } const superDescriptor = Object.getOwnPropertyDescriptor(Super, key) if ( !isPrimitive(descriptor.value) && superDescriptor && superDescriptor.value === descriptor.value ) { return } } // Warn if the users manually declare reserved properties if ( process.env.NODE_ENV !== 'production' && reservedPropertyNames.indexOf(key) >= 0 ) { warn( `Static property name '${key}' declared on class '${Original.name}' ` + 'conflicts with reserved property name of Vue internal. ' + 'It may cause unexpected behavior of the component. Consider renaming the property.' ) } Object.defineProperty(Extended, key, descriptor) }) }
下面简单总结一下vue-class-component作了什么:
相关文章:
https://zhuanlan.zhihu.com/p/48371638
http://www.lutoyvan.cn/2019/03/05/vue-class-component-source-code-analysis.html
vue-property-decorator
是在vue-class-component
基础上添加了几个属性装饰器
这里采用几种经常使用方式作介绍
interface PropOptions<T=any> { type?: PropType<T>; required?: boolean; default?: T | null | undefined | (() => T | null | undefined); validator?(value: T): boolean; } export function Prop(options: PropOptions | Constructor[] | Constructor = {}) { return (target: Vue, key: string) => { applyMetadata(options, target, key) // 把props push到vue-class-component的__decorators__数组中 createDecorator((componentOptions, k) => { ;(componentOptions.props || ((componentOptions.props = {}) as any))[ k ] = options })(target, key) } } /** @see {@link https://github.com/vuejs/vue-class-component/blob/master/src/reflect.ts} */ const reflectMetadataIsSupported = typeof Reflect !== 'undefined' && typeof Reflect.getMetadata !== 'undefined' // 设置类型 function applyMetadata( options: PropOptions | Constructor[] | Constructor, target: Vue, key: string, ) { if (reflectMetadataIsSupported) { if ( !Array.isArray(options) && typeof options !== 'function' && typeof options.type === 'undefined' ) { // 类型元数据使用元数据键"design:type" // 参考文章:https://www.jianshu.com/p/2abb2469bcbb options.type = Reflect.getMetadata('design:type', target, key) } } } export function createDecorator (factory: (options: ComponentOptions<Vue>, key: string, index: number) => void): VueDecorator { return (target: Vue | typeof Vue, key?: any, index?: any) => { const Ctor = typeof target === 'function' ? target as DecoratedClass : target.constructor as DecoratedClass if (!Ctor.__decorators__) { Ctor.__decorators__ = [] } if (typeof index !== 'number') { index = undefined } Ctor.__decorators__.push(options => factory(options, key, index)) } }
/** * decorator of a watch function * @param path the path or the expression to observe * @param WatchOption * @return MethodDecorator */ export function Watch(path: string, options: WatchOptions = {}) { const { deep = false, immediate = false } = options return createDecorator((componentOptions, handler) => { if (typeof componentOptions.watch !== 'object') { componentOptions.watch = Object.create(null) } const watch: any = componentOptions.watch if (typeof watch[path] === 'object' && !Array.isArray(watch[path])) { watch[path] = [watch[path]] } else if (typeof watch[path] === 'undefined') { watch[path] = [] } watch[path].push({ handler, deep, immediate }) }) }
综上所述, 其实和 vue-class-component
一个原理 都是用装饰器去解析出适用于vue里的参数