概述
相对于typeScript的class写法,是对当前类的封装(个人理解)
首先看2.0 js的写法
const App=Vue.extent({
data(){return{'hello':'hello'}},
computed: {
world() {
return this.hello + 'world';
},
},
mounted(){
this.sayHello();
}
methods:{
sayHello() {
console.log(this.hello);
},
}
})
3.0 ts写法
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() 是?(@Component就是一个修饰器, 用来修改类的行为,更多关于装饰器信息请参阅阮老师Es6)
2:hello = 'world'; 这是啥写法
js语法中使用的是constructor去给属性赋值,在chrome上像vue-class-component中定义class是会报错的,但vue-class-component中却又这么做了.
然后我们看看class通过webpack + babel-loader解析后会变成什么样子
转换前
class App{
hello='world';
sathello(){
console.log(this.hello);
}
}
// 转换后 挂在原型上
function App () {
this.hello = 'world'
}
App.prototype.sayHello = function () {
console.log(this.hello);
}
3:App类没有constructor 构造函数
4:导出的类没有new 直接使用?
重点分析(index.js所做的东西)
// Component实际上是既作为工厂函数,又作为装饰器函数
function Component (options: ComponentOptions | VueClass): any {
if (typeof options === 'function') {
// 区别一下。这里的命名虽然是工厂,其实它才是真正封装装饰器逻辑的函数
return componentFactory(options)
}
return function (Component: VueClass) {
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,
options: ComponentOptions = {}
): VueClass {
// 为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组件
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)
})
}