寫在最前面
- 剛開始寫 typescript 遇到的問題和簡單的解決方案。
Q&A
1、是否所有變數都需要做型別註解?
-
這個分情況,原則上來說,我們希望能對所有的值都做型別註解。
-
對於TS編譯器來說,如果宣告變數時沒有做型別註解,那麼TS會根據賦值自動推匯出變數型別。這一點大多數情況下很完美,很方便,但是有一些列外:
-
後面賦值不同型別的值
當你後面需要重新對該變數賦值其他型別時,那麼TS會給出錯誤,因為與TS初始推匯出的型別不一致了。
let a = 1;
a= `string`; // error!
let obj = {
name: `John`
}
obj.age = 20; // error!
obj.name = 250; // error!
// better
let a: string | number = 1;
a = `string`; // ok
let obj: {
name: string | number;
age?: number;
} = {
name: `John`
}
obj.age = 20; // ok
obj.name = 250; // ok
複製程式碼
初始賦值時不是明確的值
即該值當前TS並不知道其型別,比如來自於後端介面返回的值、其他為明確宣告型別的函式返回等。即沒有初始化或者TS無法根據初始化值推匯出型別,則會預設為any型別。
2、在對介面的時候我們定義介面的引數值
- 其實這種情況下不會報錯,但是這樣子會丟失型別檢查和程式碼提示功能。所以這種情況,最好可以新增型別宣告和註解。
// normal
http.get(`/api`)
.then(resp => {
let data = resp; // data: any
});
// good one
interface IResponse {
code: number;
data: IUser;
}
http.get(`/api`)
.then(resp => {
let data: IResponse = resp; // data: any
});
複製程式碼
如果是上面兩種情況,則需要提前定義好型別,並新增型別註解。否則,我們對於是否新增型別註解,鼓勵,但不強求。因為大多數情況,我們在初始化賦值時TS就能很好的幫助我們自動確認好型別,並且通過
typeof
也可以獲取該值的型別。一舉兩得!
3、巧用 type
定義型別
// good one
let user = {
name: `Lucy`,
age: 20
}
type User = typeof user;
// deprecate
type User = {
name: string;
age: number;
};
let user: User = {
name: `Lucy`,
age: 20`
}
複製程式碼
如上第一種寫法,我們在宣告user變數時,即得到了值,又獲得了型別!反之,第二種寫法就有點囉嗦了。
4、使用TS改寫當前程式碼遇到各種錯誤問題?
-
物件屬性不存在錯誤::
這種情況一般在於,該物件值TS知道其有明確型別(不是any,如果是any就不會報錯了),但是當前要訪問的屬性不存在與其已知型別結構。這種情況分兩種辦法解決:- 如果能修改該值的型別宣告,那麼
新增上缺損值的屬性即可
; - 否則,使用
// @ts-ignore
註釋,或者使用型別斷言,強制為 any 型別:(this.props as any).notExists
- 如果能修改該值的型別宣告,那麼
-
型別不明確的錯誤:
即一個值的型別可能被註解為聯合型別,那麼在直接訪問時,TS無法確定當前值到底屬於哪個精確的型別,所以會報告錯誤。這種情況有以下解決拌飯:- 使用型別保護(type guards)
- 使用型別斷言
- 使用 // @ts-ignore 註釋
應該優先考慮型別保護,因為型別保護本質上就是增加程式碼邏輯,幫助TS理解確定當前型別,所以程式碼也會更健壯。而後兩種辦法,除非明確知道此時該值就是確定的型別,否則即使通過了TS編譯器,在程式碼執行階段,依然有可能出錯!
-
值可能不存在的或為undefined的錯誤:
-
這種情況其實是上面提到的型別不明確錯誤的一種,一般發生在可選屬性或者可選引數時。解決這些情況,最簡單的就是使用非空型別斷言(前提是確認該值確實是非空):
-
非空型別斷言的形式是在值後面新增半形感嘆號:
-
someVar!.toString();
複製程式碼