コンストアサーションの力


const assertions TypeScript 3.4(2019年3月)で導入されたので、彼らは全く新しいものではありません、しかし、私は多くの開発者がまだその特徴を知らないのを見ました.
多分、それは時々少し奇妙になる構文ですconst something = ... as const ). それは、人々がそれらを使用することを恐れさせるタイプキャストへの類似であるかもしれません.または、おそらく、あなただけのReadOnlyに関するいくつかの奇妙なエラーを得たので、深く掘ることを決めた.
この記事では、混乱をクリアし、constアサーションについてのすべての疑問をつぶしたいです.

constアサーションは型キャストではない
タイプキャストは、単に置く、悪です.彼らはコンパイラに言うつもりです:「私は何をしているかを知っています.
率直に言って、ほとんどの場合、開発者はコンパイラよりよく知らない.本当に良い理由がない限り、タイプキャストを使用しないでください.
以下に、タイプキャストが行うことのできるいくつかの例を示します.
type Foo = 'foo'
const foo = 'bar' as Foo

type Obj = { foo: Foo }
const obj = {} as Obj
TypeScript playground
型が十分に重なっているので、型スクリプトはとても細かくなります.もちろん、それは単なるfalseですが、型キャストを使用すると、コンパイラはあなたに降伏します.
あなたが正しいと思う場合でさえ、これは面倒でありえます.次の例を考えます.
type Variant = 'primary' | 'secondary'

type Props = {
    variant: Variant
}

const Component = (props: Props) => null

const props = { variant: 'primary' }

Component(props)
TypeScript playground
コンパイラは以下のようになります.
Argument of type '{ variant: string; }' is not assignable to parameter of type 'Props'.
  Types of property 'variant' are incompatible.
    Type 'string' is not assignable to type 'Variant'.(2345)
VariantはStringに推論されるからです.これは他の文字列を別の文字列に再割り当てするのを止めないからです.
type Variant = 'primary' | 'secondary'

type Props = {
    variant: Variant
}

const Component = (props: Props) => null

const props = { variant: 'primary' }
props.variant = 'somethingElse'

Component(props)
TypeScript playground
私たちがconstを定義しても、JavaScriptのオブジェクトはまだ可変です、したがって、ストリングリテラル「プライマリ」を推論することは間違っています.タイプキャストはこれを解決します.
type Variant = 'primary' | 'secondary'

type Props = {
    variant: Variant
}

const Component = (props: Props) => null

const props = { variant: 'primary' } as Props

Component(props)
TypeScript playground
私が以前に言及したのと同じ理由のために、我々が我々のバリアントタイプからプライマリを取り除くならば、我々はタイプ誤りをここで得ません.これは、多くの解決策のように、これは現在動作する何かですが、非常に将来の証拠ではないことを意味します.
あなたのソフトウェアを変えるために弾力的にすることは、私の意見では、タイプスクリプトを使用する本当の利益のうちの1つです.弾力性を達成するには、タイプキャストを放棄することを含む適切な考え方が必要です.
このシナリオでは、最も簡単な解決方法(オブジェクトのインライン化はオプションではないと仮定する)は、型キャストではなく明示的な型注釈を使用することです.
type Variant = 'primary' | 'secondary'

type Props = {
    variant: Variant
}

const Component = (props: Props) => null

const props: Props = { variant: 'primary' }

Component(props)
TypeScript playground
これはおそらくあなたのほとんどが今やっていることであり、それは完全に安全性についてタイプ安全です.

constアサーションの使用
私はまだ問題をconstアサーションで修正することが好ましい方法であると考えます.
type Variant = 'primary' | 'secondary'

type Props = {
    variant: Variant
}

const Component = (props: Props) => null

const props = { variant: 'primary' } as const

