typescript

 使用官方腳手架構建

npm install -g @vue/cli # OR yarn global add @vue/cli 複製代碼

新的Vue CLI工具容許開發者 使用 TypeScript 集成環境 建立新項目。javascript

只需運行vue create my-apphtml

而後,命令行會要求選擇預設。使用箭頭鍵選擇Manually select features前端

接下來,只需確保選擇了TypeScriptBabel選項,以下圖:vue

 

image-20190611163034679

 

完成此操做後,它會詢問你是否要使用class-style component syntaxjava

而後配置其他設置,使其看起來以下圖所示。python

 

image-20190611163127181

 

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

 

image-20190611163225739 接下來就跑項目喇。

 

 

image-20190611163245714

 

總之,先跑起來再說。git

2. 項目目錄解析

經過tree指令查看目錄結構後可發現其結構和正常構建的大有不一樣。github

 

image-20190611163812421

 

這裏主要關注shims-tsx.d.tsshims-vue.d.ts兩個文件web

兩句話歸納:

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

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

<template> <div class="hello"> <h1>{{ msg }}</h1> <!-- 省略 --> </div> </template> <script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue { @Prop() private msg!: string; } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped></style> 複製代碼

至此,準備開啓新的篇章 TypeScript極速入門 和 vue-property-decorator

3. TypeScript極速入門

3.1 基本類型和擴展類型

 

image-20190611173126273

 

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

  • 元組 Tuple
  • 枚舉 enum
  • AnyVoid

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 list: number[] = [1, 2, 3]; let list: Array<number> = [1, 2, 3]; let u: undefined = undefined; let n: null = null; 複製代碼

2. 特殊類型

1. 元組 Tupleimage-20190611174157121

想象 元組 做爲有組織的數組,你須要以正確的順序預約義數據類型。

const messyArray = [' something', 2, true, undefined, null]; const tuple: [number, string, string] = [24, "Indrek" , "Lasn"] 複製代碼

若是不遵循 爲元組 預設排序的索引規則,那麼Typescript會警告。

 

image-20190611174515658

 

​ (tuple第一項應爲number類型)

2. 枚舉 enum

 

image-20190611174833904

 

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 複製代碼

另外一個很好的例子是使用枚舉來存儲應用程序狀態。

 

image-20190611175542886

 

3. Void

 

image-20190611175724302

 

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

 

image-20190611175858587

 

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

 

image-20190611175932713

 

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

 

image-20190611180043827

 

此時將沒法 return

4. Any

 

image-20190611180255381

 

Emmm...就是什麼類型都行,當你沒法確認在處理什麼類型時能夠用這個。

但要慎重使用,用多了就失去使用Ts的意義。

let person: any = "前端勸退師" person = 25 person = true 複製代碼

主要應用場景有:

  1. 接入第三方庫
  2. Ts菜逼前期都用
5. Never

 

image-20190611180943940

 

用很粗淺的話來描述就是:"Never是你永遠得不到的爸爸。"

具體的行爲是:

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

 

image-20190611181410052

 

3. 類型斷言

 

image-20190611182337690

 

簡略的定義是:能夠用來手動指定一個值的類型。

有兩種寫法,尖括號和as:

let someValue: any = "this is a string"; let strLength: number = (<string>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 ((<string>something).length) { return (<string>something).length; } else { return something.toString().length; } } 複製代碼

3.2 泛型:Generics

軟件工程的一個主要部分就是構建組件,構建的組件不只須要具備明確的定義和統一的接口,同時也須要組件可複用。支持現有的數據類型和未來添加的數據類型的組件爲大型軟件系統的開發過程提供很好的靈活性。

C#Java中,可使用"泛型"來建立可複用的組件,而且組件可支持多種數據類型。這樣即可以讓用戶根據本身的數據類型來使用組件。

1. 泛型方法

在TypeScript裏,聲明泛型方法有如下兩種方式:

