useSpringsの型を直す
わけあって、react-springのCard Stack デモを typescript 化してみた、途中に謎のタイプエラーを遭遇して、それを解決するまでの推理を記事にした。
react-springのバーション: ^8.0.27
デモの中にこういうコードがあった。
set(i => {
if (index !== i) return
const isGone = gone.has(index)
const x = isGone ? (200 + window.innerWidth) * dir : down ? xDelta : 0
const rot = xDelta / 100 + (isGone ? dir * 10 * velocity : 0)
const scale = down ? 1.1 : 1
return {
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の返り値によって定義されている。
const [props, set] = useSprings(cards.length, i => ({
...to(i),
from: from(i),
}))
よって、useSpringsの返り値の型は合っていない。
返り値の型が合っていないのでuseSpringsの返り値の型定義を見に行くと、以下のコードになっていた。
export function useSprings<DS extends object>(
count: number,
getProps: (i: number) => UseSpringProps<DS>
): [AnimatedValue<ForwardedProps<DS>>[], SetUpdateCallbackFn<DS>]
エラーが出ているのは後半のset関数なので、前半のAnimatedValue<ForwardedProps<DS>>[]を置いといて、SetUpdateCallbackFn<DS>の型定義を見に行く。
export 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 => { ... })のように使えない。よって、正しい型は以下になる。
export interface SetUpdateCallbackFn<DS extends object> {
(cb: (i: number) => Partial<UseSpringProps<T>> | undefined): void
}
AnimatedValueの部分とまとめて、以下になる。
type useSpringsOverride<T extends Object> = [
AnimatedValue<T>[],
(cb: (i: number) => Partial<UseSpringProps<T>> | undefined) => void
]
これを使って type casting する
const [props, set] = useSprings(cards.length, i => ({
...to(i),
from: from(i),
})) as useSpringsOverride<DeckProps>
これでエラーが消えて、set(i => { ... }の部分にマウスをかざすと (parameter) i: numberが正しく表示されるようになった。
おまけ
この記事書いた時点で v9ブランチはマスターブランチと 400 Commits 以上の差分があるので、近いうちに大きなアプデートが来ると思う