在vue2中使用ts

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"介紹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文本所介紹的內容是使用 TypeScript 編寫 Vue2.6.11 前端應用,具體 demo 地址可訪問: "},{"type":"link","attrs":{"href":"https://github.com/hy08/all-demo/tree/master/vue-demo","title":null},"content":[{"type":"text","text":"vue-ts-demo"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結幾個月來在 ts 環境 中使用 vue 的經驗,提煉一個最小可運行案例,該案例將包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"搭建 ts 項目,配置 tsconfig.json"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"單文件組件(template 組件)的使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"tsx 組件的使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"vue-router 的 ts 方案"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"vuex 的 ts 方案"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"api 類型定義的建議"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"項目搭建與配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ts 環境下 vue2 版本的項目可直接使用官方的腳手架 vue-cli 進行搭建,根據項目組情況判斷是否需要使用 tsx、css 預處理+css module、單元測試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"項目創建完成,默認生成一份"},{"type":"codeinline","content":[{"type":"text","text":"tsconfig.json"}]},{"type":"text","text":"文件。ts 配置項解釋可以參考"},{"type":"link","attrs":{"href":"https://www.tslang.cn/docs/handbook/tsconfig-json.html","title":null},"content":[{"type":"text","text":"TypeScript 官方教程"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":"中默認安裝"},{"type":"link","attrs":{"href":"https://github.com/vuejs/vue-class-component","title":null},"content":[{"type":"text","text":"vue-class-component"}]},{"type":"text","text":",該庫通過裝飾器模式實現了 vue 的 ts 適配,也是官方推薦的使用 ts 方式。不過更建議使用"},{"type":"link","attrs":{"href":"https://github.com/kaorun343/vue-property-decorator","title":null},"content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"包,因爲後者在前者基礎上進行了修改與擴充。"},{"type":"codeinline","content":[{"type":"text","text":"vue-class-component"}]},{"type":"text","text":"擁有的功能"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"都具備,並且功能更強大,也更易於使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於使用 Vuex 的項目,建議安裝"},{"type":"link","attrs":{"href":"https://github.com/championswimmer/vuex-module-decorators","title":null},"content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":"包,這是在 ts 環境下中使用 vuex 的一種解決方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於 vue 對 jsx 的支持問題,如果想實現如同 react 的組件 props 的智能提示,需要安裝"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support","title":null},"content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"單文件組件(template 組件)的使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"組件實例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vue-class-component 允許我們通過使用類語法聲明 vue 組件,需要使用"},{"type":"codeinline","content":[{"type":"text","text":"@Component"}]},{"type":"text","text":"裝飾器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"生命週期"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生命週期鉤子的使用和原先使用的區別:在類語法中直接將生命週期生命爲方法(方法名稱和生命週期名稱一致)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n created() {\n console.log('created');\n }\n mounted() {\n console.log('mounted');\n }\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"響應式數據 data"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類語法中可以直接定義爲類的實例屬性作爲組件的響應式數據。原始類型的數據不需要定義類型,ts 可以實現類型推斷,但是複雜的類型需要定義。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中值得注意的一點是:當數據的值是 undefined 或者只定義未賦初值,"},{"type":"codeinline","content":[{"type":"text","text":"vue-class-component"}]},{"type":"text","text":"不會將該屬性修飾爲響應式數據!這會導致異常。推薦方案是進行賦初值,或者擴展一個 null 類型再賦值未 null。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n type User = {\n name: string;\n age: number;\n };\n @Component\n export default class Index extends Vue {\n message = 'hello world';\n info: User = { name: 'test', age: 25 };\n //如果數據的值是undefined或者未賦初值,則不會成爲響應式數據。解決方案:追加類型定義null\n count: number;\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"計算屬性 computed"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類語法中的計算屬性的實現,是通過 get 取值函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n //computed定義\n get introduction() {\n return `姓名:${this.info.name}, 年齡:${this.info.age}`;\n }\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"數據監聽 watch"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類語法實現響應式的數據監聽,是由"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"依賴提供"},{"type":"codeinline","content":[{"type":"text","text":"@Watch"}]},{"type":"text","text":"裝飾器來完成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n //watch定義,其中Wacth裝飾器第一個參數:響應式數據字符串(也可以定義爲'a.b');\n //第二個參數options成員[immediate,deep]分別對應的是原生的用法\n @Watch('$route', { immediate: true })\n changeRouter(val: Route, oldVal: Route) {\n console.log('$route watcher: ', val, oldVal);\n }\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"方法 methods"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在類語法實現原生 vue 的方法的方式,即通過直接定義類方法成員。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n hello(){\n console.log('hello world');\n }\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"引入組件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和原生寫法一致,都需要先引入在註冊,區別在於類語法註冊在修飾器中。組件使用方式和 vue 原生一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n import Header from '../component/header/index.vue';\n\n @Component({\n components: {\n Header,\n },\n })\n export default class Index extends Vue {\n }\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"組件屬性 props"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類語法實現組件 props 定義是通過裝飾器"},{"type":"codeinline","content":[{"type":"text","text":"@Prop"}]},{"type":"text","text":"實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component, Prop } from 'vue-property-decorator';\n import { User } from '@/types/one';\n\n @Component\n export default class Header extends Vue {\n @Prop({ type: String, default: '標題' }) readonly title?: string;\n //複雜類型type參數的值爲Object,默認值需要以函數形式返回\n @Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly author!: User;\n }\n\n\n //相當於\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"事件觸發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ts 環境下 vue 的事件觸發方式和 js 環境下是一致的,區別只是事件回調定義的地方不同(ts 定義爲類的實例方法,js 定義在 methods 屬性中)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ref 使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在類語法中使用 ref 需要藉助"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"提供的"},{"type":"codeinline","content":[{"type":"text","text":"@Ref"}]},{"type":"text","text":"裝飾器,使用方法如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//模板和原生vue保持一致\n\n\n\n\n//相當於\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"mixins 使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類語法使用 mixins 需要繼承"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"提供的 Mixins 函數所生成的類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mixins 函數的參數是 Vue 實例類,正確使用會用 mixin 成員的的智能提示,使用方式如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// mixins.js\n import Vue from 'vue';\n import Component from 'vue-class-component';\n\n // You can declare mixins as the same style as components.\n @Component\n export class Hello extends Vue {\n /**\n * mixin中的響應式數據\n */\n mixinText = 'Hello mixins';\n\n obj: { name: string } = { name: 'han' };\n }\n\n//index.vue\n\n\n//相當於\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"slots 和 scopedSlots"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slots 和 scopedSlots 的使用方式和原生 vue 保持一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"tsx 組件的使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在項目中需要使用 jsx,默認 vue-cli 創建項目會提示是否支持 jsx,但是由於 vue 對 jsx 的支持不完善,導致在使用不像 react 那樣可以提示組件 props 的類型定義,使用上非常難受。因此引入"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"解決該問題。詳情請見:"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support","title":null},"content":[{"type":"text","text":"vue-tsx-support(github 文檔)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於 在 vue 中如何使用 jsx,推薦"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/37920151","title":null},"content":[{"type":"text","text":"在 Vue 中使用 JSX 的正確姿勢"}]},{"type":"text","text":",該文詳細介紹了 vue 實現 jsx 的原理以及幾種 props 的區別和使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tsx 組件的很多地方和 template 組件使用方式一致,但是 props 定義、scopedSlots 定義和使用,以及引入第三方組件之後的處理方式有差異。其他地方例如生命週期、data、computed、watch、methods、事件觸發、ref 使用都是一致的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下載完"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":",我們需要配置"},{"type":"codeinline","content":[{"type":"text","text":"tsconfig.json"}]},{"type":"text","text":",設置內容如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" \"compilerOptions\": {\n \"jsx\": \"preserve\",\n \"jsxFactory\": \"VueTsxSupport\",\n \"...\": \"...\"\n },\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之後,我們需要在項目入口處引入"},{"type":"codeinline","content":[{"type":"text","text":"import 'vue-tsx-support/enable-check';"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在 tsx 組件的 props 智能提示開始生效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"組件定義的方式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"支持的 tsx 組件定義方式可以使用類似與原生 vue 的對象的寫法,或者類語法編寫。更推薦使用類語法編寫組件,這樣和模板寫法也更相近。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果喜歡接近原生 vue 的對象風格,可以參考:"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support#writing-components-by-object-style-api-like-vueextend","title":null},"content":[{"type":"text","text":"官方文檔"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用類語法編寫組件有兩種方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"通過繼承"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"提供的 Component 類來編寫"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"通過繼承 Vue 類並且聲明"},{"type":"codeinline","content":[{"type":"text","text":"_tsx"}]},{"type":"text","text":"成員"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"項目中一直在使用前者,但是最近總結經驗,發現後者更好些。主要是繼承 Component 之後使用 mixins 想要有智能提示的話,需要將定義掛載在 Vue 上,不夠友好。因此推薦使用:"},{"type":"text","marks":[{"type":"strong"}],"text":"通過繼承 Vue 類並且聲明"},{"type":"codeinline","content":[{"type":"text","text":"_tsx"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"成員"},{"type":"text","text":",下文都是針對該方案的說明。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"組件實例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聲明 tsx 組件,文件後綴必須爲"},{"type":"codeinline","content":[{"type":"text","text":".tsx"}]},{"type":"text","text":",這點和 react 不同,react 在 ts 文件中也是可以使用 jsx 的,但是 vue 不可以。如果一定要在"},{"type":"codeinline","content":[{"type":"text","text":".ts"}]},{"type":"text","text":"文件中,可以使用定義 jsx 原始方式,具體可參照"},{"type":"link","attrs":{"href":"https://cn.vuejs.org/v2/guide/render-function.html#%E5%AE%8C%E6%95%B4%E7%A4%BA%E4%BE%8B","title":null},"content":[{"type":"text","text":"vue 官網:"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 tsx 文件中,聲明組件的方式和 template 組件是一致的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { Vue, Component, Prop } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\n\n@Component\nexport default class Header extends Vue {\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"dataProps 定義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們需要和 template 組件一樣將所有的 props 定義好。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後根據情況,如果可以將所有 data 數據、computed 方法、方法定義設置爲私有,這樣可以使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"提供的"},{"type":"codeinline","content":[{"type":"text","text":"AutoProps別名"}]},{"type":"text","text":",來聲明 Props。如果有成員需要設置爲 public,可以使用 tsx 提供的"},{"type":"codeinline","content":[{"type":"text","text":"PickProps別名"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//AutoProps\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\nimport { User } from '@/types/one';\nimport styles from './index.less';\n\n@Component\nexport default class Header extends Vue {\n _tsx!: tsx.DeclareProps>;\n\n @Prop({ type: String, default: '標題' }) readonly title?: string;\n @Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly author!: User;\n\n private goAboutMe() {\n this.$router.push('/about');\n }\n\n render() {\n return (\n
\n
\n

{this.title}

\n \n 作者:\n {this.author.name}\n \n
\n
\n );\n }\n}\n\n//PickProps\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\nimport { User } from '@/types/one';\nimport styles from './index.less';\n\n@Component\nexport default class Header extends Vue {\n _tsx!: tsx.DeclareProps>;\n\n @Prop({ type: String, default: '標題' }) readonly title?: string;\n @Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly author!: User;\n\n goAboutMe() {\n this.$router.push('/about');\n }\n\n render() {\n return (\n
\n
\n

