vue + typescript(二)基礎(chǔ)使用篇

回顧下我們?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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拴签,隨后出現(xiàn)的幾起案子孝常,更是在濱河造成了極大的恐慌,老刑警劉巖蚓哩,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构灸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡岸梨,警方通過查閱死者的電腦和手機(jī)喜颁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曹阔,“玉大人半开,你說我怎么就攤上這事≡叻荩” “怎么了寂拆?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵奢米,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我纠永,道長(zhǎng)鬓长,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任尝江,我火速辦了婚禮涉波,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炭序。我一直安慰自己啤覆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布少态。 她就那樣靜靜地躺著城侧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪彼妻。 梳的紋絲不亂的頭發(fā)上嫌佑,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音侨歉,去河邊找鬼屋摇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛幽邓,可吹牛的內(nèi)容都是我干的炮温。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼牵舵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柒啤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起畸颅,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤担巩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后没炒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涛癌,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年送火,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拳话。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡种吸,死狀恐怖弃衍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骨稿,我是刑警寧澤笨鸡,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布姜钳,位于F島的核電站坦冠,受9級(jí)特大地震影響形耗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辙浑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一激涤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧判呕,春花似錦倦踢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至边涕,卻和暖如春晤碘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背功蜓。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工园爷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人式撼。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓童社,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親著隆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扰楼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349