iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0

模組二|選型、語言遷移與地基(Day 6–11)

標題不是我打錯。這是我在新專案的 TypeScript 設定檔裡看到的一行,一字不差:

{
  "compilerOptions": {
    "strict": false, // 啟用嚴格模式
  }
}

註解說「啟用」,值是 false

我第一次看到的時候笑了一下,然後意識到這行程式碼其實記錄了一整段歷史:有人在某個時間點把它從 true 改成 false,而且沒有改註解。

今天就從這一行開始,講我們怎麼導入 TypeScript,以及為什麼把最嚴格的檢查關掉了。

先講這個決定有多刻意

我原本以為這是「忘了開」。但往上追一層就發現不是。

專案的設定檔繼承自一份共用的基礎設定,而那份基礎設定裡寫著:

{
  "strict": true,
  "noImplicitOverride": true,
  "noUnusedLocals": true,
}

框架的預設是嚴格的。是我們在專案層把它覆蓋成 false 的。

這代表有人做過一個決定:明知道基礎設定要求嚴格,還是把它關掉。所以這篇要回答的是「為什麼刻意關掉」,不是「為什麼忘了開」。

先說清楚 strict 是什麼

如果你沒用過 TypeScript:strict 是一個總開關,打開之後編譯器會啟用一整批嚴格檢查。最常見的三個:

  • 不准有「型別不明」的參數:你寫 function f(x),編譯器會說「x 是什麼型別?」
  • 不准把可能是空的東西直接拿來用:user.nameuser 可能是 null 的時候會報錯
  • 函式參數的型別要對得更嚴

這些檢查全都是對的。長期來說一定要有。

但「對的」和「現在就該做」是兩件事。

我們的處境,用校稿來理解

你手上有一份 200 頁的報告,任務是把它從英文翻成中文。

這時候你請了一個超級嚴格的校對來幫你。他很厲害,連標點符號全形半形、括號前後空格都會挑出來。

於是你翻到第 3 頁的時候,桌上已經堆了 4,000 條標點符號的修改建議。

問題來了:在這 4,000 條裡面,哪幾條是「你翻錯意思」?

你找不到。因為它們混在一起了。

這就是我們的處境。遷移期要同時處理兩件事:

  1. 業務邏輯從 Vue 2 搬到 Vue 3(本來就夠難)
  2. 把一份完全沒有型別的程式碼補上型別

如果一開始就打開 strict,你會在畫面壞掉的時候分不清楚:是搬邏輯搬錯了,還是型別沒補完?

而 Day 6 講過同一個原則:在最不穩定的階段,不要增加額外的變因。

那我們實際做了什麼

關掉 strict 不等於放棄 TypeScript。我去數了新專案的檔案型別分布:

副檔名 數量
.ts 401
.vue 396
.tsx 32
.js 8

JavaScript 檔案只剩 8 個。 整個專案實質上已經全面 TypeScript 化了

我們做的是這三件事,它們的共同點是不需要 strict 就能拿到好處:

一、所有檔案都是 TypeScript。 .vue 檔一律用 <script setup lang="ts">。這一步本身就會逼出很多型別,因為編譯器至少會推導。

二、API 回傳有型別定義。 這是投報率最高的一件事,後端回什麼、前端拿到什麼,是所有 bug 的高發區(Day 23 會用數字證明)。

三、API 路徑集中管理。 專案有個慣例:每個 API 檔案先用列舉宣告所有路徑,再包成薄薄的函式:

enum Api {
  GetOrderList = '/order/list',        // 訂單列表
  GetOrderDetail = '/order/detail',    // 訂單詳情
  ExportOrder = '/order/export',       // 匯出
}

export function getOrderList(params: OrderListParams) {
  return defHttp.get<OrderListResult>({ url: Api.GetOrderList, params })
}

好處很實際:路徑字串只出現一次。之後後端改路徑、或你要盤點「哪些 API 沒人用了」(Day 28 會講),都有一個唯一的地方可以查。

這三件事就吃掉了 TypeScript 大約八成的價值,而它們都不需要 strict

因為對日常開發來說,TypeScript 最有感的不是「型別體操」,是IDE 會自動完成、按一下就跳到定義、改名字的時候不會漏。這些東西在 strict: false 底下一樣有。

代價:431 個 any

旁邊就是鋪好的步道,我還是從草地上那條被踩禿的線走過去

講完好處,講代價。我去數了 any 的用量:

grep -rho ': any\|as any\|<any>\|any\[\]' src --include='*.ts' --include='*.vue' | wc -l

431 次,分布在 128 個檔案。

any 是 TypeScript 的逃生門:寫上去等於跟編譯器說「這個你別管」。用太多,型別系統就形同虛設。

431 次是多是少?以一個 800 多個檔案的專案來說,我覺得不算失控,但也絕對不是乾淨。它代表這個專案裡有一批地方,型別檢查是關著的。

