「茴」字寫法 —— Vue typescript 組件

孔乙己顯出極高興的樣子,將兩個指頭的長指甲敲着櫃檯,點頭說,「對呀對呀!……回字有四樣寫法,你知道麼?」我愈不耐煩了,努着嘴走遠。孔乙己剛用指甲蘸了酒,想在櫃上寫字,見
我絕不熱心,便又嘆一口氣,顯出極可惜的樣子。

《「茴」字寫法》系列文章主要總結常見的代碼操做,給出多種實現方式,就像茴香豆的「茴」字有多種寫法同樣。
本文連接:https://www.taskhub.work/arti... javascript

規則千萬條,簡潔第一條vue


正文開始 java

Vue 組件有不少中寫法,在3.0以後會更好的支持typescript,ts用過都知道,真香,不管是代碼提示仍是代碼重構都很是方便,本人以前寫過一個UI庫大概4~5萬行規模,全用ts,期間發現不少不合理的地方,對UI庫進行重構,只花了2天時間。下面經過對比不一樣寫法。ios

各個Vue 組件UI庫實現方式:

UI 庫 實現方式
muse-ui 徹底不寫 <template> 只使用 render 函數
iview 使用 .vue 文件,樣式單獨寫
element 使用 .vue 文件,樣式單獨寫
vant 使用 .vue 文件,樣式單獨寫
ant-design-vue 使用 .jsx 文件,樣式單獨寫
vux 使用帶 <style> 的 .vue 文件,但在使用時必須用 vux-loader
cube-ui 使用帶 <style> 的 .vue 文件,但有一些配置

在實際開發中用不用 *.vue 這樣的單文件組件來開發呢?
網上有不少網友吐槽Vue的單文件組件模式寫法,認爲Vue的模板語法很雞肋,各類不方便,不如所有jsx。決定這個問題的關鍵是解耦,包括功能解耦、模塊解耦、甚至框架解耦。*.vue文件組織方式雖然多少和Vue相關,可是實際操做時發現要解耦重構也不是很大的問題,因此還OK。git

三種組件寫法對比

Object API 29 lines

import Vue, { PropOptions } from 'vue'

interface User {
  firstName: string
  lastName: number
}

export default Vue.extend({
  name: 'YourComponent',

  props: {
    user: {
      type: Object,
      required: true
    } as PropOptions<User>
  },

  data () {
    return {
      message: 'This is a message'
    }
  },

  computed: {
    fullName (): string {
      return `${this.user.firstName} ${this.user.lastName}`
    }
  }
})

Class API 17 lines

import { Vue, Component, Prop } from 'vue-property-decorator'

interface User {
  firstName: string
  lastName: number
}

@Component
export default class YourComponent extends Vue {
  @Prop({ type: Object, required: true }) readonly user!: User

  message: string = 'This is a message'

  get fullName (): string {
    return `${this.user.firstName} ${this.user.lastName}`
  }
}

Function API 25 lines

import Vue from 'vue'
import { computed, value } from 'vue-function-api'

interface User {
  firstName: string
  lastName: number
}

interface YourProps {
  user?: User
}

export default Vue.extend({
  name: 'YourComponent',

  setup ({ user }: YourProps) {
    const fullName = computed(() => `${user.firstName} ${user.lastName}`)
    const message = value('This is a message')

    return {
      fullName,
      message
    }
  }
})
寫法 優勢 缺點
Object API Vue 官方寫法,方便Vue直接處理組件 1. 代碼長、縮進多,組件複雜時難以理清邏輯,很差進行分割
2. 混入較多Vue的概念,新手學習成本高
Class API 相關概念能夠用class的思路理解,能夠更好地描述Vue的混入、data、computed,生命週期鉤子等概念。Vue 3.0 將原生支持class寫法 用到了修飾器語法特性,目前還在實驗階段(typescript可使用helper函數解決兼容問題,問題不大)
Function API 無狀態,更好的單元測試、並行化 函數式寫法很容易寫出回調地獄,致使代碼可讀性、可維護性差,目前純粹function api 寫法較少見

