Vue & Vuex in Typescript - 使用指南

概述:最近在學習 vue & typeScript,發現有有一些寫法和方法須要去梳理和總結。主要是參考 參考文章 vue-property-decoratorvue-class-component,因此選擇一些關鍵點用於沉澱和思考。html

publish:2019-09-15vue

可與 React & Redux in TypeScript - 靜態類型指南 對比着看react

目錄

  • 簡介、環境配置(vue-cli)
  • Vue - 組件模式(vue-property-decorator 的使用)
  • Vuex - 狀態管理(vuex-module-decorators、vuex-class 的使用)

簡介、環境配置

vue-cli 中包含着 typescript 選項,只須要選擇便可git

vue create repo
# 手動配置的時候須要選擇 TypeScript
Check the features needed for your project:
 ◉ Babel
 ◉ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◉ Vuex
❯◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
複製代碼

vuex-module-decorators、vuex-class 的引入見github

vuex-module-decorators 官方文檔vuex

vue-class ReadMevue-cli

Vue - 組件模式

用 Typescript 來開發 Vue,主要是採用 vue-property-decorator,來賦予組件 Class 各類能力,具體有typescript

  • @Component 主要是用來實現 class-style 形式的 Vue 組件npm

    import { Component, Vue } from 'vue-property-decorator';
    
    @Component
    export default class HelloWorld extends Vue {
    
        public render() {
            return (<h5>Hello world</h5>);
        }
    }
    複製代碼

    與以前的 .vue 不一樣的是,利用 render 來渲染組件。此時的 class Hello world 中實現的組件的狀態與邏輯bash

    • data 組件狀態,在 vue 中很關鍵的 data 就是做爲 class 的屬性來實現的

      import { Component, Vue } from 'vue-property-decorator';
      
      @Component
      export default class HelloWorld extends Vue {
      
          private message: string = 'world';
      
          public render() {
              const { message }: HelloWorld = this;
      
              return (<h5>Hello {message}</h5>);
          }
      }
      複製代碼

      使用 privat message 做爲組件的狀態;

    • computed 組件的計算屬性,在 class-style 形式的 vue 組件中,常採用 get() 操做符來實現

      @Component
      export default class HelloWorld extends Vue {
      
          private message: string = 'world';
      
          get subMessage(): string {
              return `boy ${this.message}`;
          }
      
          public render() {
              const { message, subMessage }: HelloWorld = this;
      
              return (
                  <div>
                      <h5>Hello {message}</h5>
                      <span>{subMessage}</span>
                  </div>
              );
          }
      }
      複製代碼
    • methodsdata 相似,也是做爲 class 的屬性來實現的,經常使用於實現頁面中的操做事件,例如點擊、選擇等

      @Component
      export default class HelloWorld extends Vue {
      
          private message: string = 'world';
      
          get subMessage(): string {
              return `boy ${this.message}`;
          }
      
          public setMessage(msg: string): void {
              this.message = msg;
          }
      
          public render() {
              const { message, subMessage, setMessage }: HelloWorld = this;
      
              return (
                  <div> <h5>Hello {message}</h5> <span>{subMessage}</span> <button onClick={() => setMessage('person')}> Click </button> </div>
              );
          }
      }
      複製代碼
  • @Prop 做爲單向數據流實現的關鍵一環,也是組件之間傳遞數據的關鍵

    import { Component, Prop, Vue } from 'vue-property-decorator';
    
    @Component
    export default class HelloWorld extends Vue {
    
        @Prop({ default: 'Click' }) public readonly porpA!: string;
    
        public render() {
            const { porpA }: HelloWorld = this;
    
            return (
                <div>
                    <h5>{porpA}</h5>
                </div>
            );
        }
    }
    複製代碼

    能夠在 @Prop 的參數中配置相關 options,而且 vue-property-decorator 還提供了 @PropSync,它將 propscomputed 複合使用,具體用法

    // 父組件 parent
    <Child 
        name="hello world" 
        @update:name="handleUpdate" 
    />
    // 子組件 child
    @Component
    export default class Child extends Vue {
      @PropSync('name', { type: String }) 
      syncedName!: string
      
      public setSyncedName() {
          this.syncedName = 'delete';
      }
    }
    複製代碼

    等同於以前單文件組件模式下的

    export default {
      props: {
        name: {
          type: String
        }
      },
      computed: {
        syncedName: {
          get() {
            return this.name
          },
          set(value) {
            this.$emit('update:name', value)
          }
        }
      }
    }
    複製代碼
  • @Emit 等同於 this.$emit,經常使用於觸發自定義事件

    // tsx
    @Emit()
    addToCount(n: number) {
        this.count += n
    }
    // vue
    methods: {
        addToCount(n) {
            this.count += n
            this.$emit('add-to-count', n)
        }
    }
    複製代碼
  • @Watch 指的是偵聽屬性,也就是當偵聽的 data 或者 props 變化的時候,會觸發對應變化

    import { Component, Watch, Vue } from 'vue-property-decorator';
    
    @Component
    export default class HelloWorld extends Vue {
    
        private message: string = 'world';
        @Watch('message', { immediate: true, deep: true })
        public onMsgChange(val: string, oldVal: string) {
            console.log('onMsgChange', val, oldVal);
        }
    
    
        public render() {
            const { message }: HelloWorld = this;
    
            return (
                <div> <h5>Hello {message}</h5> <input type='text' v-model={this.message}/> </div> ); } } 複製代碼

    @Watch 的第一個參數表明監聽的 path,第二個參數則是表明

    { immediate: true, deep: true } //是否當即觸發、深度偵聽
    複製代碼
  • @Provide@inject 這兩個經常使用在組件開發上,簡單的來講就是在父組件中經過 provider 來提供變量,而後在子組件中經過 inject 來注入變量,在 class-style 中

    import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
     
    const symbol = Symbol('baz')
     
    @Component
    export class MyComponent extends Vue {
      @Inject() readonly foo!: string
     
      @Provide() foo = 'foo'
    }
    複製代碼

