$30 off During Our Annual Pro Sale. View Details »

any禁止 絶対に型付けを諦めないための便利なユーティリティ関数 / techstand6

OKUNOKENTARO
November 10, 2021

any禁止 絶対に型付けを諦めないための便利なユーティリティ関数 / techstand6

2021/11/10 TECH STAND #6 TypeScriptにて発表した資料です。

OKUNOKENTARO

November 10, 2021
Tweet

More Decks by OKUNOKENTARO

Other Decks in Technology

Transcript

  1. BOZېࢭ
    ઈରʹܕ෇͚ΛఘΊͳ͍ͨΊͷ
    ศརͳϢʔςΟ
    ϦςΟؔ਺
    /PW TUBOEGN5&$)45"/%5ZQF4DSJQU
    !PLVOPLFOUBSP

    View Slide


  2. w Ԟ໺ݡଠ࿠!PLVOPLFOUBSP
    w ΫϨε΢ΣΞגࣜձࣾ
    w 5ZQF4DSJQUྺ೥

    View Slide

  3. ܕɺ
    ͪΌΜͱ෇͚ͯ·͔͢
    const res = await fetch('/api/users', {method: 'GET'});


    const users = await res.json();

    View Slide

  4. ͜ΕͰຬ଍ͯ͠ͳ͍
    ʁ
    const res = await fetch('/api/users', {method: 'GET'});


    const users = await res.json() as User[];

    View Slide

  5. anyܕʹas TΛࢦఆ͢Δͷ͸ةݥ
    wʮanyܕ͸ةݥʯ

    ʜͱ͍͏࿩୊͸ཧղ͍ͯͯ͠΋ɺ
    as TͳΒฏؾͰ࢖ͬͯ͠·
    ͏ݱ৔͸গͳ͘ͳ͍
    w Ͱ΋
    ʮany as T΋ةݥʯ

    w anyܕͷ஋ʹas T
    ʢas User[]ͳͲʣ
    Λ෇͚ͯ΋ίϯύΠϧΤϥʔʹͳΒͳ͍

    View Slide

  6. ͳʹ͕ةݥͳͷ͔
    w ͍͘
    ΒͰ΋ӕ͕͚ͭΔ
    w ίϯύΠϧ͸௨͍ͬͯͯ΋ɺ
    ࣮ࡍʹͦ͜ʹྲྀΕͯ͘
    Δ஋ͷߏ଄͸ҟͳΔঢ়گ
    w as T͸
    ʮίϯύΠ
    ϥΛ͍͍ٗͯΔ͚ͩʯ
    Ͱ͋Δ

    View Slide

  7. ͜ͷίʔ
    υ͸ΤϥʔʹͳΒͳ͍
    const res1 = await fetch('/api/users', {method: 'GET'});


    const users = await res1.json() as User[];


    const res2 = await fetch('/api/items', {method: 'GET'});


    const items = await res2.json() as User[];
    as Item[] Ͱ͸ʁʁ

    View Slide

  8. asΛ࢖Θͣ҆શʹܕ෇͚Λ͢Δ
    w anyېࢭasېࢭ
    w 5ZQFQSFEJDBUFTΛ࢖͏

    IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLOBSSPXJOHIUNMVTJOHUZQFQSFEJDBUFT

    w function isSomething(v: unknown): v is Something { ...

    Έ͍ͨͳ΍ͭ
    w JG৚݅ࣜʹͯisSomething(v)Λ࢖ͬͨΒɺ

    ͦͷJGϒϩοΫ಺Ͱ͸v͕Somethingܕͱͯ͠ѻ͑Δ

    View Slide

  9. type User = {


    id: number;


    name: string;


    };


    function hasId(v: unknown): v is { id: unknown } {


    if (typeof v !== "object" || v === null) {


    return false;


    }


    return "id" in v;


    }


    function hasName(v: unknown): v is { name: unknown } {


    if (typeof v !== "object" || v === null) {


    return false;


    }


    return "name" in v;


    }


    function isUser(v: unknown): v is User {


    if (!hasId(v) || !hasName(v)) {


    return false;


    }


    if (typeof v.id !== "number") {


    return false;


    }


    if (typeof v.name !== "string") {


    return false;


    }


    return true;


    }

    View Slide

  10. ׂͱखؒ
    w asΛઈ໓ͤ͞Α
    ͏
    ͱͨ͠Βɺ
    5ZQFQSFEJDBUFTؔ਺͕ͨ͘
    ͞Μඞཁ
    w JGϒϩοΫͷதͰ͔͠ਪ࿦͞Εͳ͍ͷͰɺ
    ίʔ
    υ͕ෳࡶʹͳΓ͕ͪ
    w JGϒϩοΫ͕ඞਢͳͷ͸໘౗

    View Slide

  11. JGจΛ࢖Θͳ͍
    w "TTFSUJPOGVODUJPOTΛ࢖͏

    IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLSFMFBTFOPUFTUZQFTDSJQUIUNMBTTFSUJPOGVODUJPOT

    w function assertSomething(

    v: unknown): asserts v is Something { ...

    Έ͍ͨͳ΍ͭ
    w assertSomething(v)Λ࢖ͬͨΒɺ
    ͦͷؔ਺͕ྫ֎Λ౤͛ͳ͍ݶΓ

    ಉҰείʔϓͷҎ߱ͷεςʔ
    τϝϯ
    τͰ͸v͕Somethingܕͱͯ͠ѻ͑Δ

    View Slide

  12. type User = {


    id: number;


    name: string;


    };


    function assertId(v: unknown): asserts v is { id: unknown } {


    if (typeof v !== "object" || v === null) {


    throw new Error("Value should be non-null object");


    }


    if (!("id" in v)) {


    throw new Error('Value should have the property "id"');


    }


    // noop


    }


    function assertName(v: unknown): asserts v is { name: unknown } {


    if (typeof v !== "object" || v === null) {


    throw new Error("Value should be non-null object");


    }


    if (!("name" in v)) {


    throw new Error('Value should have the property "name"');


    }


    // noop


    }


    function assertUser(v: unknown): asserts v is User {


    assertId(v);


    assertName(v);


    if (typeof v.id !== "number") {


    throw new Error('"id" should be a number');


    }


    if (typeof v.name !== "string") {


    throw new Error('"name" should be a string');


    }


    // noop


    }

    View Slide

  13. ΍ͬͺΓखؒ
    w asΛઈ໓ͤ͞ΔͨΊʹ͜͜·Ͱ͢Δ
    ʁ

    w ҰͭͷΦϒδΣΫ
    τʹ͍ͭͯݕূ͢Δ͚ͩͰؔ਺ଟ͗͢͡Όͳ͍
    ʁ

    w ΋͏asΛ࢖ͬͯ΋͍͍Μ͡Όʜ
    ʁ

    w ઈରʹఘΊͳ͍

    View Slide

  14. ศརؔ਺Λ࡞ͬͯΈͨ
    w ϓϩύςΟ͕ੜ͍͑ͯΔ͜ͱΛϥϯλΠϜͰݕূͭͭ͠
    w ܕ΋ͪΌΜͱ෇͍ͯ͘Εͯ
    w ͔͠΋ܕఆٛΛमਖ਼ͨ͠ΒϥϯλΠϜͰ

    ݕূ͞ΕΔϓϩύςΟ
    ΋௥ैͯ͠΄͍͠

    View Slide

  15. type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (


    k: infer I


    ) => void


    ? I


    : never;


    type LastOf = UnionToIntersection<


    T extends any ? () => T : never


    > extends () => infer R


    ? R


    : never;


    type Push = [...T, V];


    type UnionToTuple<


    T,


    L = LastOf,


    N = [T] extends [never] ? true : false


    > = true extends N ? [] : Push>, L>;


    type Tuple = [TItem, ...TItem[]] & {


    length: TLength;


    };


    /**


    * Tuple ͸ [string, string, string] Λฦ͢


    * ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ


    *


    * 'a' | 'b'


    * => ['a' | 'b', 'a' | 'b']


    * 'a' | 'b' | 'c'


    * => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c']


    */


    type UnionToUnionTuple = Tuple["length"]>;


    type TypeEq = (() => T extends A ? 1 : 2) extends () => T extends B


    ? 1


    : 2


    ? true


    : false;


    type RemoveNeverProperties = Pick<


    T,


    {


    [P in keyof T]: [T[P]] extends [never] ? never : P;


    }[keyof T]


    >;


    type AnyAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type UnknownAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type OptionalAsUndefined = {


    [P in keyof T]: T[P] extends NonNullable ? T[P] : undefined;


    };


    type UndefinedAsNever = {


    [P in keyof T]-?: T[P] extends undefined ? never : T[P];


    };


    /**


    * { a?: string; b: number; c?: any; d: any } Λ


    * { b: number; d: any } ͚ͩʹ͢Δɻ


    *


    * AnyAs0 ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ


    */


    type RemoveOptionalProperties = {


    [P in keyof RemoveNeverProperties<


    UndefinedAsNever>>>


    >]: T[P];


    };


    function isObject(v: unknown): v is Record {


    if (typeof v !== "object") {


    return false;


    }


    return v !== null;


    }


    function assertObject(


    v: unknown,


    target = ""


    ): asserts v is Record {


    if (!isObject(v)) {


    throw new Error(`${target} should be object`.trim());


    }


    }


    function isMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    errorPropsRef: string[],


    target = ""


    ): v is T {


    assertObject(v, target);


    if (new Set(props).size !== props.length) {


    throw new Error("Invalid props");


    }


    return props


    .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর


    if (!isMatchedType(v, props, errorPropsRef, target)) {


    // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ


    throw new Error(


    `${target} should be aligned type. ${


    0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""


    }`.trim()


    );


    }


    }

    View Slide



  16. .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর


    if (!isMatchedType(v, props, errorPropsRef, target)) {


    // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ


    throw new Error(


    `${target} should be aligned type. ${


    0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""


    }`.trim()


    );


    }


    }


    ͳʹΛ࡞͔ͬͨ
    w assertMatchedType()ؔ਺Λ࣮૷ͨ͠
    w ୈҾ਺ʹݕূ͍ͨ͠ΦϒδΣΫ
    τ
    w ୈҾ਺ʹݕূ͍ͨ͠ϓϩύςΟ໊ͷ഑ྻ
    w ܕύϥϝʔλʹཧ૝ͷܕఆٛΛॻ͘

    View Slide



  17. .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর


    if (!isMatchedType(v, props, errorPropsRef, target)) {


    // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ


    throw new Error(


    `${target} should be aligned type. ${


    0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""


    }`.trim()


    );


    }


    }


    ͳʹΛ࡞͔ͬͨ
    w assertMatchedType()ؔ਺Λ࣮૷ͨ͠
    w ୈҾ਺ʹݕূ͍ͨ͠ΦϒδΣΫ
    τ
    w ୈҾ਺ʹݕূ͍ͨ͠ϓϩύςΟ໊ͷ഑ྻ
    w ܕύϥϝʔλʹཧ૝ͷܕఆٛΛॻ͘
    type User = {


    id: number;


    name: string;


    };


    const obj: unknown = { id: 1, name: "foo" };


    assertMatchedType(obj, ["id", "name"]);


    console.log(obj);

    View Slide



  18. .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর


    if (!isMatchedType(v, props, errorPropsRef, target)) {


    // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ


    throw new Error(


    `${target} should be aligned type. ${


    0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""


    }`.trim()


    );


    }


    }


    ศརͳͱ͜Ζ
    w ܕఆ͕ٛมΘΔͱɺ
    ୈҾ਺ͰٻΊΒΕΔ

    Ҿ਺ͷܕ͕มΘΔ
    w ܕఆ͚ٛͩ௚ͯ͠ɺ

    ϥϯλΠϜଆ͕௚ͬͯͳ͍ঢ়گΛ๷͙
    type User = {


    id: number;


    name: string;


    email: string;


    };


    const obj: unknown = { id: 1, name: "foo" };


    assertMatchedType(obj, ["id", "name"]);


    console.log(obj);
    Argument of type '["id", "name"]' is not assignable to
    parameter of type 'UnionToUnionTuple<"id" | "name" |
    "email">'.

    View Slide

  19. function assertObject(


    v: unknown,


    target = ""


    ): asserts v is Record {


    if (!isObject(v)) {


    throw new Error(`${target} should be object`.trim());


    }


    }


    function isMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    errorPropsRef: string[],


    target = ""


    ): v is T {


    assertObject(v, target);


    if (new Set(props).size !== props.length) {


    throw new Error("Invalid props");


    }


    return props


    .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর




    w ศརؔ਺ΛassertObject()Λࣄલʹ

    ࡞͓ͬͯ͘
    w ϓϩύςΟࢦఆ͸͢΂ͯϢχʔΫͰ͋Δ͜ͱΛ

    อূ
    w ͻͱͭͻͱͭinΛ࢖ͬͯ֬ೝ
    w ͳ͚Ε͹Τϥʔࢀরʹه࿥
    ϥϯλΠϜଆͷ࢓૊Έ

    View Slide

  20. * { a?: string; b: number; c?: any; d: any } Λ


    * { b: number; d: any } ͚ͩʹ͢Δɻ


    *


    * AnyAs0 ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ


    */


    type RemoveOptionalProperties = {


    [P in keyof RemoveNeverProperties<


    UndefinedAsNever>>>


    >]: T[P];


    };


    function isObject(v: unknown): v is Record {


    if (typeof v !== "object") {


    return false;


    }


    return v !== null;


    }


    function assertObject(


    v: unknown,


    target = ""


    ): asserts v is Record {


    if (!isObject(v)) {


    throw new Error(`${target} should be object`.trim());


    }


    }


    function isMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    errorPropsRef: string[],


    target = ""


    ): v is T {


    assertObject(v, target);


    if (new Set(props).size !== props.length) {


    throw new Error("Invalid props");


    }


    return props


    .map((prop) => {



    w ศརؔ਺assertObject()͸

    "TTFSUJPOGVODUJPOTͷ

    ͓खຊͷΑ
    ͏ͳؔ਺
    assertObject()

    View Slide

  21. w ͔͜͜Β5ZQF4DSJQUͷຊؾ
    w ͢΂ͯͷϓϩύςΟ
    Λྻڍ͍ͨ͠
    w PQUJPOBMͳϓϩύςΟ͸ݕূͨ͘͠ͳ͍
    w PQUJPOBMͳϓϩύςΟ͸ॻ͔ͳ͘
    ͯ΋

    ίϯύΠϧΤϥʔʹͨ͘͠ͳ͍
    w ͦΕΛ͢ΔͨΊͷܕύζϧ
    ܕύζϧ


    : false;


    type RemoveNeverProperties = Pick<


    T,


    {


    [P in keyof T]: [T[P]] extends [never] ? never : P;


    }[keyof T]


    >;


    type AnyAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type UnknownAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type OptionalAsUndefined = {


    [P in keyof T]: T[P] extends NonNullable ? T[P] : undefined;


    };


    type UndefinedAsNever = {


    [P in keyof T]-?: T[P] extends undefined ? never : T[P];


    };


    /**


    * { a?: string; b: number; c?: any; d: any } Λ


    * { b: number; d: any } ͚ͩʹ͢Δɻ


    *


    * AnyAs0 ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ


    */


    type RemoveOptionalProperties = {


    [P in keyof RemoveNeverProperties<


    UndefinedAsNever>>>


    >]: T[P];


    };


    function isObject(v: unknown): v is Record {






    View Slide



  22. : false;


    type RemoveNeverProperties = Pick<


    T,


    {


    [P in keyof T]: [T[P]] extends [never] ? never : P;


    }[keyof T]


    >;


    type AnyAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type UnknownAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type OptionalAsUndefined = {


    [P in keyof T]: T[P] extends NonNullable ? T[P] : undefined;


    };


    type UndefinedAsNever = {


    [P in keyof T]-?: T[P] extends undefined ? never : T[P];


    };


    /**


    * { a?: string; b: number; c?: any; d: any } Λ


    * { b: number; d: any } ͚ͩʹ͢Δɻ


    *


    * AnyAs0 ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ


    */


    type RemoveOptionalProperties = {


    [P in keyof RemoveNeverProperties<


    UndefinedAsNever>>>


    >]: T[P];


    };


    function isObject(v: unknown): v is Record {






    Ṗͷຐ๏
    w AnyAs0ͱUnknownAs0Ͱɺ
    anyܕͱ

    unknownܕΛҰ୴͢΂ͯ


    ʢ਺஋Ϧςϥϧܕʣ
    ʹஔ͖׵͑Δ
    w PQUJPOBMͰ͋Ε͹

    ͢΂ͯundefinedʹ͢Δ
    w 0ܕʹஔ͖׵͓͔͑ͯͳ͍ͱ

    PQUJPOBManyͱPQUJPOBMunknown͕

    ਖ਼͘͠ॲཧ͞Εͳ͍

    View Slide



  23. : false;


    type RemoveNeverProperties = Pick<


    T,


    {


    [P in keyof T]: [T[P]] extends [never] ? never : P;


    }[keyof T]


    >;


    type AnyAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type UnknownAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type OptionalAsUndefined = {


    [P in keyof T]: T[P] extends NonNullable ? T[P] : undefined;


    };


    type UndefinedAsNever = {


    [P in keyof T]-?: T[P] extends undefined ? never : T[P];


    };


    /**


    * { a?: string; b: number; c?: any; d: any } Λ


    * { b: number; d: any } ͚ͩʹ͢Δɻ


    *


    * AnyAs0 ͕ͳ͍ͱ ?: any ΁ͷରԠ͕࿙ΕΔͷͰ஫ҙɻ


    */


    type RemoveOptionalProperties = {


    [P in keyof RemoveNeverProperties<


    UndefinedAsNever>>>


    >]: T[P];


    };


    function isObject(v: unknown): v is Record {






    Ṗͷຐ๏
    w undefinedΛ͢΂ͯPQUJPOBMΛ֎ͭͭ͠

    neverʹஔ͖׵͑Δ
    w neverͰ͋Ε͹ɺ
    ͦΕΒΛ͢΂ͯ

    PickΛ࢖ͬͯআڈ͢Δ
    w ͜Ε͸undefinedΛؚΊଞͷܕͰ͸

    ͜ͷΑ
    ͏ͳॲཧ͕࣮ݱͰ͖ͣ

    neverͷΈআڈͰ͖Δ

    View Slide

  24. ܕύζϧͷ൪ਓ
    w ܕύζϧΛॻ͍͍ͯΔͱ͖͸ݕূ͕ࠔ೉
    w TypeEqʻA, BʼΛ࢖ͬͯ

    ܕςε
    τΛॻ͘
    ͱΑ͍
    w AnyAs0 UnknownAs0Ͱ΋࢖༻
    > = true extends N ? [] : Push>, L>;


    type Tuple = [TItem, ...TItem[]] & {


    length: TLength;


    };


    /**


    * Tuple ͸ [string, string, string] Λฦ͢


    * ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ


    *


    * 'a' | 'b'


    * => ['a' | 'b', 'a' | 'b']


    * 'a' | 'b' | 'c'


    * => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c']


    */


    type UnionToUnionTuple = Tuple["length"]>;


    type TypeEq = (() => T extends A ? 1 : 2) extends () => T extends B


    ? 1


    : 2


    ? true


    : false;


    type RemoveNeverProperties = Pick<


    T,


    {


    [P in keyof T]: [T[P]] extends [never] ? never : P;


    }[keyof T]


    >;


    type AnyAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type UnknownAs0 = {


    [P in keyof T]: TypeEq extends true ? 0 : T[P];


    };


    type OptionalAsUndefined = {



    https://qiita.com/kgtkr/items/2a8290d1b1314063a524

    View Slide

  25. type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (


    k: infer I


    ) => void


    ? I


    : never;


    type LastOf = UnionToIntersection<


    T extends any ? () => T : never


    > extends () => infer R


    ? R


    : never;


    type Push = [...T, V];


    type UnionToTuple<


    T,


    L = LastOf,


    N = [T] extends [never] ? true : false


    > = true extends N ? [] : Push>, L>;


    type Tuple = [TItem, ...TItem[]] & {


    length: TLength;


    };


    /**


    * Tuple ͸ [string, string, string] Λฦ͢


    * ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ


    *


    * 'a' | 'b'


    * => ['a' | 'b', 'a' | 'b']


    * 'a' | 'b' | 'c'


    * => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c']


    */


    type UnionToUnionTuple = Tuple["length"]>;





    Ṗͷຐ๏
    w PQUJPOBMͳϓϩύςΟ
    Λ͢΂ͯ

    আڈͨ͠ͷͰɺ
    ࢒ΓͷඞਢϓϩύςΟ
    Λ

    ྻڍ͠λϓϧͰड͚औΓ͍ͨ
    w UnionToTupleΛ࢖͑͹keyof Tͷ

    ݁ՌΛλϓϧͱͯ͠ѻ͑Δ
    w ͔͠͠ॱෆಉͰ͋Δ
    ʢఆ͕ٛهड़ॱʹ

    ͳΒͳ͍ʣ
    ͜ͱͰ௚ײతͰ͸ͳ͍ͨΊ

    UnionToUnionTupleͰهड़ॱΛ

    ࣗ༝ʹ͢Δ
    w ϥϯλΠϜͰnew Set()Λͨ͠ͷ͸

    ͜Εରࡦ

    View Slide

  26. ͜Ε͕࣮ݱͰ͖Δ
    w ܕఆ͕ٛมΘΔͱɺ
    ୈҾ਺ͰٻΊΒΕΔ

    Ҿ਺ͷܕ͕มΘΔ
    w ܕఆ͚ٛͩ௚ͯ͠ɺ

    ϥϯλΠϜଆ͕௚ͬͯͳ͍ঢ়گΛ๷͙


    .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর


    if (!isMatchedType(v, props, errorPropsRef, target)) {


    // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ


    throw new Error(


    `${target} should be aligned type. ${


    0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""


    }`.trim()


    );


    }


    }


    type User = {


    id: number;


    name: string;


    email: string;


    };


    const obj: unknown = { id: 1, name: "foo" };


    assertMatchedType(obj, ["id", "name"]);


    console.log(obj);
    Argument of type '["id", "name"]' is not assignable to
    parameter of type 'UnionToUnionTuple<"id" | "name" |
    "email">'.

    View Slide



  27. .map((prop) => {


    if (typeof prop !== "string") {


    throw new Error("Invalid prop");


    }


    const within = prop in v;


    if (!within) {


    errorPropsRef.push(prop); // mutate


    }


    return within;


    })


    .every((flag) => flag);


    }


    export function assertMatchedType(


    v: unknown,


    props: UnionToUnionTuple>,


    target = ""


    ): asserts v is T {


    const errorPropsRef: string[] = []; // ࢠʹ஋Λ֨ೲͤ͞ΔͨΊͷۭ഑ྻࢀর


    if (!isMatchedType(v, props, errorPropsRef, target)) {


    // ͜͜ʹ֘౰͢Δͱ͖ errorPropsRef ഑ྻͷத਎ʹ͸ΤϥʔՕॴ͕٧·͍ͬͯΔɻ


    throw new Error(


    `${target} should be aligned type. ${


    0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""


    }`.trim()


    );


    }


    }


    ࿙Εʹ͍ͭͯ
    w id͕number name͕stringͰ͋Δ͜ͱ

    ·Ͱ͸Έͯ͘Εͳ͍
    w Α
    Γ҆શʹ͍ͨ͠ͳΒ͢΂ͯͷϓϩύςΟͷܕΛ

    unknownܕʹ͢Δͷ͕Α͍
    w ͦͷ৔߹͸assertString()
    assertNumber()ͳͲͷؔ਺ͱ૊Έ߹ΘͤΔ
    type User = {


    id: unknown;


    name: unknown;


    email: unknown;


    };


    const obj: unknown = { id: 1, name: "foo" };


    assertMatchedType(obj, ["id", "name"]);


    console.log(obj);

    View Slide

  28. $POEJUJPOBM5ZQFTΛۃΊΔͱָ͍͠
    w $POEJUJPOBM5ZQFT͸࣮͸͔ͳΓͷදݱྗ͕͋Δ

    IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLDPOEJUJPOBMUZQFTIUNM

    w Ұݟݫͦ͠͏ͳܕਪ࿦Ͱ΋࣮͸ॻ͚ͨΓ͢Δ
    w $POEJUJPOBM5ZQFTΛۃΊΑ
    ͏
    w as͸ېࢭͰ͖Δ

    View Slide

  29. 5IBOLZPV
    https://gist.github.com/okunokentaro/74361c2683bd8e0f349d21ddebe189cf

    View Slide