わけあって、react-springのCard Stack デモを typescript 化してみた、途中に謎のタイプエラーを遭遇して、それを解決するまでの推理を記事にした。
react-springのバーション: ^8.0.27
デモの中にこういうコードがあった。
tsset(i => {if (index !== i) returnconst isGone = gone.has(index)const x = isGone ? (200 + window.innerWidth) * dir : down ? xDelta : 0const rot = xDelta / 100 + (isGone ? dir * 10 * velocity : 0)const scale = down ? 1.1 : 1return {x,rot,scale,delay: undefined,config: { friction: 50, tension: down ? 800 : isGone ? 200 : 500 },}})
そのままのタイプだとset(i => {の行が以下のエラーが出る
(parameter) i: any Parameter 'i' implicitly has an 'any' type.ts(7006) No overload matches this call. Overload 1 of 2, '(ds: Partial<Merge<{ from: DeckProps; x: number; y: number; scale: number; rot: number; delay?: number | undefined; } & UseSpringBaseProps, { from?: Partial<Pick<{ from: DeckProps; x: number; y: number; scale: number; rot: number; delay?: number | undefined; }, "x" | ... 2 more ... | "rot">> | undefined; onRest?(ds: Partial<...>): void; }>>): void', gave the following error. Type '(i: any) => { x: number; rot: number; scale: number; delay: undefined; config: { friction: number; tension: number; }; } | undefined' has no properties in common with type 'Partial<Merge<{ from: DeckProps; x: number; y: number; scale: number; rot: number; delay?: number | undefined; } & UseSpringBaseProps, { from?: Partial<Pick<{ from: DeckProps; x: number; y: number; scale: number; rot: number; delay?: number | undefined; }, "x" | ... 2 more ... | "rot">> | undefined; onRest?(ds: Part...'. Overload 2 of 2, '(i: number): Partial<Merge<{ from: DeckProps; x: number; y: number; scale: number; rot: number; delay?: number | undefined; } & UseSpringBaseProps, { from?: Partial<Pick<{ from: DeckProps; x: number; y: number; scale: number; rot: number; delay?: number | undefined; }, "x" | ... 2 more ... | "rot">> | undefined; onRest?(ds: Partial<...>): void; }>>', gave the following error. Argument of type '(i: any) => { x: number; rot: number; scale: number; delay: undefined; config: { friction: number; tension: number; }; } | undefined' is not assignable to parameter of type 'number'.ts(2769)
これは、set(i => {})の引数の型が間違っているエラーである。関数を渡すはずなのに、何故かnumberしか渡せなくてエラーが出ている。
エラー結果から、set関数自体の型が間違えている可能性が高い。更に追跡するとsetはuseSpringsの返り値によって定義されている。
tsconst [props, set] = useSprings(cards.length, i => ({...to(i),from: from(i),}))
よって、useSpringsの返り値の型は合っていない。
返り値の型が合っていないのでuseSpringsの返り値の型定義を見に行くと、以下のコードになっていた。
tsexport function useSprings<DS extends object>(count: number,getProps: (i: number) => UseSpringProps<DS>): [AnimatedValue<ForwardedProps<DS>>[], SetUpdateCallbackFn<DS>]
エラーが出ているのは後半のset関数なので、前半のAnimatedValue<ForwardedProps<DS>>[]を置いといて、SetUpdateCallbackFn<DS>の型定義を見に行く。
tsexport interface SetUpdateCallbackFn<DS extends object> {(ds: Partial<UseSpringProps<DS>>): void(i: number): Partial<UseSpringProps<DS>>}
よく見ると、SetUpdateCallbackFnの定義はどれにも当てはまらず、下のほうの定義が近いが、(i: number): Partial<UseSpringProps<DS>>のように定義するとset関数の第一引数はnumberになっているので、set(i => { ... })のように使えない。よって、正しい型は以下になる。
tsexport interface SetUpdateCallbackFn<DS extends object> {(cb: (i: number) => Partial<UseSpringProps<T>> | undefined): void}
AnimatedValueの部分とまとめて、以下になる。
tstype useSpringsOverride<T extends Object> = [AnimatedValue<T>[],(cb: (i: number) => Partial<UseSpringProps<T>> | undefined) => void]
これを使って type casting する
tsconst [props, set] = useSprings(cards.length, i => ({...to(i),from: from(i),})) as useSpringsOverride<DeckProps>
これでエラーが消えて、set(i => { ... }の部分にマウスをかざすと (parameter) i: numberが正しく表示されるようになった。
おまけ
この記事書いた時点で v9ブランチはマスターブランチと 400 Commits 以上の差分があるので、近いうちに大きなアプデートが来ると思う