不可能な状態を型スクリプトで不可能にする


数ヶ月後、私は仕事でシンプルな機能を作成することを求められた.私は会社のプライバシーポリシーのリンクへのリンクを入力することができる会社のプロファイルに小さなセクションを追加することでした.以下に必要条件のリストを示します.
  • 前にプライバシーポリシーを入力したことがない場合は、プライバシーポリシーリンクを含むボックスは
  • 彼らがプライバシーポリシーリンクに入ったならば、ボックスは単にクリック可能なリンクを示すべきです
  • 会社が「編集」をクリックするとき、彼らは会社のプライバシーポリシーへのリンクを編集することができるでしょう
  • 簡単、右?このスクリプトをモデル化する方法を見てみましょう.
    type PrivacyPolicy = {
        link: string;
        isEditing: boolean;
    }
    
    The link フィールドは明らかにプライバシーポリシーへのリンクを保持しますisEditing Booleanはビューと編集モードを切り替えることができます.
    次に、別の要件があります.
  • ユーザーが編集するとき、彼らは編集を始める前に、リンクにされた変更を戻すために「キャンセル」をクリックすることができなければなりません.
  • わかりました.タイプを拡大しましょう:
    type PrivacyPolicy = {
        link: string;
        isEditing: boolean;
        oldValue: string;
    }
    
    ユーザーが「編集」を開始すると、現在の値link インoldValue を設定し、isEditing からtrue . ユーザーが「キャンセル」をクリックすると、値link であるoldValue フィールドと設定isEditing リンクに戻るfalse . ジョブ完了.
    今、このアプローチは動作しますが、それは我々の消費者が必要ですPrivacyPolicy 型のフィールドの値の設定とリセットについて細心の注意を払うように入力し、我々は簡単に我々のタイプの状態を理解するのは難しい状況に入ることができます.説明するために、以下のインスタンスは何を意味しますか?
    const privacyPolicy: PrivacyPolicy = {
        link: "http://google.com",
        isEditing: false,
        oldValue: "http://yahoo.com"
    };
    
    ユーザが変更を必要とし、変更が保存されたときに古い値がクリアされていないことを意味します.これが問題につながるoldValue フィールドは、ユーザーが編集していないときも意味をなしますか?"
    次の例を見てみましょう.
    const privacyPolicy: PrivacyPolicy = {
        link: "",
        isEditing: true,
        oldValue: ""
    };
    
    これはおそらく、ユーザーが最初にリンクを編集していることを意味しますoldValue ).
    これは、次の開発者は、この機能を変更する必要が動作しますが、このすべてを取得する必要があります.次の開発者はあまり頭をかがめないようにしよう.
    まず第一にoldValue フィールド.それは我々が編集している場合にのみ意味をなします.それで、我々は我々のタイプを2に分けることができます;「編集」ケースを表示し、表示ケースを表す
    type Editing = {
        kind: 'editing';
        link: string;
        oldValue: string;
    };
    
    type Viewing = {
        kind: 'viewing';
        link: string;
    };
    
    type PrivacyPolicy = Editing | Viewing;
    
    代わりに厄介なブールを反転すると、我々は今はっきりとは、ユーザーがいる状態を識別する2つのタイプの間で切り替えることができます.さらに、我々はoldValue ユーザーがプライバシーポリシーリンクを表示している場合のフィールド.すごい!
    今、もし我々がPrivacyPolicy このように見えるインスタンスlink フィールドは、プライバシーポリシーのリンクが設定されていないためです.
    const privacyPolicy: PrivacyPolicy = {
        kind: 'viewing',
        link: "",
    }; 
    
    これについてさらに明確にするために、我々は我々の第3の状態を抽出することを考慮することができましたPrivacyPolicy リンクが入力されていないことを表す型
    type NoneSet {
        kind: 'none-set';
    }
    
    type Editing = {
        kind: 'editing';
        link: string;
        oldValue: string;
    };
    
    type Viewing = {
        kind: 'viewing';
        link: string;
    };
    
    type PrivacyPolicy = NoneSet | Viewing | Editing;
    
    わかりました.だから今我々は明らかに我々のコードの意図を述べている.プライバシーポリシーができる3つの州がありますどちらも設定されていない、それが設定されているユーザーがそれを表示しているか、ユーザーが現在それを編集しています.
    私たちは、私たちのタイプができるだけ密接に現実のシナリオを表すのに長い道のりを来ました、しかし、まだ改善されることができる1つのものがあります.値がその値に保持されていないタイプレベルの執行はありませんlink 我々のフィールドViewing タイプは実際に現実世界のリンクに付属しています.これは任意の文字列です.
    const privacyPolicy: PrivacyPolicy = {
        kind: 'viewing',
        link: 'haha, bad luck amigo!',
    };
    
    この保証を作成するには、不透明型と呼ばれるテクニックを使用する必要があります.不透明型を含むファイルは、ファイルのコンテキストでのみインスタンス化できるインターフェイスを公開します.typescriptが構造的なタイピングを使用するように、これは少しトリッキーですが、シンボルの使用によって達成することができます.ファイルを作成しましょうlink.ts を返します.以下に説明します.
    // file: link.ts
    
    const linkTag = Symbol('LINK-SYMBOL');
    
    export type Link = {
        [linkTag]: 'LINK';
        link: string;
    };
    
    export const fromString = (link: string): Link | null => {
        if (isValidUrl(link)) {
            return {
                [linkTag]: 'LINK',
                link: link,
            };
        } else {
            return null;
        }
    };
    
    const isValidUrl = (candidate: string): boolean => {
        // the implementation of this function is unimportant
        // but it will return a boolean based on whether
        // `candidate` is a valid URL
    }; 
    
    さて、ここで何が起こっているのか.第一に、私たちはlinkTag . シンボルは文字列、数値、ブール値のようなecmascriptプリミティブです.使用例に興味深いシンボルの2つのプロパティがあります.
  • シンボルはユニークで、他の値がそれに等しくないことを意味する
  • 文字列と同じように、オブジェクトプロパティのキーとして使用できます
  • 組み合わせで、これはtypescriptの構造的な性質から抜け出すために使用することができます私たちのシステムの他のいかなる場所も割り当て可能な値を作成できませんLink ! 試してみると、コンパイラはエラーをスローします.
    今我々は更新することができますPrivacyPolicy 種類:
    type NoneSet {
        kind: 'none-set';
    }
    
    type Editing = {
        kind: 'editing';
        link: string;
        oldValue: Link | null;
    };
    
    type Viewing = {
        kind: 'viewing';
        link: Link;
    };
    
    type PrivacyPolicy = NoneSet | Viewing | Editing;
    
    それで、我々がリンクを見ているとき、我々はそれが有効なURLでなければならないということを知っています.なぜ?なぜなら、私たちがそのインスタンスを作る唯一の方法だからですLink タイプは終わりまでfromString 関数は、最愛のユーザが入力した文字列を検証します.
    編集中の場合はoldValue フィールドは有効ですLink or null . The null ケースは前のリンクセットがなかったことを意味しなければなりません.
    わかりました.例を見ましょう!はい、ここに行きます.