當前位置: 妍妍網 > 碼農

TypeScript 5.4 正式釋出

2024-03-10碼農

3 月 6 日,TypeScript 釋出了 v5.4 版本,該版本帶來了以下更新:

  • 型別縮小會在閉包中保留

  • 引入新的實用程式型別 NoInfer<T>

  • 新增 Object.groupBy Map.groupBy

  • 新的模組解析選項

  • 新的模組匯入檢查機制

  • TypeScript 5.5 即將棄用的功能

  • 型別縮小會在閉包中保留

    TypeScript 透過型別縮小來最佳化程式碼,但在閉包中並不總是保留這些縮小後的型別。從TypeScript 5.4開始,當在非提升函式中使用參數或 let 變量時,型別檢查器會尋找最後的賦值點,從而智慧地進行型別縮小。然而,如果變量在巢狀函式中被重新分配,即使這種分配不影響其型別,也會使閉包中的型別細化無效。

    // TypeScript的型別縮小在閉包中通常不保留
    functionexampleFunction(input: string | number){
    if(typeof input ==="string"){
    input =parseInt(input);// 假設想要將字串轉為數位
    }
    return()=>{
    // 在這裏,TypeScript不知道input是string還是number
    // 因為在閉包建立後,input可能已經被修改
    console.log(input.toString());// 錯誤!'input'可能是number,沒有toString方法
    };
    }

    TypeScript 5.4後,當在閉包外部對變量進行最後一次賦值時,型別縮小會在閉包中保留:

    functionimprovedFunction(input: string | number){
    let value;
    if(typeof input ==="string"){
    value =parseInt(input);
    }else{
    value = input;
    }
    return()=>{
    // 在這裏,TypeScript知道value是number,因為這是在閉包建立後的最後一次賦值
    console.log(value.toString());// 正確!因為現在我們知道value是number
    };
    }

    引入新的實用程式型別 NoInfer

    TypeScript的泛型函式能夠根據傳入的參數自動推斷型別。但在某些情況下,這種自動推斷可能不符合預期,導致不合法的函式呼叫被接受,而合法的呼叫卻被拒絕。為了處理這種情況,開發者通常需要添加額外的型別參數來約束函式的行為,確保型別安全。但這種做法可能會使程式碼看起來更加復雜,特別是當這些額外的型別參數在函式簽名中只使用一次時。

    TypeScript 5.4 引入了 NoInfer<T> 實用型別,允許開發者明確告訴編譯器哪些型別不應該被自動推斷。這避免了不合法的函式呼叫被接受,增強了程式碼的型別安全性。

    考慮以下函式,它接受一個使用者ID列表和一個可選的預設使用者ID。

    functionselectUser<Uextendsstring>(userIds:U[], defaultUserId?:U){
    // ...
    }
    const userIds =["123","456","789"];
    selectUser(userIds,"000");// 錯誤地被接受,因為"000"不在userIds中

    在這個例子中,即使"000"不在userIds陣列中,selectUser函式的呼叫也會被接受,因為TypeScript自動推斷預設使用者ID可以是任何字串。

    TypeScript 5.4 中:

    functionselectUser<Uextendsstring>(userIds:U[], defaultUserId?: NoInfer<U>){
    // ...
    }
    const userIds =["123","456","789"];
    selectUser(userIds,"000");// 正確的錯誤,因為"000"不在userIds中

    透過使用 NoInfer<T> 告訴 TypeScript 不要推斷預設使用者ID的型別,從而確保只有當預設使用者ID在 userIds 陣列中時才接受呼叫。這增強了程式碼的型別安全性,避免了潛在的錯誤。

    新增 Object.groupBy 和 Map.groupBy

    TypeScript 5.4 引入了兩個新方法: Object.groupBy Map.groupBy ,它們用於根據特定條件將陣列元素分組。

  • Object.groupBy 返回一個物件,其中每個鍵代表一個分組,對應的值是該分組的元素陣列。

  • Map.groupBy 返回一個`` Map 物件,實作了相同的功能,但允許使用任何型別的鍵。

  • 使用 Object.groupBy Map.groupBy 可以方便地根據自訂邏輯對陣列進行分組,無需手動建立和填充物件或 Map。然而,在使用 Object.groupBy 時,由於物件的內容名必須是有效的識別元,因此可能無法覆蓋所有情況。此外,這些方法目前僅在 esnext 目標或特定庫設定下可用。

    假設有一個學生陣列,每個學生都有姓名和成績。我們想要根據成績將學生分為「優秀」和「及格」兩組。

    const students:{ name:string, score:number}[]=[
    { name:"Alice", score:90},
    { name:"Bob", score:75},
    { name:"Charlie", score:85},
    // ...其他學生
    ];
    const groupedStudents:{ excellent:any[], passing:any[]}={
    excellent:[],
    passing:[]
    };
    for(const student of students){
    if(student.score >=80){
    groupedStudents.excellent.push(student);
    }else{
    groupedStudents.passing.push(student);
    }
    }

    使用 Array.prototype.groupBy 方法,可以更簡潔地實作相同的功能。

    const students:{ name:string, score:number}[]=[
    { name:"Alice", score:90},
    { name:"Bob", score:75},
    { name:"Charlie", score:85},
    // ...其他學生
    ];
    const groupedStudents = students.groupBy(student =>{
    return student.score >=80?"excellent":"passing";
    });
    // 使用時可以直接存取分組
    console.log(groupedStudents.get("excellent"));// 輸出優秀學生陣列
    console.log(groupedStudents.get("passing"));// 輸出及格學生陣列

    在這個例子中,groupBy 方法根據每個學生的成績將學生陣列分為「優秀」和「及格」兩組,並返回一個 Map 物件,其中鍵是分組名稱,值是對應的學生陣列。這種方法更加簡潔且易於理解。

    新的模組解析選項

    TypeScript 5.4 引入了一個新的模組解析選項 bundler,它模擬了現代構建工具(如Webpack、Vite 等)確定匯入路徑的方式。當與 --module esnext 配合使用時,它允許開發者使用標準的 ECMAScript 匯入語法,但禁止了 import ... = require(...) 這種混合語法。

    同時,TypeScript 5.4 還增加了一個名為 preserve 的模組選項,該選項允許開發者在 TypeScript 中使用 require() ,並更準確地模擬了構建工具和其他執行時環境的模組尋找行為。當設定 module preserve 時,構建工具會隱式地成為預設的模組解析策略,同時啟用 esModuleInterop resolveJsonModule

    假設有一個使用 TypeScript 編寫的計畫,並且想從一個名為 my-lib 的庫中匯入兩個模組 moduleA 和 moduleB。這個庫提供了 ES 模組和 CommonJS 模組兩種格式。在 TypeScript 配置中,你可能這樣設定:

    // tsconfig.json
    {
    "compilerOptions":{
    "module":"commonjs",
    "moduleResolution":"node"
    }
    }

    然後程式碼中這樣匯入:

    import*as moduleA from'my-lib/moduleA';
    import*as moduleB =require('my-lib/moduleB');

    在這種情況下,TypeScript 可能會為兩個匯入生成相同的路徑,因為它們都使用了 Node.js 的模組解析策略。

    在 TypeScript 5.4 中,如果想更精確地控制匯入的路徑,特別是當庫提供了基於匯入語法的不同實作時,可以使用 preserve 模組選項和構建工具模組解析策略:

    // tsconfig.json
    {
    "compilerOptions":{
    "module":"preserve",
    // 隱式設定:
    // "moduleResolution": "bundler",
    // "esModuleInterop": true,
    // "resolveJsonModule": true
    }
    }

    然後,可以這樣編寫程式碼:

    import*as moduleA from'my-lib/moduleA';// 使用 ES 模組匯入
    const moduleB =require('my-lib/moduleB');// 使用 CommonJS 模組匯入

    現在,TypeScript 會根據 my-lib 的 package.json 檔中的 exports 欄位來決定使用哪個檔路徑。如果庫為 ES 模組和 CommonJS 模組提供了不同的檔,TypeScript 將根據匯入的語法( import require )選擇正確的檔。

    這意味著開發者可以更精細地控制模組匯入的行為,確保與庫的意圖一致,尤其是在處理那些提供條件匯出的庫時。

    新的模組匯入檢查機制

    TypeScript 5.4 引入了新的模組匯入檢查機制,確保匯入的內容與全域定義的 ImportAttributes 介面相匹配。這種檢查提高了程式碼的準確性,因為任何不符合該介面的匯入內容都會導致編譯錯誤。

    在早期的 TypeScript 版本中,開發者可以自由地為 import 語句指定任何匯入內容,而不會有嚴格的型別檢查。這可能導致執行時錯誤,因為匯入的內容可能與實際的模組不匹配。

    // 假設存在一個全域的模組定義,但沒有明確的匯入內容型別
    import*as myModule from'my-module'with{ custom:'value'};

    在上述程式碼中, custom 內容是自由定義的,沒有與任何全域介面或型別進行匹配,這增加了出錯的風險。

    在 TypeScript 5.4 及以後的版本中,開發者必須確保匯入內容與全域定義的 ImportAttributes 介面相符。這確保了型別安全,並減少了潛在的執行時錯誤。

    // 全域定義的匯入內容介面
    interfaceImportAttributes{
    validProperty:string;
    }
    // 在模組中匯入時,必須使用符合 ImportAttributes 介面的內容
    import*as myModule from'my-module'with{ validProperty:'someValue'};
    // 下面的匯入將引發錯誤,因為內容名稱不匹配
    import*as myModule from'my-module'with{ invalidProperty:'someValue'};
    // 錯誤:內容 'invalidProperty' 不存在於型別 'ImportAttributes' 中

    在這個新版本中,如果開發者嘗試使用不符合 ImportAttributes 介面的匯入內容,TypeScript 編譯器將丟擲錯誤,從而避免了潛在的錯誤。

    TypeScript 5.5 即將棄用的功能

    TypeScript 5.0 已經廢棄了以下選項和行為:

  • charset

  • target: ES3

  • importsNotUsedAsValues

  • noImplicitUseStrict

  • noStrictGenericChecks

  • keyofStringsOnly

  • suppressExcessPropertyErrors

  • suppressImplicitAnyIndexErrors

  • out

  • preserveValueImports

  • 在計畫參照中的 prepend

  • 隱式OS特定的 newLine

  • 為了在 TypeScript 5.0 及更高版本中繼續使用這些已廢棄的選項和行為,開發人員必須指定一個新的選項 ignoreDeprecations ,並將其值設定為 "5.0"。

    註意,TypeScript 5.4 將是這些已廢棄選項和行為按預期運作的最後一個版本。在預計於 2024 年 6 月釋出的 TypeScript 5.5 中,這些選項和行為將變成嚴格的錯誤,使用它們的程式碼將需要進行遷移以避免編譯錯誤。因此,建議開發人員盡早遷移其程式碼庫,以避免未來相容性問題。

    往期推薦