function gen_func1<T>(arg: T): T { return arg; } // 或者 let gen_func2: <T>(arg: T) => T = function (arg) { return arg; } 複製代碼

調用方式也有兩種:

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

3. 泛型類型

泛型接口:

interface Generics_interface<T> { (arg: T): T; } function func_demo<T>(arg: T): T { return arg; } let func1: Generics_interface<number> = func_demo; func1(123); // 正確類型的實際參數 func1('123'); // 錯誤類型的實際參數 複製代碼

3.3 自定義類型:Interface vs Type alias

Interface,國內翻譯成接口。

Type alias,類型別名。

 

image-20190613192317416

 

如下內容來自:

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):

interfacetype 均可以拓展,而且二者並非相互獨立的,也就是說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<string, Person>; type Callback<T> = (data: T) => void; type Pair<T> = [T, T]; type Coordinates = Pair<number>; type Tree<T> = T | { left: Tree<T>, right: Tree<T> }; 複製代碼

interface能夠而 type不行

interface 可以聲明合併

interface User { name: string age: number } 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 複製代碼

搭配 interfacetype的用法有:

 

image-20190612003025759

 

3.5 聲明文件與命名空間:declarenamespace

前面咱們講到Vue項目中的shims-tsx.d.tsshims-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 訪問修飾符:privatepublicprotected

其實很好理解:

  1. 默認爲public

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

class Animal { private name: string; constructor(theName: string) { this.name = theName; } } let a = new Animal('Cat').name; //錯誤,‘name’是私有的 複製代碼
  1. protectedprivate相似,可是,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

 

image-20190613013846506

 

咱們回到src/components/HelloWorld.vue

<template> <div class="hello"> <h1>{{ msg }}</h1> <!-- 省略 --> </div> </template> <script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue { @Prop() private msg!: string; } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped></style> 複製代碼

有寫過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 ... 複製代碼

上面代碼能夠看出來:

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

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

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

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

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

 

狗狗祟祟,鬼鬼祟祟表æƒ
åŒ
,三狗并祟,三个狗穿着小裙子,鬼鬼祟祟表æƒ
åŒ
.jpg

 

2. vue-property-decoratorvuex-class提供的裝飾器

vue-property-decorator的裝飾器:

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.$store.state.count } }, methods: { changeMessage () { this.message = "Good bye" }, getName() { let name = this.$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; $t: TranslationFunction; } } 複製代碼

以後使用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,作一個博客帖子組件:

<template> <div class="blogpost"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> <p class="meta">Written by {{ post.author }} on {{ date }}</p> </div> </template> <script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; // 在這裏對數據進行類型約束 export interface Post { title: string; body: string; author: string; datePosted: Date; } @Component export default class HelloWorld extends Vue { @Prop() private post!: Post; get date() { return `${this.post.datePosted.getDate()}/${this.post.datePosted.getMonth()}/${this.post.datePosted.getFullYear()}`; } } </script> <style scoped> h2 { text-decoration: underline; } p.meta { font-style: italic; } </style> 複製代碼

而後在Home.vue中使用:

<template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png"> <HelloWorld v-for="blogPost in blogPosts" :post="blogPost" :key="blogPost.title" /> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import HelloWorld, { Post } from '@/components/HelloWorld.vue'; // @ is an alias to /src @Component({ components: { HelloWorld, }, }) export default class Home extends Vue { private blogPosts: Post[] = [ { title: 'My first blogpost ever!', body: 'Lorem ipsum dolor sit amet.', author: 'Elke', datePosted: new Date(2019, 1, 18), }, { title: 'Look I am blogging!', body: 'Hurray for me, this is my second post!', author: 'Elke', datePosted: new Date(2019, 1, 19), }, { title: 'Another one?!', body: 'Another one!', author: 'Elke', datePosted: new Date(2019, 1, 20), }, ]; } </script> 

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