{this.title}

\n \n 作者:\n {this.author.name}\n \n
\n
\n );\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"eventProps 定義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"_tsx"}]},{"type":"text","text":"成員的類型可以定義爲交叉類型,將事件類型定義混入到"},{"type":"codeinline","content":[{"type":"text","text":"_tsx"}]},{"type":"text","text":"中就可以了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import * as tsx from 'vue-tsx-support';\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nexport default class Header extends Vue {\n _tsx!: tsx.DeclareProps> & tsx.DeclareOnEvents;\n render(){\n return
\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"scopedSlotsProps 定義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vue 中的 scopedSlots 相當於 react 中的 renderProp。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tsx 組件中定義如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import * as tsx from 'vue-tsx-support';\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nexport default class Header extends Vue {\n //這樣就聲明瞭兩個scopedSlot,默認的scopedSlot參數類型爲空,header參數類型爲string\n $scopedSlots!: tsx.InnerScopedSlots;\n render(){\n return
\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"mixins 使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"mixins 使用和 template 組件保持一致"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第三方組件 props 推斷"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於 vue 實現的 jsx 沒有參數類型提示,因此引入第三方組件也是沒有 props 提示。所有我們需要使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"來進行 jsx 支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我創建一份"},{"type":"codeinline","content":[{"type":"text","text":"propsCovert.ts"}]},{"type":"text","text":"文件,使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"提供的 ofType 方法來對第三方組件的 props 進行定義推導。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遞歸第三方組件的 dataProps,並將其類型推導出。eventProps 定義爲索引類型,參數類型定義爲 any。scopedSlotsProps 同樣定義爲索引類型,參數類型定義爲 any。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之後每次使用第三方組件,只要用 antdPropsConvert 方法包裝下即可在使用時得到 props 的智能提示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是單頁應用,也可以創建一份組件清單文件,在該文件中轉換所有的組件並導出,這樣就省的一次次轉換。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//propsConvert.ts\n\nimport { ofType } from 'vue-tsx-support';\n\ntype PowerPartial = {\n // 如果是 object,則遞歸類型\n [U in keyof T]?: T[U] extends Function ? Function : T[U] extends object ? PowerPartial : T[U];\n};\ntype Omit = Pick>;\ntype OmitVue = PowerPartial>;\n\ninterface AnyEvent {\n [key: string]: any;\n}\ninterface AnyScopedSlots {\n [key: string]: any;\n}\n\nfunction antdPropsConvert(componentType: new (...args: any[]) => T) {\n return ofType, AnyEvent, AnyScopedSlots>().convert(componentType);\n}\nexport { antdPropsConvert };\n\n\n// sider.tsx\nimport { Vue, Component } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\nimport { Button as AButton } from 'ant-design-vue';\nimport styles from './index.less';\nimport { antdPropsConvert } from '@/utils/propsConvert';\n\nconst Button = antdPropsConvert(AButton);\n\n@Component\nexport default class Sider extends Vue {\n _tsx!: tsx.DeclareOnEvents;\n\n $scopedSlots!: tsx.InnerScopedSlots;\n\n render() {\n return (\n
\n {this.$scopedSlots.default && this.$scopedSlots.default()}\n
\n \n
\n
\n );\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"事件修飾符"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何在 tsx 組件中使用事件修飾符,推薦官方教程,"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support#modifiers","title":null},"content":[{"type":"text","text":"modifiers"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"遺留問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在單文件組件模式中,文件跳轉正常(ctrl+鼠標點擊可以跳轉到定義),但是暫未實現路徑的智能提示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":".tsx | .ts"}]},{"type":"text","text":"文件引入"},{"type":"codeinline","content":[{"type":"text","text":".vue"}]},{"type":"text","text":",路徑智能提示正常,但是會發生無法跳轉到 vue 文件的情況。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vue-router 的 ts 方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":"官方已經支持 ts,在我們使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-cli"}]},{"type":"text","text":"創建了 ts 項目之後就可以使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是如果我們需要在組件中定義路由鉤子函數,需要先在全局進行註冊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// class-component-hooks.js\nimport Component from 'vue-class-component';\n\n// Register the router hooks with their names\nComponent.registerHooks(['beforeRouteEnter', 'beforeRouteLeave', 'beforeRouteUpdate']);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後需要給 Vue 類型擴展定義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import Vue from 'vue';\nimport { Route, NavigationGuardNext } from 'vue-router';\ndeclare module 'vue/types/vue' {\n // Augment component instance type\n interface Vue {\n beforeRouteEnter?(to: Route, from: Route, next: NavigationGuardNext): void;\n\n beforeRouteLeave?(to: Route, from: Route, next: NavigationGuardNext): void;\n\n beforeRouteUpdate?(to: Route, from: Route, next: NavigationGuardNext): void;\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用前,在項目的入口文件引入註冊文件即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import '@/utils/class-component-hooks';\nimport Vue from 'vue';\nimport 'vue-tsx-support/enable-check';\nimport App from './App';\nimport router from './router';\nimport store from '@/modules';\nVue.config.productionTip = false;\nnew Vue({\n router,\n store,\n render: (h) => h(App),\n}).$mount('#app');\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後在組件中定義路由鉤子,即可獲得準確的提示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vuex 的 ts 方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了在 ts 環境中使用 vuex,vue 社區推出了"},{"type":"link","attrs":{"href":"https://github.com/championswimmer/vuex-module-decorators","title":null},"content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":",其工作方式和"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"一致,都是通過裝飾器來實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"模塊創建"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":"中常使用的成員:"},{"type":"codeinline","content":[{"type":"text","text":"VuexModule, Module, Mutation, Action, getModule"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建步驟:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"定義 Module 實例之前,我們需要先定義 state 的接口,這是爲了之後"},{"type":"codeinline","content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":"進行類型檢測。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"自定義 Module 類型,繼承 VuexModule 類型,並實現 state 的接口"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"使用"},{"type":"codeinline","content":[{"type":"text","text":"@Module"}]},{"type":"text","text":"裝飾器裝飾自定義 module,如果是動態 Module(意味着引入的時候自動注入到 vuex 中),需要傳參"},{"type":"codeinline","content":[{"type":"text","text":"dynamic, store, name"}]},{"type":"text","text":"給 Module 函數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"定義 action 和 mutation 我們都需要使用對應的裝飾器"},{"type":"codeinline","content":[{"type":"text","text":"@Action、@Mutation"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"導出自定義 Module,將自定義 Module 作爲函數參數傳遞給 getModule 函數,該 module 中所有的 state,action,mutation 都綁定在導出對象上"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完整示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators';\nimport store from './index';\n\ntype TodoItem = {\n id: string;\n content: string;\n isDone: boolean;\n};\ntype TodoListState = {\n todos: TodoItem[];\n};\nconst todos: TodoItem[] = [\n {\n id: '0',\n content: 'todo-item1',\n isDone: false,\n },\n {\n id: '1',\n content: 'todo-item2',\n isDone: true,\n },\n {\n id: '2',\n content: 'todo-item3',\n isDone: false,\n },\n];\n@Module({ dynamic: true, store, name: 'todoListModule' })\nclass TodoListModule extends VuexModule implements TodoListState {\n todos: TodoItem[] = [];\n\n //獲取當前的todoList\n @Action\n async getAllTodoItems() {\n const data = await new Promise((resolve) => {\n setTimeout(resolve, 1000, todos);\n });\n this._saveTodos(data);\n }\n\n @Mutation\n private _saveTodos(data: TodoItem[]) {\n this.todos = data;\n }\n}\nexport default getModule(TodoListModule);\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"store 創建和使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建 store 實例,由於項目是使用動態導入 module,因此很簡潔。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果需要在入口文件定義好全部 module,可以參照"},{"type":"link","attrs":{"href":"https://github.com/championswimmer/vuex-module-decorators#usage","title":null},"content":[{"type":"text","text":"官方教程"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import Vue from 'vue';\nimport Vuex from 'vuex';\n\nVue.use(Vuex);\n\n// Declare empty store first, dynamically register all modules later.\nconst Store = new Vuex.Store({});\nexport default Store;\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vuex 使用和原生 vue 一致,都是引入 store 的入口文件,然後將其傳入 Vue 實例中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"在組件中使用 vuex(動態導入 Module)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用步驟:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"需要導入對應的 module 文件"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"導入 state,因爲 state 成員通過計算屬性使用,因此在 ts 中需要通過 get 函數導入"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"調用 action 或者 mutation 方法,直接調用對應的 Module 即可"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { Component, Vue } from 'vue-property-decorator';\nimport TodoListModule from '@/modules/todoList';\n\n@Component\nexport default class Index extends Vue {\n get todos() {\n return TodoListModule.todos;\n }\n\n created() {\n TodoListModule.getAllTodoItems().then(() => {\n console.log('todos', this.todos);\n });\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"api 類型定義的建議"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在項目中,定義 api 接口的類型是個麻煩事,尤其是接口很多的情況下。如果手動定義,成本會很大,也影響效率。當接口修改(這是常常發生的),我們將不得不進行同步的修正。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此我建議使用阿里團隊出品的"},{"type":"codeinline","content":[{"type":"text","text":"pont"}]},{"type":"text","text":"庫,該庫有效的解決了 api 接口定義的麻煩問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"詳情請見官網:"},{"type":"link","attrs":{"href":"https://github.com/alibaba/pont","title":null},"content":[{"type":"text","text":"pont"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章