我個人對更嚴格類型限制沒有積極的看法,畢竟各類轉類型的騷寫法寫習慣了。
然鵝最近的一個項目中,是 TypeScript+ Vue,毛計喇,學之...…真香!
1、使用官方腳手架構建npm install -g @vue/cli
# OR
yarn global add @vue/cli
新的 VueCLI工具允許開發者 使用 TypeScript 集成環境 創建新項目。
只需運行 vue createmy-app。
然後,命令行會要求選擇預設。使用箭頭鍵選擇 Manuallyselectfeatures。
接下來,只需確保選擇了 TypeScript和 Babel選項,如下圖:
完成此操作後,它會詢問你是否要使用 class-style component syntax。
然後配置其餘設置,使其看起來如下圖所示。
Vue CLI工具現在將安裝所有依賴項並設置項目。
接下來就跑項目喇。
總之,先跑起來再說。
2、項目目錄解析通過 tree指令查看目錄結構後可發現其結構和正常構建的大有不同。
這裡主要關注 shims-tsx.d.ts和 shims-vue.d.ts兩個文件
兩句話概括:
此時我們打開親切的 src/components/HelloWorld.vue,將會發現寫法已大有不同
<template>
<divclass="hello">
<h1>{{ msg }}</h1>
<!-- 省略 -->
</div>
</template>
<scriptlang="ts">
import{ Component, Prop, Vue} from 'vue-property-decorator';
@Component
exportdefaultclassHelloWorld extends Vue{
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<stylescoped></style>
至此,準備開啟新的篇章 TypeScript極速入門 和 vue-property-decorator
3、TypeScript極速入門3.1 基本類型和擴展類型Typescript與 Javascript共享相同的基本類型,但有一些額外的類型。
元組 Tuple
枚舉 enum
Any 與 Void
1、基本類型合集// 數字,二、八、十六進位都支持
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. 元組 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開始
enumColor{Red= 1, Green= 2, Blue= 4}
let c: Color= Color.Green;
let colorName: string= Color[2];
console.log(colorName); // 輸出'Green'因為上面代碼裡它的值是2
另一個很好的例子是使用枚舉來存儲應用程式狀態。
3. Void在 Typescript中,你必須在函數中定義返回類型。像這樣:
若沒有返回值,則會報錯:
我們可以將其返回值定義為 void:
此時將無法 return
4. AnyEmmm...就是什麼類型都行,當你無法確認在處理什麼類型時可以用這個。
但要慎重使用,用多了就失去使用Ts的意義。
let person: any = "前端勸退師"
person = 25
person = true
主要應用場景有:
接入第三方庫
Ts菜逼前期都用
5. Never用很粗淺的話來描述就是:" Never是你永遠得不到的爸爸。"
具體的行為是:
3. 類型斷言簡略的定義是:可以用來手動指定一個值的類型。
有兩種寫法,尖括號和 as:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue asstring).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;
}
}
安全導航操作符 ( ?. )和非空斷言操作符(!.)安全導航操作符 ( ?. ) 和空屬性路徑: 為了解決導航時變量值為null時,頁面運行時出錯的問題。
Thenull hero's name is {{nullHero?.name}}
非空斷言操作符:
能確定變量值一定不為空時使用。
與安全導航操作符不同的是,非空斷言操作符不會防止出現 null 或 undefined。
let s = e!.name; // 斷言e是非空並訪問name屬性
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. 泛型與 AnyTs 的特殊類型 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;
}
3. 泛型類型泛型接口:
interfaceGenerics_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 TypealiasInterface,國內翻譯成接口。
Typealias,類型別名。
以下內容來自:
Typescript 中的 interface 和 type 到底有什麼區別
1. 相同點都可以用來描述一個對象或函數:
interfaceUser{
name: string
age: number
}
type User= {
name: string
age: number
};
interfaceSetUser{
(name: string, age: number): void;
}
type SetUser= (name: string, age: number): void;
都允許拓展(extends):
interface 和 type 都可以拓展,並且兩者並不是相互獨立的,也就是說 interface可以 extendstype, type 也可以 extendsinterface 。 雖然效果差不多,但是兩者語法不同。
interface extends interface
interfaceName{
name: string;
}
interfaceUserextendsName{
age: number;
}
type extends type
type Name= {
name: string;
}
type User= Name& { age: number };
interface extends type
type Name= {
name: string;
}
interfaceUserextendsName{
age: number;
}
type extends interface
interfaceName{
name: string;
}
type User= Name& {
age: number;
}
2. 不同點type 可以而 interface 不行
// 基本類型別名
type Name= string
// 聯合類型
interfaceDog{
wong();
}
interfaceCat{
miao();
}
type Pet= Dog| Cat
// 具體定義數組每個位置的類型
type PetList= [Dog, Pet]
// 當你想獲取一個變量的類型時,使用 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 能夠聲明合併
interfaceUser{
name: string
age: number
}
interfaceUser{
sex: string
}
/*
User 接口為 {
name: string
age: number
sex: string
}
*/
interface 有可選屬性和只讀屬性
接口裡的屬性不全都是必需的。有些是只在某些條件下存在,或者根本不存在。例如給函數傳入的參數對象中只有部分屬性賦值了。帶有可選屬性的接口與普通的接口定義差不多,只是在可選屬性名字定義的後面加一個 ?符號。如下所示
interfacePerson{
name: string;
age?: number;
gender?: number;
}
顧名思義就是這個屬性是不可寫的,對象屬性只能在對象剛剛創建的時候修改其值。你可以在屬性名前用 readonly來指定只讀屬性,如下所示:
interfaceUser{
readonly loginName: string;
password: string;
}
上面的例子說明,當完成User對象的初始化後loginName就不可以修改了。
3.4 實現與繼承: implementsvs extendsextends很明顯就是ES6裡面的類繼承,那麼 implement又是做什麼的呢?它和 extends有什麼不同?
implement,實現。與C#或Java裡接口的基本作用一樣, TypeScript也能夠用它來明確的強制一個類去符合某種契約
implement基本用法:
interfaceIDeveloper{
name: string;
age?: number;
}
// OK
class dev implementsIDeveloper{
name = 'Alex';
age = 20;
}
// OK
class dev2 implementsIDeveloper{
name = 'Alex';
}
// Error
class dev3 implementsIDeveloper{
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
importVue, { VNode} from'vue';
declare global{
namespace JSX {
// tslint:disable no-empty-interface
interfaceElementextendsVNode{}
// tslint:disable no-empty-interface
interfaceElementClassextendsVue{}
interfaceIntrinsicElements{
[elem: string]: any;
}
}
}
// shims-vue.d.ts
declare module'*.vue'{
importVuefrom'vue';
exportdefaultVue;
}
declare:當使用第三方庫時,我們需要引用它的聲明文件,才能獲得對應的代碼補全、接口提示等功能。
這裡列舉出幾個常用的:
declare var聲明全局變量
declare function聲明全局方法
declare class聲明全局類
declare enum聲明全局枚舉類型
declare global擴展全局變量
declare module擴展模塊
namespace:「內部模塊」現在稱做「命名空間」
moduleX{ 相當於現在推薦的寫法 namespaceX{)
跟其他 JS 庫協同類似模塊,同樣也可以通過為其他 JS 庫使用了命名空間的庫創建 .d.ts 文件的聲明文件,如為 D3 JS 庫,可以創建這樣的聲明文件:
declare namespace D3{
exportinterfaceSelectors{ ... }
}
declare var d3: D3.Base;
所以上述兩個文件:
3.6 訪問修飾符: private、 public、 protected其實很好理解:
默認為 public
當成員被標記為 private時,它就不能在聲明它的類的外部訪問,比如:
classAnimal{
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
let a = newAnimal('Cat').name; //錯誤,『name』是私有的
protected和 private類似,但是, protected成員在派生類中可以訪問
classAnimal{
protected name: string;
constructor(theName: string) {
this.name = theName;
}
}
classRhinoextendsAnimal{
constructor() {
super('Rhino');
}
getName() {
console.log(this.name) //此處的name就是Animal類中的name
}
}
4、Vue組件的 Ts寫法從 vue2.5 之後,vue 對 ts 有更好的支持。根據官方文檔,vue 結合 typescript ,有兩種書寫方式
Vue.extend
importVuefrom'vue'
constComponent= Vue.extend({
// type inference enabled
})
vue-class-componentimport{ Component, Vue, Prop} from'vue-property-decorator'
@Component
exportdefaultclassTestextendsVue{
@Prop({ type: Object})
private test: { value: string}
}
理想情況下, Vue.extend 的書寫方式,是學習成本最低的。在現有寫法的基礎上,幾乎 0 成本的遷移。
但是 Vue.extend模式,需要與 mixins 結合使用。在 mixin 中定義的方法,不會被 typescript 識別到
,這就意味著會出現丟失代碼提示、類型檢查、編譯報錯等問題。
菜鳥才做選擇,大佬都挑最好的。直接講第二種吧:
4.1 vue-class-component我們回到 src/components/HelloWorld.vue<template>
<divclass="hello">
<h1>{{ msg }}</h1>
<!-- 省略 -->
</div>
</template>
<scriptlang="ts">
import{ Component, Prop, Vue} from 'vue-property-decorator';
@Component
exportdefaultclassHelloWorld extends Vue{
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<stylescoped></style>
有寫過 python的同學應該會發現似曾相識:
1. 函數修飾符 @「@」,與其說是修飾函數倒不如說是引用、調用它修飾的函數。
或者用句大白話描述: @: "下面的被我包圍了。"
舉個慄子,下面的一段代碼,裡面兩個函數,沒有被調用,也會有輸出結果:
test(f){
console.log("before ...");
f()
console.log("after ...");
}
@test
func(){
console.log("func was called");
}
直接運行,輸出結果:
before ...
func was called
after ...
上面代碼可以看出來:
但是,解釋器讀到函數修飾符「@」的時候,後面步驟會是這樣:
1、去調用 test函數, test函數的入口參數就是那個叫「 func」的函數;
2、test函數被執行,入口參數的(也就是 func函數)會被調用(執行);
換言之,修飾符帶的那個函數的入口參數,就是下面的那個整個的函數。有點兒類似 JavaScript裡面的 functiona(function(){...});
2. vue-property-decorator和 vuex-class提供的裝飾器vue-property-decorator的裝飾器:
vuex-class的裝飾器:
@State
@Getter
@Action
@Mutation
我們拿原始Vue組件模版來看:
import{componentA,componentB} from'@/components';
exportdefault{
components: { componentA, componentB},
props: {
propA: { type: Number},
propB: { default: 'default value'},
propC: { type: [String, Boolean] },
}
// 組件數據
data () {
return{
message: 'Hello'
}
},
// 計算屬性
computed: {
reversedMessage () {
returnthis.message.split('').reverse().join('')
}
// Vuex數據
step() {
returnthis.$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},
})
exportdefaultclassHelloWorldextendsVue{
@Prop(Number) readonly propA!: number | undefined
@Prop({ default: 'default value'}) readonly propB!: string
@Prop([String, Boolean]) readonly propC!: string| boolean| undefined
// 原data
message = 'Hello'
// 計算屬性
privateget reversedMessage (): string[] {
returnthis.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{ }
}
正如你所看到的,我們在生命周期 列表那都添加 privateXXXX方法,因為這不應該公開給其他組件。
而不對 method做私有約束的原因是,可能會用到 @Emit來向父組件傳遞信息。
4.2 添加全局工具引入全局模塊,需要改 main.ts:
importVuefrom'vue';
importAppfrom'./App.vue';
import router from'./router';
import store from'./store';
Vue.config.productionTip = false;
newVue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
npm iVueI18n
importVuefrom'vue';
importAppfrom'./App.vue';
import router from'./router';
import store from'./store';
// 新模塊
import i18n from'./i18n';
Vue.config.productionTip = false;
newVue({
router,
store,
i18n, // 新模塊
render: (h) => h(App),
}).$mount('#app');
但僅僅這樣,還不夠。你需要動 src/vue-shim.d.ts:
// 聲明全局方法
declare module'vue/types/vue'{
interfaceVue{
readonly $i18n: VueI18Next;
$t: TranslationFunction;
}
}
之後使用 this.$i18n()的話就不會報錯了。
4.3 Axios 使用與封裝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) => {
returnPromise.reject(error);
});
exportdefault service;
為了方便,我們還需要定義一套固定的 axios 返回的格式,新建 ajax.ts:
exportinterfaceAjaxResponse{
code: number;
data: any;
message: string;
}
3. main.ts接口調用:// api/main.ts
import request from'../utils/request';
// get
exportfunction getSomeThings(params:any) {
return request({
url: '/api/getSomethings',
});
}
// post
exportfunction postSomeThings(params:any) {
return request({
url: '/api/postSomethings',
methods: 'post',
data: params
});
}
5、編寫一個組件為了減少時間,我們來替換掉 src/components/HelloWorld.vue,做一個博客帖子組件:
<template>
<divclass="blogpost">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
<pclass="meta">Written by {{ post.author }} on {{ date }}</p>
</div>
</template>
<scriptlang="ts">
import{ Component, Prop, Vue} from 'vue-property-decorator';
// 在這裡對數據進行類型約束
export interface Post{
title: string;
body: string;
author: string;
datePosted: Date;
}
@Component
exportdefaultclassHelloWorld extends Vue{
@Prop() private post!: Post;
get date() {
return`${this.post.datePosted.getDate()}/${this.post.datePosted.getMonth()}/${this.post.datePosted.getFullYear()}`;
}
}
</script>
<stylescoped>
h2 {
text-decoration: underline;
}
p.meta {
font-style: italic;
}
</style>
然後在 Home.vue中使用:
<template>
<divclass="home">
<imgalt="Vue logo"src="../assets/logo.png">
<HelloWorldv-for="blogPost in blogPosts" :post="blogPost" :key="blogPost.title"/>
</div>
</template>
<scriptlang="ts">
import{ Component, Vue} from 'vue-property-decorator';
importHelloWorld, { Post} from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Component({
components: {
HelloWorld,
},
})
exportdefaultclassHome extends Vue{
private blogPosts: Post[] = [
{
title: 'My first blogpost ever!',
body: 'Lorem ipsum dolor sit amet.',
author: 'Elke',
datePosted: newDate(2019, 1, 18),
},
{
title: 'Look I am blogging!',
body: 'Hurray for me, this is my second post!',
author: 'Elke',
datePosted: newDate(2019, 1, 19),
},
{
title: 'Another one?!',
body: 'Another one!',
author: 'Elke',
datePosted: newDate(2019, 1, 20),
},
];
}
</script>
這時候運行項目
這就是簡單的父子組件
6、參考文章TypeScript — JavaScript with superpowers — Part II
VUE WITH TYPESCRIPT
TypeScript + 大型項目實戰
Python修飾符 (一)—— 函數修飾符 「@」
Typescript 中的 interface 和 type到底有什麼區別
●編號1089,輸入編號直達本文
●輸入m獲取文章目錄
Web開發