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