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裏的參數