注意這種共享的 data 是非響應式的

  • @ProvideReactive 以及 @InjectReactive 與上面的用法差很少,可是共享的值,若是在父組件中發生了變化,那麼在子組件中會捕捉到。

  • @ref 經常使用於直接訪問組件

    import { Vue, Component, Ref } from 'vue-property-decorator'
     
    import AnotherComponent from '@/path/to/another-component.vue'
     
    @Component
    export default class YourComponent extends Vue {
      @Ref() readonly anotherComponent!: AnotherComponent
      @Ref('aButton') readonly button!: HTMLButtonElement
    }
    # 等同於
    export default {
      computed() {
        anotherComponent: {
          cache: false,
          get() {
            return this.$refs.anotherComponent as AnotherComponent
          }
        },
        button: {
          cache: false,
          get() {
            return this.$refs.aButton as HTMLButtonElement
          }
        }
      }
    }
    複製代碼
  • @Model 經常使用於實現 v-model 這樣的指令,在以前的 vue 組件中

    export default {
      model: {
        prop: 'checked',
        event: 'change'
      },
      props: {
        checked: {
          type: Boolean
        }
      }
    }
    複製代碼

    在 class-style 中,只須要用 @Model 來實現

    import { Vue, Component, Model } from 'vue-property-decorator'
    
    @Component
    export default class YourComponent extends Vue {
      @Model('change', { type: Boolean }) readonly checked!: boolean
    }
    複製代碼

Vuex - 狀態管理

