簡介:
這節我們繼續解析一個叫vue-property-decorator的第三方庫,首先可以看它官網的一個介紹:
This library fully depends on [vue-class-component](https://github.com/vuejs/vue-class-component), so please read its README before using this library.
也就是說它是基於vue-class-component庫的,在上一篇文章中我們介紹瞭如何在vue中利用裝飾器使用類組件,我們寫了一篇叫vue-class-component的文章,大家有興趣可以去看一下。
實現:
創建工程:
我們直接copy一份上一節代碼的demo,然後讓它支持一下typescript
vue-property-decorator-demo
vue-property-decorator-demo
demo
index.html //頁面入口文件
lib
main.js //webpack打包過後的文件
src
view
component.d.ts //類組件ts聲明文件
component.js //類組件文件
demo-class.vue //demo組件
main.ts //應用入口文件
babel.config.js //babel配置文件
tsconfig.json //ts配置文件
package.json //項目清單文件
webpack.config.js //webpack配置文件
index.html:
我們直接引用打包過後的文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="http://127.0.0.1:8081/main.js"></script>
</body>
</html>
demo-class.vue:
<template>
<div @click="say()">{{msg}}</div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "./component";
@Component
class DemoComponent extends Vue{
msg = 'hello world';
say(){
alert(this.msg);
}
}
export default DemoComponent;
</script>
main.ts:
加載demo.vue組件,掛在到“#app”元素上
import Vue from "vue";
import Demo from "./view/demo-class.vue";
new Vue({
render(h){
return h(Demo);
}
}).$mount("#app");
component.d.ts:
export declare const $internalHooks: string[];
export default function componentFactory(Component: any, options?: any): any;
component.js:
import Vue from "vue";
export const $internalHooks = [
'data',
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeDestroy',
'destroyed',
'beforeUpdate',
'updated',
'activated',
'deactivated',
'render',
'errorCaptured', // 2.5
'serverPrefetch' // 2.6
];
function collectDataFromConstructor(vm,Component) {
//創建一個組件實例
const data = new Component();
const plainData = {};
//遍歷當前對象的屬性值
Object.keys(data).forEach(key => {
if (data[key] !== void 0) {
plainData[key] = data[key];
}
});
//返回屬性值
return plainData
}
/**
* 組件工程函數
* @param Component //當前類組件
* @param options //參數
*/
function componentFactory(Component, options = {}) {
options.name = options.name || Component.name; //如果options沒有name屬性的話就直接使用類名
//獲取類的原型
const proto = Component.prototype;
//遍歷原型上面的屬性
Object.getOwnPropertyNames(proto).forEach((key) => {
// 過濾構造方法
if (key === 'constructor') {
return
}
// 賦值vue自帶的一些方法
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return
}
//獲取屬性描述器
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (descriptor.value !== void 0) {
//如果是方法的話直接賦值給methods屬性
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value;
} else {
//不是方法屬性的話就通過mixins方式直接賦值給data
(options.mixins || (options.mixins = [])).push({
data() {
return {[key]: descriptor.value}
}
});
}
}
});
//通過類實例獲取類屬性值通過mixins給data
(options.mixins || (options.mixins = [])).push({
data(){
return collectDataFromConstructor(this, Component)
}
});
//獲取當前類的父類
const superProto = Object.getPrototypeOf(Component.prototype);
//獲取Vue
const Super = superProto instanceof Vue
? superProto.constructor
: Vue;
//使用Vue.extend方法創建一個vue組件
const Extended = Super.extend(options);
//直接返回一個Vue組件
return Extended
}
/**
* 組件裝飾器
* @param options 參數
* @returns {Function} 返回一個vue組件
*/
export default function Component(options) {
//判斷有沒有參數
if (typeof options === 'function') {
return componentFactory(options)
}
return function (Component) {
return componentFactory(Component, options)
}
}
babel.config.js:
babel的配置跟上一節的是一樣的,大家感興趣可以去看一下前端框架系列之(裝飾器Decorator
module.exports = {
"presets": [
["@babel/env", {"modules": false}]
],
"plugins": [
["@babel/plugin-proposal-decorators", {"legacy": true}],
["@babel/proposal-class-properties", {"loose": true}]
]
};
package.json:
因爲要編譯vue文件所以我們加入了webpack跟vue、vue-loader等依賴
{
"name": "decorator-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-decorators": "^7.10.1",
"@babel/preset-env": "^7.10.2",
"babel-loader": "^8.1.0",
"ts-loader": "^7.0.5",
"vue-loader": "^15.9.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"typescript": "^3.9.5",
"vue": "^2.6.11"
}
}
webpack.config.js:
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const path = require('path');
module.exports = {
mode: 'development',
context: __dirname,
entry: './src/main.ts',
output: {
path: path.join(__dirname, 'lib'),
filename: 'main.js'
},
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js'
},
extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
appendTsxSuffixTo: [/\.vue$/]
}
}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: [
'babel-loader',
]
},
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
devtool: 'source-map',
plugins: [
new VueLoaderPlugin(),
new (require('webpack/lib/HotModuleReplacementPlugin'))()
]
};
tsconfig.json:
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"esnext"
],
"module": "es2015",
"moduleResolution": "node",
"experimentalDecorators": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"jsx": "preserve",
"jsxFactory": "h"
},
"include": [
"./**/*.ts"
],
"compileOnSave": false
}
運行工程:
npm run dev
瀏覽器打開,http://127.0.0.1:8081/demo/index.html
我們可以看到:
好啦,我們的工程就創建完畢了。
實現效果:
main.ts:
import Vue from "vue";
import Demo from "./view/demo-class.vue";
new Vue({
render(h){
return h(Demo,{
props:{
msg: "我是自定義屬性msg"
}
});
}
}).$mount("#app");
demo-class.vue:
<template>
<div @click="say()">{{msg}}</div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "./component";
import {Prop} from "./view-property-decorator";
@Component
class DemoComponent extends Vue{
@Prop({type: String,default: 'hello world'})msg!: string;
say(){
alert(this.msg);
}
}
export default DemoComponent;
</script>
好了,我們照着最終的樣子實現一下代碼。
代碼實現:
我們首先修改一下我們的component.js文件:
/**
* 組件工程函數
* @param Component //當前類組件
* @param options //參數
*/
function componentFactory(Component, options = {}) {
options.name = options.name || Component.name; //如果options沒有name屬性的話就直接使用類名
//獲取類的原型
const proto = Component.prototype;
//遍歷原型上面的屬性
Object.getOwnPropertyNames(proto).forEach((key) => {
// 過濾構造方法
if (key === 'constructor') {
return
}
// 賦值vue自帶的一些方法
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return
}
//獲取屬性描述器
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (descriptor.value !== void 0) {
//如果是方法的話直接賦值給methods屬性
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value;
} else {
//不是方法屬性的話就通過mixins方式直接賦值給data
(options.mixins || (options.mixins = [])).push({
data() {
return {[key]: descriptor.value}
}
});
}
}
});
//通過類實例獲取類屬性值通過mixins給data
(options.mixins || (options.mixins = [])).push({
data() {
return collectDataFromConstructor(this, Component)
}
});
// decorate options
const decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(fn => fn(options));
delete Component.__decorators__
}
//獲取當前類的父類
const superProto = Object.getPrototypeOf(Component.prototype);
//獲取Vue
const Super = superProto instanceof Vue
? superProto.constructor
: Vue;
//使用Vue.extend方法創建一個vue組件
const Extended = Super.extend(options);
//直接返回一個Vue組件
return Extended
}
可以看到,我們加一了一段代碼:
// decorate options
const decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(fn => fn(options));
delete Component.__decorators__
}
就是在Component類上綁定了一個__decorators__屬性供給其它地方使用,其實地方是哪呢?對的,就是我們的view-property-decorator.ts,做法很簡單,就是把我們類組件的options對象暴露出去通過__decorators__屬性提供給其它地方。
那麼類組件中的__decorators__屬性怎麼給其它地方用呢?
我們繼續在我們的component.js中提供一個createDecorator方法
export function createDecorator(factory, key, index) {
return (target, key, index) => {
const Ctor = typeof target === 'function'
? target
: target.constructor;
if (!Ctor.__decorators__) {
Ctor.__decorators__ = []
}
if (typeof index !== 'number') {
index = undefined
}
Ctor.__decorators__.push(options => factory(options, key, index))
}
}
component.js:
import Vue from "vue";
export const $internalHooks = [
'data',
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeDestroy',
'destroyed',
'beforeUpdate',
'updated',
'activated',
'deactivated',
'render',
'errorCaptured', // 2.5
'serverPrefetch' // 2.6
];
function collectDataFromConstructor(vm, Component) {
//創建一個組件實例
const data = new Component();
const plainData = {};
//遍歷當前對象的屬性值
Object.keys(data).forEach(key => {
if (data[key] !== void 0) {
plainData[key] = data[key];
}
});
//返回屬性值
return plainData
}
/**
* 組件工程函數
* @param Component //當前類組件
* @param options //參數
*/
function componentFactory(Component, options = {}) {
options.name = options.name || Component.name; //如果options沒有name屬性的話就直接使用類名
//獲取類的原型
const proto = Component.prototype;
//遍歷原型上面的屬性
Object.getOwnPropertyNames(proto).forEach((key) => {
// 過濾構造方法
if (key === 'constructor') {
return
}
// 賦值vue自帶的一些方法
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return
}
//獲取屬性描述器
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (descriptor.value !== void 0) {
//如果是方法的話直接賦值給methods屬性
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value;
} else {
//不是方法屬性的話就通過mixins方式直接賦值給data
(options.mixins || (options.mixins = [])).push({
data() {
return {[key]: descriptor.value}
}
});
}
}
});
//通過類實例獲取類屬性值通過mixins給data
(options.mixins || (options.mixins = [])).push({
data() {
return collectDataFromConstructor(this, Component)
}
});
// decorate options
const decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(fn => fn(options));
delete Component.__decorators__
}
//獲取當前類的父類
const superProto = Object.getPrototypeOf(Component.prototype);
//獲取Vue
const Super = superProto instanceof Vue
? superProto.constructor
: Vue;
//使用Vue.extend方法創建一個vue組件
const Extended = Super.extend(options);
//直接返回一個Vue組件
return Extended
}
/**
* 組件裝飾器
* @param options 參數
* @returns {Function} 返回一個vue組件
*/
export default function Component(options) {
//判斷有沒有參數
if (typeof options === 'function') {
return componentFactory(options)
}
return function (Component) {
return componentFactory(Component, options)
}
}
export function createDecorator(factory, key, index) {
return (target, key, index) => {
const Ctor = typeof target === 'function'
? target
: target.constructor;
if (!Ctor.__decorators__) {
Ctor.__decorators__ = []
}
if (typeof index !== 'number') {
index = undefined
}
Ctor.__decorators__.push(options => factory(options, key, index))
}
}
接下來我們創建一個view-property-decorator.ts文件:
import {createDecorator} from "./component";
/**
* 屬性裝飾器
* @param options
* @returns {(target: any, key: string) => any}
* @constructor
*/
export function Prop(options: any) {
if (options === void 0) {
options = {};
}
return function (target: any, key: string) {
//獲取類組件的options屬性,把當前屬性的options賦給類組件options的props屬性
createDecorator(function (componentOptions: any, k: string) {
(componentOptions.props || (componentOptions.props = {}))[k] = options;
})(target, key);
};
}
代碼很簡單,對裝飾器修飾屬性不熟悉的童鞋,可以看一下我前面寫的一篇文章前端框架系列之(裝飾器Decorator)
最終效果:
在vue-property-decorator中還有一些其它功能:
@Prop
@PropSync
@Model
@Watch
@Provide
@Inject
@ProvideReactive
@InjectReactive
@Emit
@Ref
@Component
(provided by vue-class-component)Mixins
(the helper function namedmixins
provided by vue-class-component)
我們只是簡單的實現了一下@Prop,感興趣的小夥伴可以自己去clone一份源碼
把其它功能都實現一下,你會發現有不一樣的收穫的~~
總結:
寫了三篇關於裝飾器的文章了,我覺得就對類組件這一塊的話,特別是接觸過一些其它語言,比如java的童鞋來說,用起來是真的爽!!,但是就像java中的註解一樣,我們也是一步一步的用裝飾器實現了一下類組件,性能方面的話肯定是比不上函數組件的,因爲我們每一個類組件就相對於在內存中創建了一個實例對象,這是很佔內存的,這是它的弊端,但是好處我就不多說了吧,更貼近面嚮對象語言設計,特別是結合typescript,然後在一些多人合作的項目上還是發揮着很大的作用的,就增加一點點內存的話其實也還能接受。
好啦! 這節就到這裏了,下面一節我將會利用類組件結合項目需求做mvc、mvp、mvvp架構模式的演示,大家敬請期待!!