Vue3.0 前的 TypeScript 最佳入門實踐

前言
其實Vue官方從2.6.X版本開始就部分使用Ts重寫了。
我我的對更嚴格類型限制沒有積極的見解,畢竟各種轉類型的騷寫法寫習慣了。
然鵝最近的一個項目中,是TypeScript+ Vue,毛計喇,學之……真香!
注意此篇標題的「前」,本文旨在講Ts混入框架的使用,不講Class API前端

  1. 使用官方腳手架構建
    npm install -g @vue/cli
OR

yarn global add @vue/cli
複製代碼新的Vue CLI工具容許開發者 使用 TypeScript 集成環境 建立新項目。
只需運行vue create my-app。
而後,命令行會要求選擇預設。使用箭頭鍵選擇Manually select features。
接下來,只需確保選擇了TypeScript和Babel選項,以下圖:vue

完成此操做後,它會詢問你是否要使用class-style component syntax。
而後配置其他設置,使其看起來以下圖所示。python

Vue CLI工具如今將安裝全部依賴項並設置項目。ios

接下來就跑項目喇。ajax

總之,先跑起來再說。
2. 項目目錄解析
經過tree指令查看目錄結構後可發現其結構和正常構建的大有不一樣。vuex

這裏主要關注shims-tsx.d.ts和 shims-vue.d.ts兩個文件
兩句話歸納:typescript

shims-tsx.d.ts,容許你以.tsx結尾的文件,在Vue項目中編寫jsx代碼
shims-vue.d.ts 主要用於 TypeScript 識別.vue 文件,Ts默認並不支持導入 vue 文件,這個文件告訴ts 導入.vue 文件都按VueConstructor處理。npm

此時咱們打開親切的src/components/HelloWorld.vue,將會發現寫法已大有不一樣
axios

{{ msg }}

複製代碼至此,準備開啓新的篇章 TypeScript極速入門 和 vue-property-decorator
3. TypeScript極速入門
3.1 基本類型和擴展類型api

Typescript與Javascript共享相同的基本類型,但有一些額外的類型。

元組 Tuple
枚舉 enum
Any 與Void

  1. 基本類型合集
    // 數字,2、8、十六進制都支持
    let decLiteral: number = 6;
    let hexLiteral: number = 0xf00d;

