概述

vue-property-decorator是基于vue组织里vue-class-component所做的拓展,先来了解一下vue-class-component

Vue-Class-Component

vue-class-component是一个Class Decorator,也就是类的装饰器,但目前装饰器在vue中只属于草案阶段.

原理简述

vue2.x只有Object一种声明组件的方式, 比如这样:

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就成了以下写法:

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);
}
}

在这个例子中,很容易发现几个疑点:

  1. @Component()是什么?
  2. hello = 'world'这是什么语法?
  3. App类没有constructor构造函数;
  4. 导出的类没有被new就直接使用了;

疑点1:

对装饰器的有一定了解. 装饰器种类有好几种, vue-class-component中主要使用了类装饰器.

更多关于装饰器信息请参阅阮老师的文章: ECMAScript6入门

看完阮老师所写的文章已经可以解决了疑点1

简述: @Component就是一个修饰器, 用来修改类的行为

疑点2:

在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做了什么:

  1. 收集class中的属性, 如果是方法就放到Methods里, 如果是普通变量就放到mixin中的data里
  2. 实例化class, 把这个class的属性也作为mixin中的data, 我们所写class的构造函数只会被这里所调用
  3. 利用Options执行生成组件
  4. 处理静态属性
  5. 反射相关处理

相关文章:

https://zhuanlan.zhihu.com/p/48371638

http://www.lutoyvan.cn/2019/03/05/vue-class-component-source-code-analysis.html

vue-property-decorator

vue-property-decorator是在vue-class-component基础上添加了几个属性装饰器

这里采用几种常用方式做介绍

Prop:

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))
}
}

Watch:

/**
* 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里的参数

Vue-Property-Decorator源码分析的更多相关文章

  1. 从vue.js的源码分析,input和textarea上的v-model指令到底做了什么

    v-model是 vue.js 中用于在表单表单元素上创建双向数据绑定,它的本质只是一个语法糖,在单向数据绑定的基础上,增加了监听用户输入事件并更新数据的功能:对,它本质上只是一个语法糖,但到底是一个 ...

  2. Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解

    先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...

  3. Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解

    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...

  4. Vue.js 源码分析(二十八) 高级应用 transition组件 详解

    transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性: n ...

  5. Vue.js 源码分析(十九) 指令篇 v-html和v-text指令详解

    双大括号会将数据解释为普通文本,而非 HTML 代码.为了输出真正的 HTML,你需要使用 v-html 指令,例如: <!DOCTYPE html> <html lang=&quo ...

  6. Vue.js 源码分析(四) 基础篇 响应式原理 data属性

    官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...

  7. Vue.js 源码分析(十三) 基础篇 组件 props属性详解

    父组件通过props属性向子组件传递数据,定义组件的时候可以定义一个props属性,值可以是一个字符串数组或一个对象. 例如: <!DOCTYPE html> <html lang= ...

  8. vue 快速入门 系列 —— 侦测数据的变化 - [vue 源码分析]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [vue 源码分析] 本文将 vue 中与数据侦测相关的源码摘了出来,配合上文(侦测数据的变化 - [基本实现]) 一起来分析一下 vue ...

  9. vue双向绑定的原理及实现双向绑定MVVM源码分析

    vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...

  10. vue源码分析—Vue.js 源码目录设计

    Vue.js 的源码都在 src 目录下,其目录结构如下 src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # 不同平台的支持 ├── ser ...

随机推荐

  1. 一:XMind

  2. 小白学 Python(17):基础数据类型(函数)(下)

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  3. find 小案例

    说明:前几天对生产环境的一些重要数据进行备份时用到了find,查找特定符合条件的文件名后拷贝至指定目录,但是只拷贝了部分匹配到的文件 小案例模拟还原: [root@centos- ~]# ll /te ...

  4. TCP/IP协议第一卷第三章 IP首部分析

    IP介绍 IP是TCP/IP协议族中最为核心的协议.所有的TCP.UDP.ICMP.IGMP数据都以IP数据报格式传输. IP提供不可靠.无连接的数据报传送服务. 不可靠(unreliable)它不能 ...

  5. [考试反思]0924csp-s模拟测试51:破碎

    总参赛人数:15 有点菜. 不知道是撞了什么大运没有滚出A层. 但是一回到A层就暴露出了一个大问题:码速. 不是调试速度,,就是纯粹码的速度... 边讲考试状态边说吧... 上来肝T1.一看,是个换根 ...

  6. NOIP模拟 22

    剧情回放:xuefeng:考场上你们只打暴力不打正解,我不满意! skyh:考场怒切T2以表明自己拥护xuefeng的决心 BoboTeacher:这场考试就没想让你们上100 神犇skyh:(笑而不 ...

  7. day 2 DP专场

    上午讲了一上午背包,从01背包到完全背包到多重背包,感觉也没说什么,旁边的大佬一直在飞鸽里说让老师讲快点,然而最后也没人敢跟老师说.... 例题真的各个都是神仙题, 挂饰 好像就是一上午最简单的... ...

  8. 华为OceanConnect物联网平台概念全景 | 我的物联网成长记

    作者 | 我是卤蛋 华为云OceanConnect IoT云服务包括应用管理.设备管理.系统管理等能力,实现统一安全的网络接入.各种终端的灵活适配.海量数据的采集分析,从而实现新价值的创造. 华为云O ...

  9. 如何利用缓存机制实现JAVA类反射性能提升30倍

    一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...

  10. ARM7中断的理解

    谈谈对中断的理解?   中断是计算机中处理异步事件的重要机制      中断触发的方式:       1)中断源级设置          按键:(CPU之外的硬件)               设置中 ...