以前在使用 Vuex 的時候,主要是依賴 stategettersmutations 以及 actions,而且能夠將它們模塊化

  • state 狀態

    import { Module, VuexModule } from 'vuex-module-decorators'
    
    @Module
    export default class Vehicle extends VuexModule {
      wheels = 2
    }
    // 來替代以前的
    export default {
      state: {
        wheels: 2
      }
    }
    複製代碼
  • Getter 與 class-style 中的 computed 相似,也是藉助 getters 來實現的

    import { Module, VuexModule } from 'vuex-module-decorators'
    
    @Module
    export default class Vehicle extends VuexModule {
      wheels = 2
      get axles() {
        return this.wheels / 2
      }
    }
    // 來代替以前的
    export default {
      state: {
        wheels: 2
      },
      getters: {
        axles: (state) => state.wheels / 2
      }
    }
    複製代碼
  • Mutation 用來修改 state,與 Vuex in js 同樣,只能用來實現同步操做

    import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
    
    @Module
    export default class Vehicle extends VuexModule {
      wheels = 2
    
      @Mutation
      puncture(n: number) {
        this.wheels = this.wheels - n
      }
    }
    // 來替代以前的
    export default {
      state: {
        wheels: 2
      },
      mutations: {
        puncture: (state, payload) => {
          state.wheels = state.wheels - payload
        }
      }
    }
    複製代碼
  • actions 用來實現異步操做

    const request = require('request')
    export default {
      state: {
        wheels: 2
      },
      mutations: {
        addWheel: (state, payload) => {
          state.wheels = state.wheels + payload
        }
      },
      actions: {
        fetchNewWheels: async (context, payload) => {
          const wheels = await request.get(payload)
          context.commit('addWheel', wheels)
        }
      }
    }
    // 來替代以前的
    const request = require('request')
    export default {
      state: {
        wheels: 2
      },
      mutations: {
        addWheel: (state, payload) => {
          state.wheels = state.wheels + payload
        }
      },
      actions: {
        fetchNewWheels: async (context, payload) => {
          const wheels = await request.get(payload)
          context.commit('addWheel', wheels)
        }
      }
    }
    複製代碼

以上,咱們簡單實現了一個 Vuex 的 module,那麼如何使用呢?將上述代碼梳理一下,並配置到 Vuex 中,具體

import { Module, VuexModule } from 'vuex-module-decorators'

@Module({ name: 'Vehicle',  namespaced: true, stateFactory: true })
export default class Vehicle extends VuexModule {
    public wheels = 2;
    get axles() {
        return this.wheels / 2;
    }
    @Mutation
    public puncture(n: number): void {
        this.wheels = this.wheels - n;
    }
}
複製代碼

請注意 Module 的配置,咱們須要將須要 namespaced: true,而且爲該空間命名,而且引入到項目中

import Vue from 'vue'
import Vuex from 'vuex'
import Vehicle from './Vehicle'

Vue.use(Vuex)

export default new Vuex.Store({
    modules: {
        Vehicle,
    },
})
複製代碼

以後就是使用 vuex-class,來獲取該命名空間下的狀態以及方法

import { Component, Vue } from 'vue-property-decorator';
import {
    Getter,
    Mutation,
    namespace,
} from 'vuex-class';

const Vehicle = namespace('Vehicle');

@Component
export default class HelloWorld extends Vue {
	// 引入 Vechicle 下的 Getters
    @Vehicle.Getter('axles') public axles: number | undefined;
    // 引入 Vechicle 下的 puncture
    @Vehicle.Mutation('puncture')
    public mutationPuncture!: (n: number) => void;

    private message: string = 'world';

    public render() {
        const { message, axles, mutationPuncture }: HelloWorld = this;

        return (
            <div onClick={ () => mutationPuncture(1) }>
                <h5 ref='quickEntry'>Hello {message} { axles }</h5>
            </div>
        );
    }
}

複製代碼

利用 namespace 能夠很方便的獲取到 Vuex 的 Vehicle 模塊,再配合 GetterMutation 就能夠完成引入,其餘的引入方法與之相似,就不贅述了。

結論

整體來講整個體驗的過程很像以前寫 Mobx & React in Typescript 的感受,很相像。在實際項目中使用的話,會有 ts 帶來的一些便利,可是也感受總有些牽強,其餘後續 3.0,看看可否與 ts 產生奇妙的化學反應吧。

相關文章
相關標籤/搜索