回顧下我們?nèi)粘9ぷ髦谐S玫膙ue的東西
- data() 中定義數(shù)據(jù)
- 子組件通過props接受父組件傳來的值
- 子組件通過$emit給父組件傳遞信息
- 通過watch來進(jìn)行觀察數(shù)據(jù)
- 通過computed計(jì)算屬性
- vue組件的生命周期方法
- 路由以及vuex
這一篇章將根據(jù)vue-class-component以及vue-property-decorator來說明除了路由以及vuex的使用方法,通過通過vue寫ts有兩種方法,一種是和原有寫法類似的還有一種是基于類的寫法。以下所有的寫法都是基于類的寫法
vue-class-component解析
vue-class-component核心的代碼為component.js
export function componentFactory(Component, options = {}) {
options.name = options.name || Component._componentTag || Component.name;
// 獲取組件的原型
const proto = Component.prototype;
// 遍歷原型
Object.getOwnPropertyNames(proto).forEach(function (key) {
if (key === 'constructor') {
return;
}
// 如果原型key是$internalHooks的話就將數(shù)據(jù)直接設(shè)置為options中悍及,此時(shí)由于繼承vue的component,上面的屬性都轉(zhuǎn)成了方法,可以直接使用
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return;
}
// 獲取組件自身屬性
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
// 如果存在value 屬性則代碼為非計(jì)算屬性
if (descriptor.value !== void 0) {
// 如果類型為function則代表為方法前痘,這里要注意一下watch以及emit的都是放著方法里面
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value;
}
else {
// 如果聲明是數(shù)據(jù),判斷傳遞的參數(shù)中是否包含mixins如果包含將數(shù)據(jù)放入到data中
(options.mixins || (options.mixins = [])).push({
data() {
return { [key]: descriptor.value };
}
});
}
}
else if (descriptor.get || descriptor.set) {
// 如果存在get和set,則為計(jì)算屬性
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
};
}
});
// 如果options中包含混入,則調(diào)用data中的collectDataFromConstructor方法,將數(shù)據(jù)通過defineProperty來變成可觀察的
(options.mixins || (options.mixins = [])).push({
data() {
return collectDataFromConstructor(this, Component);
}
});
// 獲取方法繼承的裝飾器旦签,用來處理上面改變的options
const decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(fn => fn(options));
delete Component.__decorators__;
}
// 獲取父級(jí)的原型
const superProto = Object.getPrototypeOf(Component.prototype);
// 判斷原型是什么,如果是vue則直接取父級(jí)的構(gòu)造函數(shù)寸宏,否則直接取vue
const Super = superProto instanceof Vue
? superProto.constructor
: Vue;
// 子類繼承
const Extended = Super.extend(options);
forwardStaticMembers(Extended, Component, Super);
if (reflectionIsSupported()) {
copyReflectionMetadata(Extended, Component);
}
return Extended;
}
大家都知道在js的原型中宁炫,我們定義在類中的方法或者值都將是類原型鏈上的一員,所以基于此才有了上面的代碼氮凝。我們其實(shí)可以想想現(xiàn)在官方出的兩個(gè)裝飾器羔巢,其實(shí)都是通過@xxx(裝飾器)來表明當(dāng)前定義的為什么數(shù)據(jù),類似ng中的注入覆醇,在解析的時(shí)候通過裝飾器將類的原型鏈上的數(shù)據(jù)類比到以往的vue的相對(duì)應(yīng)的方法中即可朵纷,所以我們可以這樣理解裝飾器的作用將數(shù)據(jù)類比成原有vue數(shù)據(jù),只是寫法上變化了永脓,穿了衣服的貓依舊是貓。例如
@Prop() propA:string
//會(huì)最終轉(zhuǎn)換成
props:{
propA:''
}
@Watch('propA',{
deep:true
})
test(newValue:string,oldValue:string){
console.log('propA值改變了' + newValue);
}
//會(huì)被轉(zhuǎn)化成
watch:{
propA:handler(newValue,oldValue){
this.test(newValue,oldValue);
},
deep:true
}
test方法會(huì)被轉(zhuǎn)化到methods中
vue-property-decorator 核心原理已在上面說明了⌒裕現(xiàn)在就看這里面有哪些裝飾器常摧,查看vue-property-decorator.d.ts里面我們能夠看到所具有的裝飾器有
/**
* decorator of an inject
* @param from key
* @return PropertyDecorator
*/
export declare function Inject(options?: InjectOptions | InjectKey): import("vue-class-component").VueDecorator;
/**
* decorator of a reactive inject
* @param from key
* @return PropertyDecorator
*/
export declare function InjectReactive(options?: InjectOptions | InjectKey): import("vue-class-component").VueDecorator;
/**
* decorator of a provide
* @param key key
* @return PropertyDecorator | void
*/
export declare function Provide(key?: string | symbol): import("vue-class-component").VueDecorator;
/**
* decorator of a reactive provide
* @param key key
* @return PropertyDecorator | void
*/
export declare function ProvideReactive(key?: string | symbol): import("vue-class-component").VueDecorator;
/**
* decorator of model
* @param event event name
* @param options options
* @return PropertyDecorator
*/
export declare function Model(event?: string, options?: PropOptions | Constructor[] | Constructor): (target: Vue, key: string) => void;
/**
* decorator of a prop
* @param options the options for the prop
* @return PropertyDecorator | void
*/
export declare function Prop(options?: PropOptions | Constructor[] | Constructor): (target: Vue, key: string) => void;
/**
* decorator of a synced prop
* @param propName the name to interface with from outside, must be different from decorated property
* @param options the options for the synced prop
* @return PropertyDecorator | void
*/
export declare function PropSync(propName: string, options?: PropOptions | Constructor[] | Constructor): PropertyDecorator;
/**
* decorator of a watch function
* @param path the path or the expression to observe
* @param WatchOption
* @return MethodDecorator
*/
export declare function Watch(path: string, options?: WatchOptions): import("vue-class-component").VueDecorator;
/**
* decorator of an event-emitter function
* @param event The name of the event
* @return MethodDecorator
*/
export declare function Emit(event?: string): (_target: Vue, propertyKey: string, descriptor: any) => void;
/**
* decorator of a ref prop
* @param refKey the ref key defined in template
*/
export declare function Ref(refKey?: string): import("vue-class-component").VueDecorator;
至于我上面說的理論對(duì)或者不對(duì)我們可以看看vue裝飾器的實(shí)現(xiàn)
export function Watch(path, options) {
if (options === void 0) { options = {}; }
var _a = options.deep, deep = _a === void 0 ? false : _a, _b = options.immediate, immediate = _b === void 0 ? false : _b;
return createDecorator(function (componentOptions, handler) {
if (typeof componentOptions.watch !== 'object') {
componentOptions.watch = Object.create(null);
}
var watch = 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: handler, deep: deep, immediate: immediate });
});
}
下面來看具體在基于類組件中如何定義吧
data()中定義數(shù)據(jù)
前面說過了,裝飾器class將類的原型中的數(shù)據(jù)直接掛在相對(duì)應(yīng)的原有vue方法中,但是data除外落午,data只直接將非裝飾器以及非方法的直接放在data()中 谎懦,所以我們定義的時(shí)候只需要這樣做即可
export default class Test extends Vue {
public message1: string = "asd"
public message2: string = "asd1";
public message3: string = "asd2";
props傳值
props的話就沒有data那么舒服了,因?yàn)樗枰褂醚b飾器了溃斋,寫法如下
@Prop({
type: Number,
default: 1,
required: false
})
propA?: number
@Prop()
propB:string
如果沒有默認(rèn)值的建議使用可選屬性界拦?來定義,這樣避免報(bào)錯(cuò)梗劫。
$emit傳值
$emit傳值涉及到兩部分享甸,一個(gè)是父組件定義的方法,一個(gè)是子組件觸發(fā)梳侨。當(dāng)前emit有三種方法
首先子組件的寫法
<template>
<div class="test-container">
{{message}}
<input type="button" value="點(diǎn)擊觸發(fā)父級(jí)方法" @click="bindSend"/>
<input type="button" value="點(diǎn)擊觸發(fā)父級(jí)方法" @click="handleSend"/>
<input type="button" value="點(diǎn)擊觸發(fā)父級(jí)方法" @click="bindSend2"/>
<!-- <Hello></Hello> -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch, Emit } from "vue-property-decorator";
// 注明此類為一個(gè)vue組件
@Component({})
export default class Test extends Vue {
// 第一種
@Emit()
private bindSend():string{
return this.message
}
// 第二種
@Emit()
private bindSend1(msg:string){
// 如果不處理可以不寫下面的蛉威,會(huì)自動(dòng)將參數(shù)回傳
msg += 'love';
return msg;
}
//原有放在methods中的方法平鋪出來
public handleSend():void {
this.bindSend1(this.message);
}
// 第三種
// 這里的emit中的參數(shù)是表明父級(jí)通過什么接受,類似以前的$emit('父級(jí)定義的方法')
@Emit('test')
private bindSend2(){
return '這個(gè)可以用test接受';
}
}
</script>
父組件的接收代碼,這里父組件懶得換成ts寫法了走哺,意思到了就行了(哈哈)
<template>
<div id="app">
<Test :propA="propA" @bind-send = "bindSend" @bind-send1 = 'handleSend1' @test = 'handleSend2'></Test>
</div>
</template>
<script>
export default {
name: 'App',
components: {
Test:()=>import('./components/test.vue')
},
data(){
return {}
},
methods: {
bindSend(val) {
console.log('子集觸發(fā)了emit方法' +val);
},
handleSend1(val) {
console.log('子集觸發(fā)了emit方法' +val);
},
handleSend2(val) {
console.log('子集觸發(fā)了emit方法' +val);
}
},
}
</script>
如果我們需要傳多個(gè)值的話蚯嫌,我們?cè)趺磁兀縼硪黄鹂纯丛创a的實(shí)現(xiàn)你就知道了
export function Emit(event) {
return function (_target, propertyKey, descriptor) {
var key = hyphenate(propertyKey);
var original = descriptor.value;
descriptor.value = function emitter() {
var _this = this;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var emit = function (returnValue) {
var emitName = event || key;
if (returnValue === undefined) {
if (args.length === 0) {
_this.$emit(emitName);
}
else if (args.length === 1) {
_this.$emit(emitName, args[0]);
}
else {
_this.$emit.apply(_this, [emitName].concat(args));
}
}
else {
if (args.length === 0) {
_this.$emit(emitName, returnValue);
}
else if (args.length === 1) {
_this.$emit(emitName, returnValue, args[0]);
}
else {
_this.$emit.apply(_this, [emitName, returnValue].concat(args));
}
}
};
var returnValue = original.apply(this, args);
if (isPromise(returnValue)) {
returnValue.then(emit);
}
else {
emit(returnValue);
}
return returnValue;
};
};
}
通過源碼我們可以發(fā)現(xiàn)有兩種方法丙躏,如果在方法中也就是
@Emit()
private bindSend1(msg:string){
// 如果不處理可以不寫下面的择示,會(huì)自動(dòng)將參數(shù)回傳
msg += 'love';
return msg;
}
通過return返回的方式話的話只會(huì)取args[0],所以這種情況建議直接以一個(gè)對(duì)象或者數(shù)組方式傳遞
上面說到了給父組件傳值的時(shí)候可以只通過參數(shù)晒旅,而不需要return,那么源碼中可以看到var returnValue = original.apply(this, args);所以我們也可以這樣來傳
@Emit()
private bindSend1(msg:string,love:string){}
//原有放在methods中的方法平鋪出來
public handleSend():void {
this.bindSend1(this.message,'love');
}
父組件中接受數(shù)據(jù)
handleSend1(val,val1) {
console.log('子集觸發(fā)了emit方法' +val + val1);
},
watch觀察數(shù)據(jù)
//原有的watch屬性
@Watch('propA',{
deep:true
})
test(newValue:string,oldValue:string){
console.log('propA值改變了' + newValue);
}
computed計(jì)算屬性
在前面vue-class-component介紹里面我們提到過Component怎么處理數(shù)據(jù)的
else if (descriptor.get || descriptor.set) {
// 如果存在get和set,則為計(jì)算屬性
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
};
}
所以我們?cè)趯懹?jì)算屬性的時(shí)候只需要按照以下方法寫即可
public get computedMsg(){
return '這里是計(jì)算屬性' + this.message;
}
public set computedMsg(message:string){
}
vue組件的生命周期方法
和以前一樣使用即可
到此基礎(chǔ)使用已經(jīng)結(jié)束对妄,下篇說明路由以及vuex使用方法
最后附上完整代碼
<template>
<div class="test-container">
{{message}}
<input type="button" value="點(diǎn)擊觸發(fā)父級(jí)方法" @click="bindSend"/>
<input type="button" value="點(diǎn)擊觸發(fā)父級(jí)方法" @click="handleSend"/>
<input type="button" value="點(diǎn)擊觸發(fā)父級(jí)方法" @click="bindSend2"/>
<!-- <Hello></Hello> -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch, Emit } from "vue-property-decorator";
import Hello from "./HelloWorld.vue";
// 注明此類為一個(gè)vue組件
@Component({
components: {
Hello
}
})
export default class Test extends Vue {
// 原有data中的數(shù)據(jù)在這里展開編寫
public message: string = "asd";
//原有props中的數(shù)據(jù)展開編寫
@Prop({
type: Number,
default: 1,
required: false
})
propA?: number
@Prop()
propB:string
//原有computed
public get computedMsg(){
return '這里是計(jì)算屬性' + this.message;
}
public set computedMsg(message:string){
}
//原有的watch屬性
@Watch('propA',{
deep:true
})
public test(newValue:string,oldValue:string){
console.log('propA值改變了' + newValue);
}
// 以前需要給父級(jí)傳值的時(shí)候直接方法中使用emit就行了,當(dāng)前需要通過emit來處理
@Emit()
private bindSend():string{
return this.message
}
@Emit()
private bindSend1(msg:string,love:string){
// 如果不處理可以不寫下面的敢朱,會(huì)自動(dòng)將參數(shù)回傳
// msg += 'love';
// return msg;
}
//原有放在methods中的方法平鋪出來
public handleSend():void {
this.bindSend1(this.message,'love');
}
// 這里的emit中的參數(shù)是表明父級(jí)通過什么接受剪菱,類似以前的$emit('父級(jí)定義的方法')
@Emit('test')
private bindSend2(){
return '這個(gè)可以用test接受';
}
}
</script>