Skip to content

Instantly share code, notes, and snippets.

@wonderful-panda
Created February 22, 2018 10:10
Show Gist options
  • Save wonderful-panda/4f246b527cb643175d224bf974eb7f25 to your computer and use it in GitHub Desktop.
Save wonderful-panda/4f246b527cb643175d224bf974eb7f25 to your computer and use it in GitHub Desktop.
Conditional TypeでVuexのnamespacedモジュールを型安全にできないか考えてみるテスト
// モジュールをマークするためのマーカーインターフェイス
interface Module {
_module_: 1
}
// テスト用インターフェイス
interface Test {
foo: string;
bar: number;
baz: {
_module_: 1;
hoge: { // not module
a: string;
b: number;
};
fuga: {
_module_: 1;
piyo: string;
a: {
_module_: 1;
x: {};
},
b: { // not module
x: {};
}
}
}
}
// ValueOf<{ xxx: X, yyy: Y }> == X | Y
type ValueOf<T> = T[keyof T];
// PropPaths<Test> ==
// "foo" | "bar" | ["foo"] | ["bar"] | ["baz", "hoge"] |
// ["baz", "fuga", "piyo"] | ["baz", "fuga", "a", "x"] | ["baz", "fuga", "b"]
type PropPaths<
T,
K1 extends string = never,
K2 extends string = never,
K3 extends string = never,
K4 extends string = never
> = ValueOf<{
[K in Exclude<keyof T, "_module_">]: (
T[K] extends Module ? (
K1 extends never ? PropPaths<T[K], K> :
K2 extends never ? PropPaths<T[K], K1, K> :
K3 extends never ? PropPaths<T[K], K1, K2, K> :
K4 extends never ? PropPaths<T[K], K1, K2, K3, K> :
never
) : (
K1 extends never ? K | [K] :
K2 extends never ? [K1, K] :
K3 extends never ? [K1, K2, K] :
K4 extends never ? [K1, K2, K3, K] :
[K1, K2, K3, K4, K]
)
)
}>;
type PropTypeStrict<
T,
K1 extends keyof T,
K2 extends keyof T[K1] = never,
K3 extends keyof T[K1][K2] = never,
K4 extends keyof T[K1][K2][K3] = never,
K5 extends keyof T[K1][K2][K3][K4] = never
> = (
K2 extends never ? T[K1] :
K3 extends never ? T[K1][K2] :
K4 extends never ? T[K1][K2][K3] :
K5 extends never ? T[K1][K2][K3][K4] :
T[K1][K2][K3][K4][K5]
);
// PropTypeTuple<Test, "baz", "hoge"> == { a: string, b: number }
type PropType<T, K1, K2 = void, K3 = void, K4 = void, K5 = void> = (
K1 extends keyof T
? K2 extends keyof T[K1]
? K3 extends keyof T[K1][K2]
? K4 extends keyof T[K1][K2][K3]
? K5 extends keyof T[K1][K2][K3][K4]
? T[K1][K2][K3][K4][K5]
: K5 extends void ? T[K1][K2][K3][K4] : never
: K4 extends void ? T[K1][K2][K3] : never
: K3 extends void ? T[K1][K2] : never
: K2 extends void ? T[K1] : never
: never
);
// PropTypeTuple<Test, ["baz", "hoge"]> == { a: string, b: number }
type PropTypeTuple<T, Keys> = (
Keys extends string ? PropType<T, Keys> :
Keys extends [infer K1] ? PropType<T, K1> :
Keys extends [infer K1, infer K2] ? PropType<T, K1, K2> :
Keys extends [infer K1, infer K2, infer K3] ? PropType<T, K1, K2, K3> :
Keys extends [infer K1, infer K2, infer K3, infer K4] ? PropType<T, K1, K2, K3, K4> :
Keys extends [infer K1, infer K2, infer K3, infer K4, infer K5] ? PropType<T, K1, K2, K3, K4, K5> :
never
);
type X = PropPaths<Test>;
// "foo" | "bar" | ["foo"] | ["bar"] | ["baz", "hoge"] |
// ["baz", "fuga", "piyo"] | ["baz", "fuga", "a", "x"] | ["baz", "fuga", "b"]
/**
* ここから本題
*/
// dispatchメソッドの型
type Dispatch<T> = {
<X extends PropPaths<T>>(key: X, payload: PropTypeTuple<T, X>): void;
}
let dispatch: Dispatch<Test> = {} as any;
// OK
dispatch("foo", "test");
dispatch(["foo"], "test");
dispatch("bar", 1);
dispatch(["baz", "hoge"], { a: "1", b: 1 });
dispatch(["baz", "fuga", "b"], { x: { aaa: true } });
// NG
dispatch("fooo", "test"); // 存在しないpropname
dispatch("foo", 1); // 型の不一致
dispatch(["baz", "hoge"], { a: 0, b: 1 }); // 型の不一致
dispatch(["baz", "hage"], {}); // 存在しないproppath
dispatch(["baz", "fuga", "a"], { x: { aaa: true } }); // baz/huge/aは属性ではなくてモジュール
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment