iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0

有時候 TS 不需要你明確寫型別,它自己就能猜出來。 這就是型別推論(Type Inference)。 昨天我們聊了 Enum,今天我們來看看 TS 的「神之直覺」—— 型別推論。 簡單來說,就是你不寫型別,TS 也能猜出來你要幹嘛(大部分時候很準)。

1. 基本型別推論

TypeScript 會在變數宣告的那一刻,就根據賦值內容自動決定型別。

let age = 18;       // 推論成 number
let name = "Marco"; // 推論成 string
let isActive = true;// 推論成 boolean

這時候就算你沒寫 : number,TS 也不會讓你隨便改型別:

age = "十八";
// ❌ Type 'string' is not assignable to type 'number'


2. 陣列推論

TS 也能看陣列的內容來推論型別:

let scores = [100, 90, 80];
// 推論成 number[]

如果混合型別:

let mixed = [1, "two", 3];
// 推論成 (string | number)[]


3. 函式回傳值推論

函式沒寫回傳型別時,TS 會自動根據 return 推論:

function add(a: number, b: number) {
  return a + b; // 推論回傳 number
}

沒有 return 的話,就會推論成 void

function logMessage(msg: string) {
  console.log(msg); // 推論回傳 void
}


4. 字面量型別的推論

TS 會自動把值推論成比較「寬」的型別:

let direction = "up";
// 推論成 string(任何字串都能代替)

const strictDirection = "up";
// 推論成字面量 "up"(只能是這個值)

想要「鎖死值」:

  • const
  • as const
let colors = ["red", "green", "blue"] as const;
// 推論成 readonly ["red", "green", "blue"]


5. 解構賦值的推論

在物件解構時,TS 也會自動幫你帶上型別:

const user = { name: "Alice", age: 20 };

const { name, age } = user;
// name -> string
// age -> number

陣列解構也可以:

const arr = [1, "hi"] as const;

const [id, greeting] = arr;
// id -> 1
// greeting -> "hi"


6. 物件方法與 this 的推論

TS 甚至能推論 this

const counter = {
  value: 0,
  increment() {
    this.value++;
  }
};

在這裡,this 自動被推論為:

{ value: number; increment: () => void; }


7. 常見推論的坑

(1) 空陣列

let items = [];
// 推論成 any[](什麼都能放,非常危險)

items.push(123);
items.push("abc"); // 也 OK

解法:

明確標註型別

let items: number[] = [];


(2) null / undefined

function getUser() {
  if (Math.random() > 0.5) {
    return { name: "Alice" };
  }
  return null;
}
// 推論成 { name: string } | null

這種情況就要處理 null,不然會報錯:

const user = getUser();
if (user) {
  console.log(user.name);
}


(3) 回傳值過度寬鬆

function getValue(flag: boolean) {
  return flag ? 1 : "one";
}
// 推論成 number | string

這在程式裡會傳染,讓型別變得很麻煩。

建議:用明確的回傳型別


8. 額外應用:上下文型別 (Contextual Typing)

有時候 TS 不只是看值本身,還會「反推」你使用的情境。

window.addEventListener("click", (event) => {
  console.log(event.clientX);
  // event 自動推論成 MouseEvent
});

這叫做 上下文型別 (Contextual Typing),讓我們不用再寫一堆型別。


9. 最佳實務建議

  • 變數:可以放心交給推論,減少重複
  • 函式參數與回傳:最好明確標註(增加可讀性)
  • 公開 API / Export:一定要寫型別(文件 + 防呆)
  • 初始化為空值:記得加型別,避免變成 any

10. 心法總結

型別推論就像 TS 的「第六感」:

  • 幫你減少 boilerplate
  • 避免寫太多型別註記
  • 但也可能「太聰明反被聰明誤」

👉 最安全的做法:

能省則省,但關鍵地方要明寫。

🔍 11. 實務案例:手動標註 vs 型別推論

全手動標註(冗長)

type User = {
  id: number;
  name: string;
  isActive: boolean;
};

function createUser(id: number, name: string, isActive: boolean): User {
  return { id: id, name: name, isActive: isActive };
}

const user: User = createUser(1, "Alice", true);

這樣寫很嚴謹,但型別幾乎重複寫了兩次,顯得很囉嗦。


善用型別推論(簡潔)

type User = {
  id: number;
  name: string;
  isActive: boolean;
};

function createUser(id: number, name: string, isActive: boolean) {
  return { id, name, isActive };
  // 🔑 這裡 TS 會自動推論回傳型別為 User
}

const user = createUser(1, "Alice", true);
// user -> { id: number; name: string; isActive: boolean }

這樣寫:

  • 省掉重複標註
  • 型別推論依然正確
  • 程式碼更乾淨可讀

適度取捨

不過如果這個 createUserAPI 對外匯出的函式,建議還是加上明確回傳型別,避免未來改動時「默默壞掉」:

function createUser(id: number, name: string, isActive: boolean): User {
  return { id, name, isActive };
}

👉 內部工具函式:可以放心用推論

👉 對外公開 API:最好明寫型別


上一篇
Day 6|Enum 列舉:常數界的管理員
下一篇
Day 8|型別也能混搭?Union & Intersection 全攻
系列文
我與型別的 30 天約定:TypeScript 入坑實錄12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言