Component(props)
TypeScript playground
あなたが使用しているライブラリからエクスポートされていないので、注釈のために利用可能なタイプを持っていない場合に便利です.構文はより簡潔であり、constアサーションを使用することも他の利点を持っています.あなたのオブジェクトが本当に一定であることをタイプスクリプトに合図しているので、コンパイラはあなたの意図についてより良い仮定をすることができます:
  • 文字列と数字は、そのリテラルカウンターとして推論することができます
  • 配列は固定長でタプルになる
  • すべてが読み込み専用であるので、誤って後でそれを変異することはできません( array , sort )
  • これは、タイプのレベルでその定数で働くとき、あなたに柔軟性のトンを与えます.

    オブジェクトまたは配列からの型の抽出
    次の例を考えます.
    type Variant = 'primary' | 'secondary'
    type Option = { id: Variant; label: string }
    
    const options: Array<Option> = [
        {
            id: 'primary',
            label: 'The primary option',
        },
        {
            id: 'secondary',
            label: 'The secondary option',
        },
    ]
    
    TypeScript playground
    これまで、とても簡単です.別のバリアントを追加する場合は、バリアント型とオプションの配列に追加する必要があります.コードが共存している限り、これはすばらしいです、そして、オプション配列を明示的に注釈しているので、それはかなりboilerplaty yにかなり速くなることができます.constアサーションを使用すると、オプション配列から型を取得できます.
    const options = [
        {
            id: 'primary',
            label: 'The primary option',
        },
        {
            id: 'secondary',
            label: 'The secondary option',
        },
    ] as const
    
    type Variant = typeof options[number]['id']
    
    TypeScript playground
    私たちは基本的にコンパイラを指示しています:オプションのすべての項目を歩いて、私にIDのタイプを与えますprimary | secondary , そして、我々は現在、真実の1つの源を持っています.
    もちろん、これはconstアサーションのためだけに動作します.そして、それを削除するか、またはそれを忘れるならば、variantはちょうど型番号であるでしょう.これは、開発者が間違いをしないことに依存していますので、問題があります.
    幸運にも、コンパイラはこの作業をreadonly配列でのみ行うように指示することもできます.
    const options = [
        {
            id: 'primary',
            label: 'The primary option',
        },
        {
            id: 'secondary',
            label: 'The secondary option',
        },
    ] as const
    
    type EnsureReadonlyArray<T> = T extends Array<any>
        ? never
        : T extends ReadonlyArray<any>
        ? T
        : never
    export type Extract<
        T extends ReadonlyArray<any>,
        K extends keyof T[number]
    > = EnsureReadonlyArray<T>[number][K]
    
    type Variant = Extract<typeof options, 'id'>
    
    TypeScript playground
    constアサーションがすべてを読み取り専用にするという事実に頼っているので、これは確かに少しハックです.しかし、それはあなたがconstアサーションを忘れるならば、バリアントが決して推論されないことを確認します、そして、それはあなたがどこでそれを使うことができないことを意味します.私はいつでもその安全性を取るだろう(とティルはutilで離れてタック).

    どこでも読み込み専用
    最後に、私が絶えずconst主張を使用しているので、私により明らかになった何かを指摘したいです:

    Make everything readonly per default


    - tkdodo
    ものは:mutable配列またはオブジェクトを読み取り専用配列またはオブジェクトを取るメソッドに渡すことができますが、他の方法ではありません.その理由は非常に簡単です.関数が配列を受け入れると、関数はそれを変異させるかもしれません.
    const getFirst = (param: Array<string>): string => param.sort()[0]
    
    const strings = ['foo', 'bar', 'baz'] as const
    
    getFirst(strings)
    
    TypeScript playground
    配列をソートしない場合でも、以下のようにエラーが発生します.
    Argument of type 'readonly ["foo", "bar", "baz"]' is not assignable to parameter of type 'string[]'.
      The type 'readonly ["foo", "bar", "baz"]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.(2345)
    
    パラメタReadOnlyを作ることによって、我々はちょうど我々がそれを変異させないことを保証します(それは常に良いです-機能パラメタで混乱しないでください).
    あなたが図書館著者であるならば、私は強くあなたの機能にすべての入力を読むだけであることを勧めます.
    あなたも、const主張を好みますか?コメントを残す⬇️