Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

anyܕʹas TΛࢦఆ͢Δͷ͸ةݥ wʮanyܕ͸ةݥʯ 
 ʜͱ͍͏࿩୊͸ཧղ͍ͯͯ͠΋ɺ as TͳΒฏؾͰ࢖ͬͯ͠· ͏ݱ৔͸গͳ͘ͳ͍ w Ͱ΋ ʮany as T΋ةݥʯ w anyܕͷ஋ʹas T ʢas User[]ͳͲʣ Λ෇͚ͯ΋ίϯύΠϧΤϥʔʹͳΒͳ͍

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

͜ͷίʔ υ͸ΤϥʔʹͳΒͳ͍ 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[] Ͱ͸ʁʁ

Slide 8

Slide 8 text

asΛ࢖Θͣ҆શʹܕ෇͚Λ͢Δ w anyېࢭasېࢭ w 5ZQFQSFEJDBUFTΛ࢖͏ 
 IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLOBSSPXJOHIUNMVTJOHUZQFQSFEJDBUFT w function isSomething(v: unknown): v is Something { ... 
 Έ͍ͨͳ΍ͭ w JG৚݅ࣜʹͯisSomething(v)Λ࢖ͬͨΒɺ 
 ͦͷJGϒϩοΫ಺Ͱ͸v͕Somethingܕͱͯ͠ѻ͑Δ

Slide 9

Slide 9 text

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; }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

JGจΛ࢖Θͳ͍ w "TTFSUJPOGVODUJPOTΛ࢖͏ 
 IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLSFMFBTFOPUFTUZQFTDSJQUIUNMBTTFSUJPOGVODUJPOT w function assertSomething( 
 v: unknown): asserts v is Something { ... 
 Έ͍ͨͳ΍ͭ w assertSomething(v)Λ࢖ͬͨΒɺ ͦͷؔ਺͕ྫ֎Λ౤͛ͳ͍ݶΓ 
 ಉҰείʔϓͷҎ߱ͷεςʔ τϝϯ τͰ͸v͕Somethingܕͱͯ͠ѻ͑Δ

Slide 12

Slide 12 text

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 }

Slide 13

Slide 13 text

΍ͬͺΓखؒ w asΛઈ໓ͤ͞ΔͨΊʹ͜͜·Ͱ͢Δ ʁ w ҰͭͷΦϒδΣΫ τʹ͍ͭͯݕূ͢Δ͚ͩͰؔ਺ଟ͗͢͡Όͳ͍ ʁ w ΋͏asΛ࢖ͬͯ΋͍͍Μ͡Όʜ ʁ w ઈରʹఘΊͳ͍

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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() ); } }

Slide 16

Slide 16 text

.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 ܕύϥϝʔλʹཧ૝ͷܕఆٛΛॻ͘

Slide 17

Slide 17 text

.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);

Slide 18

Slide 18 text

.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">'.

Slide 19

Slide 19 text

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 ͳ͚Ε͹Τϥʔࢀরʹه࿥ ϥϯλΠϜଆͷ࢓૊Έ

Slide 20

Slide 20 text

* { 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()

Slide 21

Slide 21 text

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 {

Slide 22

Slide 22 text

: 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ܕΛҰ୴͢΂ͯ 
 0ܕ ʢ਺஋Ϧςϥϧܕʣ ʹஔ͖׵͑Δ w PQUJPOBMͰ͋Ε͹ 
 ͢΂ͯundefinedʹ͢Δ w 0ܕʹஔ͖׵͓͔͑ͯͳ͍ͱ 
 PQUJPOBManyͱPQUJPOBMunknown͕ 
 ਖ਼͘͠ॲཧ͞Εͳ͍

Slide 23

Slide 23 text

: 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ͷΈআڈͰ͖Δ

Slide 24

Slide 24 text

ܕύζϧͷ൪ਓ 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

Slide 25

Slide 25 text

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()Λͨ͠ͷ͸ 
 ͜Εରࡦ

Slide 26

Slide 26 text

͜Ε͕࣮ݱͰ͖Δ 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">'.

Slide 27

Slide 27 text

.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);

Slide 28

Slide 28 text

$POEJUJPOBM5ZQFTΛۃΊΔͱָ͍͠ w $POEJUJPOBM5ZQFT͸࣮͸͔ͳΓͷදݱྗ͕͋Δ 
 IUUQTXXXUZQFTDSJQUMBOHPSHEPDTIBOECPPLDPOEJUJPOBMUZQFTIUNM w Ұݟݫͦ͠͏ͳܕਪ࿦Ͱ΋࣮͸ॻ͚ͨΓ͢Δ w $POEJUJPOBM5ZQFTΛۃΊΑ ͏ w as͸ېࢭͰ͖Δ

Slide 29

Slide 29 text

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