跳至主要内容

[ Day-002 ] TypeScript 型別與如何型別註記方式

Primitive

JavaScript 當中的原始型別都有對應到一個相對的 TypeScript Type

Explicit type annotation

string

// javascript : typeof "guychienll" === 'string'
const name: string = 'guychienll';

number

// javascript : typeof 18 === 'number'
const age: number = 18;

boolean

// javascript : typeof false === 'boolean'
const isMarried: boolean = false;
信息

在撰寫 TypeScript 時,並不需要如上面這麼囉唆
因為 TypeScript 大多時候只要 compiler 可以辨別
就會自動隱含的判定型別 ( Type Inference ) 所以可以寫成下面這樣

Implicit Type Inference

string

// name 將會自動被判定成 string
const name: string = 'guychienll';

number

// age 將會自動被判定成 number
const age = 18;

boolean

// isMarried 將會自動被判定成 boolean
const isMarried = false;

Array

陣列型別宣告方式有兩種
如果是要定義一個全部成員都是字串的陣列
可以用 string[] / Array<string> 來做表示
請注意 [string] 並非符合上述定義而是在描述 Tuples 將在後續文章中提到

const fruits: string[] = ['apple', 'orange', 'banana'];

const animals: Array<string> = ['dog', 'cat', 'rabbit'];

Any

如字面上意義就是任何型別
TypeScript 將會跳過該變數的型別檢查
下面的這些狀況皆不會拋出 compile error

Any Type
let obj: any = { x: 0 };

obj.foo();
obj();
obj.bar = 100;
obj = 'hello';
const n: number = obj;
信息

noImplicitAny

當你沒有明確的定義一個型別
並且同時 TypeScript Compiler 無法從內容隱含推論出確切的型別
此時 TypeScript 就會認定是 Any 型別
而上面這個位於 tsconfig.json 當中的設置就是告知 TSC ( TypeScript Compiler )
如果是 true 時當隱含推論是 Any 時請用 compile error 告知我們
而如果設置 false 將會忽視這件事情

warning

你可能會發現剛開始設置的 tsconfig.json
明明沒有設置 noImplicitAny 卻也拋出錯誤
原因是因為預設的 tsconfig.json 當中
通常都會將 strict 開起來
意思是指開啟下面 Type Checking 列表當中所有的型別檢查

./tsconfig.json
{
/* Type Checking */
"strict": true
// "noImplicitAny": true,
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitThis": true,
// "useUnknownInCatchVariables": true,
// "alwaysStrict": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "exactOptionalPropertyTypes": true,
// "noImplicitReturns": true,
// "noFallthroughCasesInSwitch": true,
// "noUncheckedIndexedAccess": true,
// "noImplicitOverride": true,
// "noPropertyAccessFromIndexSignature": true,
// "allowUnusedLabels": true,
// "allowUnreachableCode": true,
}

Functions

Function Declaration

定義函式參數型別
可以於參數後加上 type annotation
達到定義參數型別

function say(sentence: string) {
console.log(sentence);
}

定義函式回傳型別
可以於 function declaration () {} 之間加上 type annotation
達到定義函式回傳型別

function say(sentence: string): string {
return sentence;
}

定義整個簽章
可以於變數後加上整個函式的參數與回傳型別 type annotation
達到定義匿名函式的傳入參數與回傳值型別

const say: (sentence: string) => string = function (sentence) {
return sentence;
};

抽出來成 type alias 可讀性會好一點

type sayFn = (sentence: string) => string;

const say: sayFn = function (sentence) {
return sentence;
};

Arrow Function

定義函式參數型別
可以於參數後加上 type annotation
達到定義參數型別

const say = (sentence: string) => {
console.log(sentence);
};

定義函式回傳型別
可以於 arrow function () => 之間加上 type annotation
達到定義函式回傳型別

const say = (sentence: string): string => {
console.log(sentence);
};

定義整個簽章
可以於變數後加上整個函式的參數與回傳型別 type annotation
達到定義匿名函式的傳入參數與回傳值型別

const say: (sentence: string) => string = (sentence) => sentence;

抽出來成 type alias 可讀性會好一點

type sayFn = (sentence: string) => string;

const say: sayFn = (sentence) => sentence;

Type Inference

在函式當中就算沒有定義回傳值型別
在 TypeScript 幫助下也可以自動由 return keyword
來辨別會傳值的型別 下面這段 code 雖然沒有定義回傳值但 TypeScript 卻能夠推斷出型別

const say = function (name: string) {
return `hello ${name}!`;
};

const result = say('guychienll');

其中 result 的值被型別推斷成

const result: string;

當 TypeScript 有上下文的程式碼可以參考時
也可以隱含的推論出匿名函式的參數型別
其中由於 forEach 當中是 iterate string[]
因此 s 參數將會被隱含推論成 string

const names = ['Alice', 'Bob', 'Eve'];

names.forEach(function (s) {
console.log(s.toUpperCase());
});

names.forEach((s) => {
console.log(s.toUpperCase());
});

Arguments Counts Checking

在沒有定義函式參數型別的情況下
TypeScript 還是會檢查傳入參數的數量是否跟介面一致

function say(name) {
console.log(`hello ${name}!`);
}

say(); // Expected 1 arguments, but got 0.ts (2554)

Object

可以緊接著 object 後續加上 type annotation
{} 就像模型一樣定義出整個 object 的型別

function say(props: { name: string; age: number }) {
const { name, age } = props;
console.log(`hello, I'm ${name}, ${age} years old !`);
}

可以加上在 key 後加上 ? 表示可選 Optional
然而加上 ? 代表在 call say 這個函式時
就可以不用必須帶入 name
同時若你沒有做 type error 的 check 時
TypeScript 會用 compile error 提醒你
name 在此處 invoke toLocaleLowerCase 有可能會噴 exception

function say(props: { name?: string; age: number }) {
const { name, age } = props;
console.log(`hello, I'm ${name.toLocaleLowerCase()}, ${age} years old!`); // 'name' is possibly 'undefined'. ts(18048)
}

因此就可以做一些預防性的處置

function say(props: { name?: string; age: number }) {
const { name, age } = props;
if (name) {
console.log(
`hello, I'm ${name.toLocaleLowerCase()}, ${age} years old!`
);
} else {
console.log(`hello, I'm ${age} years old!`);
}
}

或是

function say(props: { name?: string; age: number }) {
const { name, age } = props;
console.log(
`hello, I'm ${
name?.toLocaleLowerCase() || 'default'
}, ${age} years old!`
);
}

Union Types

可以使用 pipe | 將型別與型別做聯集
即 A 或 B

function printId(id: number | string) {
console.log('Your ID is: ' + id);
}
// OK
printId(101);
// OK
printId('202');
//Error
printId({ myID: 22342 });

在做聯集後有可能遇到下面狀況

function printId(id: number | string) {
console.log(id.toUpperCase()); //Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'. ts(2339)
}

這時可以透過 Narrowing 解決這個問題
原因是因為此時 id 將有可能是 number 或是 string
但是 toUpperCase 只有 string 才有這項成員
透過 typeof id === 'string' 就可以限縮型別的使用範圍
如下面這段程式碼

function printId(id: number | string) {
if (typeof id === 'string') {
// 在這 scope 當中 id 只會是型別 string
console.log(id.toUpperCase());
} else {
// 在這 scope 當中 id 只會是型別 number
console.log(id);
}
}

參考連結