ReactN+TypeScript
ReactNをTypeScriptで使うときにArgument of type '"a"' is not assignable to parameter of type 'never'.が出たのを、マニュアル読まずにフィーリングで直したら、実は直ってなかったので、正しい直し方をメモ。 code:ts
import { useGlobal, setGlobal } from 'reactn';
const INITIAL_STATE = {
a: 1,
b: "foo",
}
type TState = typeof INITIAL_STATE;
//type TState = {a: number; b: string;}
setGlobal(INITIAL_STATE);
const App: React.FC = () => {
const a = useGlobal("a");
// ERROR: Argument of type '"a"' is not assignable to parameter of type 'never'.
...
useGlobalに型指定を付けたらコンパイルエラーがなくなったのでそれで良いのかと思ったら、型がおかしくなっていて後から問題が起きた。
code:ts
const a = useGlobal<TState>("a"); // no compile error, it seems OK but...
// const a: string | number
// So,
console.log(a + 1);
// Operator '+' cannot be applied to types 'ReactText' and 'number'.
code:ts
declare module 'reactn/default' {
export interface State extends TState { }
}
const App: React.FC = () => {
const a = useGlobal("a"); // OK, no generics needed console.log(a + 1); // OK
the reason why a: string | number
code:ts
useGlobal<TState>("a");
// return value: StateTuple<{
// a: number;
// b: string;
// }, "a" | "b">
code:ts
useGlobal<G extends {} = State, Property extends keyof G = keyof G>(property: Property): StateTuple<G, Property>;
export type StateTuple<G extends {}, P extends keyof G> = [
Setter<G, P>,
];
この原因は型引数を2つとるgenericsに1つ指定するともう1つの型がかわることが原因。
型引数を指定しない場合は引数から型Keyが推論され、それは文字列のリテラルタイプになる。
一方Dictに型を渡した場合、 KeyはDictのkeyofになる。この場合、すべてのキーのユニオン型になる。
code:ts
interface DefaultDict {
a: number,
b: string,
}
const dict: DefaultDict = {
a: 1,
b: "foo"
}
const bar: <
Dict extends {} = DefaultDict,
Key extends keyof Dict = keyof Dict
(key: Key) => Key
= (key) => key;
const a1 = bar("a");
// const a1: "a"
const a2 = bar<typeof dict>("a");
// const a2: "a" | "b"
const a3 = bar<DefaultDict>("a");
// const a3: "a" | "b"
StateTuple[0]のG[P]でPがすべてのキーのユニオン型なので、G[P]はすべてのバリューのユニオン型になる
code:ts
export type StateTuple<G extends {}, P extends keyof G> = [
Setter<G, P>,
];