網站搜尋

型別斷言 - 在 TypeScript 中「as const」是什麼意思,它的使用情境是什麼?


我對 as const 轉型感到困惑。我查閱了一些文件和影片,但仍然不完全理解它。

我關心的是,在下面的程式碼中,as const 是什麼意思,以及使用它的好處是什麼?

const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);

答案#1

這被稱為 const 斷言。const 斷言告訴編譯器為表達式推斷最狹窄的*或最具體的類型。如果不使用它,編譯器將使用其預設的類型推斷行為,這可能會導致更寬泛的或更一般的類型。

請注意,它被稱為「斷言」而不是「強制轉換」。在 TypeScript 中,一般應避免使用「強制轉換」這個術語;當人們說「強制轉換」時,他們通常暗示某種在運行時可觀察到的效果,但 TypeScript 的類型系統,包括類型斷言和 const 斷言,會在生成的 JavaScript 中完全被移除。因此,在運行時,使用 as const 的程式與不使用的程式之間絕對沒有任何差異。


不過,在編譯時,會有明顯的差異。讓我們看看在上述範例中省略 as const 會發生什麼:

const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);

編譯器看到 const args=[8, 5]; 並推斷出類型為 number[]。這是一個可變動的陣列,其元素類型為 number,數量可以是零個或多個。編譯器無法知道有 多少 個元素或是 哪些 元素。這種推斷通常是合理的;因為陣列內容通常是預期會被修改的。如果有人想寫 args.push(17) 或 args[0]++,他們會對 number[] 這個類型感到滿意。

不幸的是,下一行 Math.atan2(...args) 會導致錯誤。Math.atan2() 函數需要恰好兩個數字參數。但編譯器對 args 所知的只是它是一個數字陣列。它已經完全忘記陣列中有兩個元素,所以編譯器會抱怨你在呼叫 Math.atan2() 時使用了“零個或多個”參數,而它需要恰好兩個。


將其與使用 as const 的代碼進行比較:

const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);

現在編譯器推斷 args 的類型為 readonly [8, 5]……一個 readonly 元組,其值正好是按順序排列的數字 8 和 5。具體來說,編譯器已知 args.length 的值正好是 2。

這已經足夠讓下一行使用 Math.atan2() 正常運作。編譯器知道 Math.atan2(...args) 與 Math.atan2(8, 5) 是相同的,而這是一個有效的呼叫。


再一次:在運行時,根本沒有任何區別。兩個版本都會將 1.0121970114513341 記錄到控制台。但 const 斷言,就像其他靜態類型系統一樣,並不是用來在運行時產生效果的。相反,它們讓編譯器更了解程式碼的 意圖,並且能更準確地區分正確的程式碼和錯誤。

程式碼的 Playground 連結


* 對於陣列和元組類型來說,這並不完全正確;技術上來說,readonly 陣列或元組比可變版本 更寬。可變陣列被認為是 readonly 陣列的子類型;前者不被認為擁有如 push() 這樣的變異方法,而後者則有。

答案#2

簡而言之,它允許你創建完全唯讀的物件,這被稱為 const 斷言,在你的程式碼中 as const 意味著陣列位置的值是 唯讀,以下是一個它如何運作的範例:

const args = [8, 5] as const;
args[0] = 3;  // throws "Cannot assign to '0' because it is a read-only   
args.push(3); // throws "Property 'push' does not exist on type 'readonly [8, 5]'" 

你可以在最後拋出的錯誤中看到,args=[8, 5] as const 被解釋為 args: readonly [8, 5],那是因為第一個聲明等同於一個只讀的元組。

元組有很多使用案例,一些例子包括:展開參數、解構值等等。一個一般性的好處是對其所有物件屬性新增的 readonly 行為:

const args = [8, 5];

// Without `as const` assert; `args` stills a constant, but you can modify its attributes
args[0] = 3;  // -- WORKS ✓
args.push(3); // -- WORKS ✓

// You are only prevented from assigning values directly to your variable
args = 7;     // -- THROWS ERROR ✖

對於斷言是“完全只讀”的,有一些例外,你可以在這裡查看。欲了解更多細節,這裡有一個其他相關問題/答案的列表,幫助我理解 const 斷言:

  1. 我如何在 TypeScript 中宣告一個唯讀陣列元組?
  2. 在 TypeScript 中,型別斷言和較新的 as 運算子有什麼差別嗎?
  3. Typescript 只讀元組

答案#3

如果你寫下 const args=[8, 5],並沒有任何東西阻止你接著寫 args[0]=23 或 args.push(30) 或其他任何修改該陣列的操作。你所做的只是告訴 TS/JS,名為 args 的變數指向那個特定的陣列,因此你不能改變它所參考的內容(例如,你不能做 args="something else")。你可以修改陣列,但不能改變其變數所指向的對象。

另一方面,現在在宣告中加入 as const 確實會讓它變成常數。整個東西都是唯讀的,所以你完全無法修改這個陣列。


為了澄清,如評論中所指出的:

「really makes it constant」可能暗示在運行時有某些效果,但實際上並沒有。在運行時,args.push(30) 仍然會修改陣列。All as const 唯一做的是讓 TypeScript 編譯器在看到你這麼做時發出警告。- jcalz

as const 只影響編譯器,而且它的唯讀效果有一個例外(請參見註解)。但一般來說,這仍然是 const 和 as const 之間主要的使用差異。一個用來使 參考 變為不可變,另一個用來使 被參考的內容 變為不可變。

答案#4

當 as const 應用到物件或陣列時,它會使它們不可變(即將它們設為唯讀)。對於其他字面量,它會防止類型擴寬。

const args = [8, 5] as const;

args[0] = 10; ❌ Cannot assign to '0' because it is a read-only property.

其他一些優點 :

  • 如果你不轉型為不同的類型,你可以在編譯時抓到錯誤,而不必執行程式。
  • 編譯器不允許你重新指定嵌套物件的屬性。

答案#5

那是一個 const 斷言。這裡有一篇關於它們的方便文章,這裡是相關的文件。

當我們用 const 斷言構建新的字面量表達式時,我們可以向語言表示

  • 該表達式中的字面量類型不應被擴寬(例如,不應從「hello」轉為 string)
  • 物件字面值取得唯讀屬性
  • 陣列字面值變為唯讀元組

使用 const args=[8, 5] as const;,第三個要點適用,tsc 將理解它的意思為:

// Type: readonly [8, 5]
const args = [8, 5] as const;

// Ok
args[0];
args[1];

// Error: Tuple type 'readonly [8, 5]' of length '2' has no element at index '2'.
args[2];

沒有斷言:

// Type: number[]
const args = [8, 5];

// Ok
args[0];
args[1];

// Also Ok.
args[2];