// 字符串,單雙引都行
let name: string = 「bob」;
let sentence: string = `Hello, my name is ${ name }.

// 數組,第二種方式是使用數組泛型,Array

let u: undefined = undefined;
let n: null = null;

複製代碼2. 特殊類型

  1. 元組 Tuple
    想象 元組 做爲有組織的數組,你須要以正確的順序預約義數據類型。
    const messyArray = [’ something’, 2, true, undefined, null];
    const tuple: [number, string, string] = [24, 「Indrek」 , 「Lasn」]
    複製代碼若是不遵循 爲元組 預設排序的索引規則,那麼Typescript會警告。

(tuple第一項應爲number類型)
2. 枚舉 enum

enum類型是對JavaScript標準數據類型的一個補充。 像C#等其它語言同樣,使用枚舉類型能夠爲一組數值賦予友好的名字。
// 默認狀況從0開始爲元素編號,也可手動爲1開始
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

let colorName: string = Color[2];
console.log(colorName); // 輸出’Green’由於上面代碼裏它的值是2
複製代碼另外一個很好的例子是使用枚舉來存儲應用程序狀態。

  1. Void

在Typescript中,你必須在函數中定義返回類型。像這樣:

若沒有返回值,則會報錯:

咱們能夠將其返回值定義爲void:

此時將沒法 return
4. Any

Emmm…就是什麼類型都行,當你沒法確認在處理什麼類型時能夠用這個。
但要慎重使用,用多了就失去使用Ts的意義。
let person: any = 「前端勸退師」
person = 25
person = true
複製代碼主要應用場景有:

接入第三方庫
Ts菜逼前期都用

  1. Never

用很粗淺的話來描述就是:「Never是你永遠得不到的爸爸。」
具體的行爲是:

throw new Error(message)
return error(「Something failed」)
while (true) {} // 存在沒法達到的終點

  1. 類型斷言

簡略的定義是:能夠用來手動指定一個值的類型。
有兩種寫法,尖括號和as:
let someValue: any = 「this is a string」;

let strLength: number = (someValue).length;
let strLength: number = (someValue as string).length;
複製代碼使用例子有:
當 TypeScript 不肯定一個聯合類型的變量究竟是哪一個類型的時候,咱們只能訪問此聯合類型的全部類型裏共有的屬性或方法:
function getLength(something: string | number): number {
return something.length;
}

// index.ts(2,22): error TS2339: Property ‘length’ does not exist on type ‘string | number’.
// Property ‘length’ does not exist on type ‘number’.
複製代碼若是你訪問長度將會報錯,而有時候,咱們確實須要在還不肯定類型的時候就訪問其中一個類型的屬性或方法,此時須要斷言纔不會報錯:
function getLength(something: string | number): number {
if ((something).length) {
return (something).length;
} else {
return something.toString().length;
}
}
複製代碼3.2 泛型:Generics
軟件工程的一個主要部分就是構建組件,構建的組件不只須要具備明確的定義和統一的接口,同時也須要組件可複用。支持現有的數據類型和未來添加的數據類型的組件爲大型軟件系統的開發過程提供很好的靈活性。
在C#和Java中,可使用"泛型"來建立可複用的組件,而且組件可支持多種數據類型。這樣即可以讓用戶根據本身的數據類型來使用組件。

  1. 泛型方法
    在TypeScript裏,聲明泛型方法有如下兩種方式:
    function gen_func1(arg: T): T {
    return arg;
    }
    // 或者
    let gen_func2: (arg: T) => T = function (arg) {
    return arg;
    }
    複製代碼調用方式也有兩種:
    gen_func1(‘Hello world’);
    gen_func2(‘Hello world’);
    // 第二種調用方式可省略類型參數,由於編譯器會根據傳入參數來自動識別對應的類型。
    複製代碼2. 泛型與Any
    Ts 的特殊類型 Any 在具體使用時,能夠代替任意類型,咋一看二者好像沒啥區別,其實否則:
    // 方法一:帶有any參數的方法
    function any_func(arg: any): any {
    console.log(arg.length);
    return arg;
    }

// 方法二:Array泛型方法
function array_func(arg: Array): Array {
console.log(arg.length);
return arg;
}
複製代碼
方法一,打印了arg參數的length屬性。由於any能夠代替任意類型,因此該方法在傳入參數不是數組或者帶有length屬性對象時,會拋出異常。
方法二,定義了參數類型是Array的泛型類型,確定會有length屬性,因此不會拋出異常。

  1. 泛型類型
    泛型接口:
    interface Generics_interface {
    (arg: T): T;
    }

function func_demo(arg: T): T {
return arg;
}

let func1: Generics_interface = func_demo;
func1(123); // 正確類型的實際參數
func1(‘123’); // 錯誤類型的實際參數
複製代碼3.3 自定義類型:Interface vs Type alias
Interface,國內翻譯成接口。
Type alias,類型別名。

如下內容來自:

Typescript 中的 interface 和 type 到底有什麼區別

  1. 相同點
    均可以用來描述一個對象或函數:
    interface User {
    name: string
    age: number
    }

type User = {
name: string
age: number
};

interface SetUser {
(name: string, age: number): void;
}
type SetUser = (name: string, age: number): void;

複製代碼都容許拓展(extends):
interface 和 type 均可以拓展,而且二者並非相互獨立的,也就是說interface能夠 extends type, type 也能夠 extends interface 。 雖然效果差很少,可是二者語法不一樣。
interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
複製代碼type extends type
type Name = {
name: string;
}
type User = Name & { age: number };
複製代碼interface extends type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
複製代碼type extends interface
interface Name {
name: string;
}
type User = Name & {
age: number;
}

複製代碼2. 不一樣點
type 能夠而 interface 不行

type 能夠聲明基本類型別名,聯合類型,元組等類型

// 基本類型別名
type Name = string

// 聯合類型
interface Dog {
wong();
}
interface Cat {
miao();
}

type Pet = Dog | Cat

// 具體定義數組每一個位置的類型
type PetList = [Dog, Pet]
複製代碼
type 語句中還可使用 typeof獲取實例的 類型進行賦值

// 當你想獲取一個變量的類型時,使用 typeof
let div = document.createElement(‘div’);
type B = typeof div
複製代碼
其餘騷操做

type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary

interface User {
sex: string
}

/*
User 接口爲 {
name: string
age: number
sex: string
}
*/

複製代碼interface 有可選屬性和只讀屬性

可選屬性
接口裏的屬性不全都是必需的。 有些是隻在某些條件下存在,或者根本不存在。 例如給函數傳入的參數對象中只有部分屬性賦值了。帶有可選屬性的接口與普通的接口定義差很少,只是在可選屬性名字定義的後面加一個?符號。以下所示

interface Person {
name: string;
age?: number;
gender?: number;
}
複製代碼

只讀屬性
顧名思義就是這個屬性是不可寫的,對象屬性只能在對象剛剛建立的時候修改其值。 你能夠在屬性名前用 readonly來指定只讀屬性,以下所示:

interface User {
readonly loginName: string;
password: string;
}
複製代碼上面的例子說明,當完成User對象的初始化後loginName就不能夠修改了。
3.4 實現與繼承:implementsvsextends
extends很明顯就是ES6裏面的類繼承,那麼implement又是作什麼的呢?它和extends有什麼不一樣?
implement,實現。與C#或Java裏接口的基本做用同樣,TypeScript也可以用它來明確的強制一個類去符合某種契約
implement基本用法:
interface IDeveloper {
name: string;
age?: number;
}
// OK
class dev implements IDeveloper {
name = ‘Alex’;
age = 20;
}
// OK
class dev2 implements IDeveloper {
name = ‘Alex’;
}
// Error
class dev3 implements IDeveloper {
name = ‘Alex’;
age = ‘9’;
}
複製代碼而extends是繼承父類,二者其實能夠混着用:
class A extends B implements C,D,E
複製代碼搭配 interface和type的用法有:

3.5 聲明文件與命名空間:declare 和 namespace
前面咱們講到Vue項目中的shims-tsx.d.ts和shims-vue.d.ts,其初始內容是這樣的:
// shims-tsx.d.ts
import Vue, { VNode } from ‘vue’;

declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

// shims-vue.d.ts
declare module ‘*.vue’ {
import Vue from ‘vue’;
export default Vue;
}

複製代碼declare:當使用第三方庫時,咱們須要引用它的聲明文件,才能得到對應的代碼補全、接口提示等功能。
這裏列舉出幾個經常使用的:
declare var 聲明全局變量
declare function 聲明全局方法
declare class 聲明全局類
declare enum 聲明全局枚舉類型
declare global 擴展全局變量
declare module 擴展模塊
複製代碼namespace:「內部模塊」如今稱作「命名空間」
module X { 至關於如今推薦的寫法 namespace X {)
跟其餘 JS 庫協同
相似模塊,一樣也能夠經過爲其餘 JS 庫使用了命名空間的庫建立 .d.ts 文件的聲明文件,如爲 D3 JS 庫,能夠建立這樣的聲明文件:
declare namespace D3{
export interface Selectors { … }
}
declare var d3: D3.Base;
複製代碼因此上述兩個文件:

shims-tsx.d.ts, 在全局變量 global中批量命名了數個內部模塊。
shims-vue.d.ts,意思是告訴 TypeScript *.vue 後綴的文件能夠交給 vue 模塊來處理。

3.6 訪問修飾符:private、public、protected
其實很好理解:

默認爲public

當成員被標記爲private時,它就不能在聲明它的類的外部訪問,好比:

class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}

let a = new Animal(‘Cat’).name; //錯誤,‘name’是私有的
複製代碼
protected和private相似,可是,protected成員在派生類中能夠訪問

class Animal {
protected name: string;
constructor(theName: string) {
this.name = theName;
}
}

class Rhino extends Animal {
constructor() {
super(‘Rhino’);
}
getName() {
console.log(this.name) //此處的name就是Animal類中的name
}
}
複製代碼3.7 可選參數 ( ?: )和非空斷言操做符(!.)
可選參數
function buildName(firstName: string, lastName?: string) {
return firstName + ’ ’ + lastName
}

// 錯誤演示
buildName(「firstName」, 「lastName」, 「lastName」)
// 正確演示
buildName(「firstName」)
// 正確演示
buildName(「firstName」, 「lastName」)
複製代碼非空斷言操做符:
能肯定變量值必定不爲空時使用。
與可選參數 不一樣的是,非空斷言操做符不會防止出現 null 或 undefined。
let s = e!.name; // 斷言e是非空並訪問name屬性
複製代碼4. Vue組件的Ts寫法
從 vue2.5 以後,vue 對 ts 有更好的支持。根據官方文檔,vue 結合 typescript ,有兩種書寫方式:
**Vue.extend **
import Vue from ‘vue’

const Component = Vue.extend({
// type inference enabled
})
複製代碼vue-class-component
import { Component, Vue, Prop } from ‘vue-property-decorator’

@Component
export default class Test extends Vue {
@Prop({ type: Object })
private test: { value: string }
}
複製代碼理想狀況下,Vue.extend 的書寫方式,是學習成本最低的。在現有寫法的基礎上,幾乎 0 成本的遷移。
可是Vue.extend模式,須要與mixins 結合使用。在 mixin 中定義的方法,不會被 typescript 識別到
,這就意味着會出現丟失代碼提示、類型檢查、編譯報錯等問題。
菜鳥才作選擇,大佬都挑最好的。直接講第二種吧:
4.1 vue-class-component

咱們回到src/components/HelloWorld.vue

{{ msg }}

複製代碼有寫過python的同窗應該會發現似曾相識:

vue-property-decorator這個官方支持的庫裏,提供了函數 **裝飾器(修飾符)**語法

  1. 函數修飾符 @
    「@」,與其說是修飾函數倒不如說是引用、調用它修飾的函數。
    或者用句大白話描述:@: 「下面的被我包圍了。」
    舉個栗子,下面的一段代碼,裏面兩個函數,沒有被調用,也會有輸出結果:
    test(f){
    console.log(「before …」);
    f()
    console.log(「after …」);
    }

@test
func(){
console.log(「func was called」);
}
複製代碼直接運行,輸出結果:
before …
func was called
after …
複製代碼上面代碼能夠看出來:

只定義了兩個函數:test和func,沒有調用它們。
若是沒有「@test」,運行應該是沒有任何輸出的。

可是,解釋器讀到函數修飾符「@」的時候,後面步驟會是這樣:

去調用test函數,test函數的入口參數就是那個叫「func」的函數;

test函數被執行,入口參數的(也就是func函數)會被調用(執行);

換言之,修飾符帶的那個函數的入口參數,就是下面的那個整個的函數。有點兒相似JavaScript裏面的
function a (function () { … });

  1. vue-property-decorator和vuex-class提供的裝飾器
    vue-property-decorator的裝飾器:

@Prop
@PropSync
@Provide
@Model
@Watch
@Inject
@Provide
@Emit
@Component (provided by vue-class-component)
Mixins (the helper function named mixins provided by vue-class-component)

vuex-class的裝飾器:

@State
@Getter
@Action
@Mutation

咱們拿原始Vue組件模版來看:
import {componentA,componentB} from ‘@/components’;

export default {
components: { componentA, componentB},
props: {
propA: { type: Number },
propB: { default: ‘default value’ },
propC: { type: [String, Boolean] },
}
// 組件數據
data () {
return {
message: ‘Hello’
}
},
// 計算屬性
computed: {
reversedMessage () {
return this.message.split(’’).reverse().join(’’)
}
// Vuex數據
step() {
return this.KaTeX parse error: Expected 'EOF', got '}' at position 23: …tate.count }̲ }, methods…store.getters[‘person/name’]
return name
}
},
// 生命週期
created () { },
mounted () { },
updated () { },
destroyed () { }
}
複製代碼以上模版替換成修飾符寫法則是:
import { Component, Vue, Prop } from ‘vue-property-decorator’;
import { State, Getter } from ‘vuex-class’;
import { count, name } from ‘@/person’
import { componentA, componentB } from ‘@/components’;

@Component({
components:{ componentA, componentB},
})
export default class HelloWorld extends Vue{
@Prop(Number) readonly propA!: number | undefined
@Prop({ default: ‘default value’ }) readonly propB!: string
@Prop([String, Boolean]) readonly propC!: string | boolean | undefined

// 原data
message = ‘Hello’

// 計算屬性
private get reversedMessage (): string[] {
return this.message.split(’’).reverse().join(’’)
}
// Vuex 數據
@State((state: IRootState) => state . booking. currentStep) step!: number
@Getter( ‘person/name’) name!: name

// method
public changeMessage (): void {
this.message = ‘Good bye’
},
public getName(): string {
let storeName = name
return storeName
}
// 生命週期
private created ():void { },
private mounted ():void { },
private updated ():void { },
private destroyed ():void { }
}
複製代碼正如你所看到的,咱們在生命週期 列表那都添加private XXXX方法,由於這不該該公開給其餘組件。
而不對method作私有約束的緣由是,可能會用到@Emit來向父組件傳遞信息。
4.2 添加全局工具
引入全局模塊,須要改main.ts:
import Vue from ‘vue’;
import App from ‘./App.vue’;
import router from ‘./router’;
import store from ‘./store’;

Vue.config.productionTip = false;

new Vue({
router,
store,
render: (h) => h(App),
}).$mount(’#app’);
複製代碼npm i VueI18n
import Vue from ‘vue’;
import App from ‘./App.vue’;
import router from ‘./router’;
import store from ‘./store’;
// 新模塊
import i18n from ‘./i18n’;

Vue.config.productionTip = false;

new Vue({
router,
store,
i18n, // 新模塊
render: (h) => h(App),
}).$mount(’#app’);
複製代碼但僅僅這樣,還不夠。你須要動src/vue-shim.d.ts:
// 聲明全局方法
declare module ‘vue/types/vue’ {
interface Vue {
readonly $i18n: VueI18Next;
KaTeX parse error: Expected 'EOF', got '}' at position 29: …nFunction; }̲ } 複製代碼以後使用this…i18n()的話就不會報錯了。
4.3 Axios 使用與封裝
Axios的封裝千人千面
若是隻是想簡單在Ts裏體驗使用Axios,能夠安裝vue-axios
簡單使用Axios
$ npm i axios vue-axios
複製代碼main.ts添加:
import Vue from ‘vue’
import axios from ‘axios’
import VueAxios from ‘vue-axios’

Vue.use(VueAxios, axios)
複製代碼而後在組件內使用:
Vue.axios.get(api).then((response) => {
console.log(response.data)
})

this.axios.get(api).then((response) => {
console.log(response.data)
})

this.$http.get(api).then((response) => {
console.log(response.data)
})
複製代碼1. 新建文件request.ts
文件目錄:
-api
- main.ts // 實際調用
-utils
- request.ts // 接口封裝
複製代碼2. request.ts文件解析
import * as axios from ‘axios’;
import store from ‘@/store’;
// 這裏可根據具體使用的UI組件庫進行替換
import { Toast } from ‘vant’;
import { AxiosResponse, AxiosRequestConfig } from ‘axios’;

/* baseURL 按實際項目來定義 */
const baseURL = process.env.VUE_APP_URL;

/* 建立axios實例 */
const service = axios.default.create({
baseURL,
timeout: 0, // 請求超時時間
maxContentLength: 4000,
});

service.interceptors.request.use((config: AxiosRequestConfig) => {
return config;
}, (error: any) => {
Promise.reject(error);
});

service.interceptors.response.use(
(response: AxiosResponse) => {
if (response.status !== 200) {
Toast.fail(‘請求錯誤!’);
} else {
return response.data;
}
},
(error: any) => {
return Promise.reject(error);
});

export default service;
複製代碼爲了方便,咱們還須要定義一套固定的 axios 返回的格式,新建ajax.ts:
export interface AjaxResponse {
code: number;
data: any;
message: string;
}
複製代碼3. main.ts接口調用:
// api/main.ts
import request from ‘…/utils/request’;

// get
export function getSomeThings(params:any) {
return request({
url: ‘/api/getSomethings’,
});
}

// post
export function postSomeThings(params:any) {
return request({
url: ‘/api/postSomethings’,
methods: ‘post’,
data: params
});
}
複製代碼5. 編寫一個組件
爲了減小時間,咱們來替換掉src/components/HelloWorld.vue,作一個博客帖子組件:


{{ post.title }}


{{ post.body }}


Written by {{ post.author }} on {{ date }}




複製代碼而後在Home.vue中使用:

Vue logo

複製代碼這時候運行項目:

這就是簡單的父子組件

  1. 參考文章
    TypeScript — JavaScript with superpowers — Part II
    VUE WITH TYPESCRIPT
    TypeScript + 大型項目實戰
    Python修飾符 (一)—— 函數修飾符 「@」
    Typescript 中的 interface 和 type到底有什麼區別
  2. 總結

而關於Class API撤銷,其實仍是挺舒服的。
用class 來編寫 Vue組件確實太奇怪了。
(因此我這篇Ts入門壓根沒寫Class API)

❤️

做者:前端勸退師 連接:https://juejin.im/post/5d0259f2518825405d15ae62 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索