而更關鍵的是:這些 any 是關掉 strict 的直接後果。如果 strict 開著,很多地方你不寫型別根本編不過,就會被迫想清楚;關掉之後,「先 any 一下」變成阻力最小的路徑。

有沒有很耳熟?這正是 Day 4 講的同一件事:沒有摩擦的捷徑,一定會被累積

但有一個數字讓我改觀

我這把傘上針孔很多,會漏一點;但真正破掉的只有九處,所以它還撐得住

不過我還數了另一個東西:

grep -rho '@ts-ignore\|@ts-nocheck' src --include='*.ts' --include='*.vue' | wc -l
# → 9

只有 9 個。

這兩者的差別很重要:

  • any 是「這個值我不宣告型別」:範圍限縮在那個變數
  • @ts-ignore 是「下一行的所有錯誤全部忽略」:不管是什麼錯

後者粗暴得多。而全專案只有 9 個,代表大家在遇到型別報錯的時候,多半是去把它處理掉,而不是直接把檢查關掉。

所以我對這個專案的型別健康度的判斷是:寬鬆,但沒有腐爛。 這兩件事不一樣。

如果哪天 @ts-ignore 開始成長,那才是真的要警戒,因為那代表大家已經放棄跟編譯器溝通了。

什麼時候該把 strict 打開

這是我覺得最實用的一段。我們的想法是:不要問「什麼時候全部打開」,要問「哪一塊可以先打開」。

TypeScript 允許你分區域設定。所以合理的順序是:

  1. 先開在新寫的共用套件上,它們沒有歷史包袱,一開始就嚴格是最便宜的
  2. 再開在已經搬遷完成、進入維護期的業務模組上,這時候它已經穩定了,型別報錯多半是真的問題
  3. 最後才是那些還在頻繁改動的地方

反過來做(一次全開,然後花兩週修四千個錯)的問題不是工作量,是你會在同一個 PR 裡混進大量「為了讓它編過」的改動,而那種 PR 沒有人有辦法認真 review。

至於我們有沒有做到?老實說,目前還沒有。 這件事在待辦清單上,但一直沒有排進去,因為它屬於那種「不做也不會怎樣,做了也沒有人看得見」的工作。

這本身就是一課:漸進式導入最大的風險,不是導不完,是「暫時這樣」變成永久。 Day 6 講過同一句話,這裡再次應驗。

為什麼型別和 Vue 3 遷移適合一起做

最後補一個當時沒想到、後來才理解的關聯。

Day 7 講過 Options API 的問題:this 是一個集散地,什麼都掛在上面。

而那種結構天生難標型別:this.someThing 從哪來?是 data、是 mixin 注入的、還是外掛掛上去的?編譯器也不知道,所以它推導不出型別。

Composition API 剛好相反:

const { initFormBase } = useFormBase()

這一行的型別是明確的,因為依賴被寫出來了。

換句話說:Composition API 讓依賴顯性化,而顯性的依賴才標得動型別。 這兩件事是互相加成的,所以一起做比分開做划算。

如果你正在考慮「先升 Vue 3 還是先導 TypeScript」,我的答案是:一起做,但型別的嚴格度先放寬。

代價

一、any 會累積,而且不會自己消失。 431 個裡面,我猜大部分永遠不會被回頭處理。要有心理準備:你是在用未來的整理成本,換現在的遷移速度。

二、「之後再開 strict」很可能不會發生。 我們就是活生生的例子。如果你真的想做,要在計畫裡給它一個明確的時間點和負責人,否則它只是一句好聽話。

三、註解會說謊。 那行 "strict": false, // 啟用嚴格模式 就是證據。設定改了、註解沒改,而下一個看到的人會困惑三分鐘。這種小事累積起來,就是「這個專案的文件不能信」的開始。

帶走什麼

  1. 型別的價值是「改動時的安全網」,不是「完美的型別體操」。 在遷移期,能自動完成、能跳定義、能安全改名,就已經賺到八成了。
  2. 關掉 strict 不等於放棄 TypeScript。 檢查 .js 檔案還剩幾個,比檢查 strict 是不是 true 更能反映真實的導入程度。
  3. any 的數量,也要看 @ts-ignore 的數量。 前者是寬鬆,後者是放棄,後者開始成長才是真的警訊。
  4. Vue 3 遷移和 TypeScript 導入適合一起做,因為顯性的依賴才標得動型別。
  5. 「之後再收緊」要有時間點和負責人,否則它跟沒說一樣。

明天 Day 11 是模組二的最後一天,講建置:舊系統的建置指令為什麼要手動指定 6GB 記憶體、那些沒人敢刪的 webpack 設定,以及換成 Vite 之後真正改變的東西,不是 build 時間,是別的。


上一篇
Day 9|換掉 UI 元件庫:8,023 個標籤,和一個我沒預料到的結果
下一篇
Day 11|建置設定裡那個沒人敢關的開關
系列文
一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言