完成一樣一件事,ts的class寫法簡潔得多,在工程較大時減小1/3左右的代碼,可維護性大大提升。github

typescript class 寫法常見問題

  1. route 鉤子無效問題

使用class寫法會發現部分Vue的鉤子函數沒法使用問題,能夠經過註冊鉤子函數解決,以下:vue-router

import Component from 'vue-class-component'

// Register the router hooks with their names
Component.registerHooks([
  'beforeRouteEnter',
  'beforeRouteLeave',
  'beforeRouteUpdate' // for vue-router 2.2+
])
  1. 與Vuex配合使用問題

使用vuex-class 解決,如state映射vuex

import { Vue, Component } from 'vue-property-decorator';
import { User } from '@/api/account';
import { State } from 'vuex-class';


@Component
export default class TestPage extends Vue {

  @State(state => state.user, { namespace: 'account' })
  user!: User;

}
  1. Vue 混入功能

代碼常常須要各類錯誤,包括用戶輸入錯誤、安全檢測、後臺錯誤、網絡故障等。若是所有錯誤處理代碼放進組件中,代碼臃腫,閱讀性差,能夠將常見的錯誤處理邏輯提取出來,經過混入的方式插入組件中。class寫法推薦使用vue-property-decorator 的 Mixins,參考附錄typescript

附錄

附上模板代碼,解決大多數Vue typescript 組件問題
Vue class 組件axios

import { Vue, Component, Prop, Watch, Model, Mixins } from 'vue-property-decorator';
import { State, Getter, Action, Mutation } from 'vuex-class';
import axios, { AxiosError } from 'axios';

interface Person {
  userId: string;
  nickname: string;
}

@Component
class CommonHandler extends Vue {
  onNetworkError(e: AxiosError) {
    console.log('on error');
  }
}

@Component
export default class Test extends Mixins(CommonHandler) {
  @Prop(Number) readonly propA: number | undefined

  @Prop({ default: 'default value' }) readonly propB!: string

  @Prop([String, Boolean]) readonly propC: string | boolean | undefined

  @Model('change', { type: Boolean }) readonly checked!: boolean
  
  message: string = 'hello world';

  get propBLen(): number {
    return this.propB.length;
  }

  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}

  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}

  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
 
  change() {
    console.log('on change');
  }

  created() {
    // 調用混入中的錯誤處理函數,簡化代碼
    axios.get('hello').catch(this.onNetworkError);
  }

  mounted() { console.log('mounted'); }
}

Vue 官方寫法

import axios from 'axios';

const CommonHandler = {
  methods: {
    onNetworkError(e) {
      console.log('on error');
    }
  },
}

export default {
  mixins: [CommonHandler],
  
  props: {
    propA: {
      type: Number
    },
    propB: {
      default: 'default value'
    },
    propC: {
      type: [String, Boolean]
    }
  },

  model: {
    prop: 'checked',
    event: 'change'
  },

  data() {
    return {
      message: 'hello world',
    }
  },

  computed: {
    propBLen() {
      return this.propB.length;
    },
  },

  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false
      }
    ],
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false
      }
    ]
  },

  methods: {
    change() {
      console.log('on change');
    },

    onChildChanged(val, oldVal) {},

    onPersonChanged1(val, oldVal) {},

    onPersonChanged2(val, oldVal) {}
  },

  // vue lifecycle hooks  
  created() {
    // 調用混入中的錯誤處理函數,簡化代碼
    axios.get('hello').catch(this.onNetworkError);
  },

  mounted() { console.log('mounted'); }
}

52行對比84行,一樣功能減小38%的代碼

最後打個廣告~

TaskHub 是咱們團隊開發的一個 Markdown 加密網盤,支持常見的任務管理功能還有Markdown 文件編輯,全部功能與平臺解耦,只使用Markdown的特性實現,更好的保障用戶的數據安全,實乃團隊協做之利器。歡迎你們使用~

相關文章
相關標籤/搜索