Appearance
styled-components v6 移行の現在地と今後
2023-08-12
先日、styled-components v6 がリリースされた。
マイグレーションガイド
主な変更は
- TypeScript 化(
@types/styled-components
が不要になった) - shouldForwardProp がデフォルトで提供されなくなった
- vender prefix がデフォルトで提供されなくなった
$as
と$forwardedAs
の廃止withComponent
の廃止
あたり。
しかし、現状不具合が多数報告されており、中でも型定義ファイルの不足がかなり足枷になっているように感じる。
自分のプロジェクトでも少なからず不具合が出ている。
issue: https://github.com/voyagegroup/ingred-ui/issues/1349
また、自分が普段からウォッチしている styled-components を使った UI コンポーネントライブラリでも同様の不具合が発生しているのを見た。
現状
今の styled-components v6 は人によっては移行が厳しい状況である。
観測範囲の話になるが、現状わかっている・困っている点をまとめる。
型定義ファイルの不足
https://github.com/styled-components/styled-components/issues/4062
v6 になった styled-components は型定義ファイルが不足している。
styled-components の型定義ファイルは v5 までは DefinitelyTyped で管理されていたが、v6 からは styled-components 本体で管理されるようになった。
従来の型定義ファイル
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/styled-components/index.d.ts
これは推測だが、おそらく移行の際に内部で使っている型定義しか移行しなかったのかなと思っている。(というよりできなかったのかもしれない)
実際、DefinitelyTyped にあった型定義ファイルは膨大かつ複雑であり、これをメンテナンスしようとすると骨が折れるとは思う。
issue にもある通り、以下の型が不足している。
- CSSObject
- CSSProperties
- CSSPseudos
- CSSKeyframes
- FlattenInterpolation
- FlattenSimpleInterpolation
- InterpolationFunction
- SimpleInterpolation
- StyledProps
- ThemeProps
- ThemedStyledProps
- StyledComponentBase
- StyledComponent
この中では一部自前で定義可能なレベルのものであったり、既存の型を使って代用できるものもある。
例えば ThemeProps
はすでに提供されている ExecutionContext
とほぼ同じである。
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/styled-components/index.d.ts#L37-L39
export interface ThemeProps<T> {
theme: T;
}
// https://github.com/styled-components/styled-components/blob/e66ad6beef7a40288b9e3e6e230a93475964bcb8/packages/styled-components/src/types.ts#L75-L77
export interface ExecutionContext extends ExecutionProps {
theme: DefaultTheme;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
INFO
ジェネリクスがないじゃないか!となるが、それは この diff で追加される気配がある。
また、自分の会社のデザインシステムでは ThemedStyledProps
を使っている箇所があるが、これは上記の ExecutionContext
を使って一応再現することができる。
こんな感じのイメージ。
import { ExecutionContext } from "styled-components";
type ThemedStyledProps<P> = P & ExecutionContext;
1
2
3
2
3
型エラーが出ていた箇所もこれでなんとかなる。
対応状況
型定義の補充に関しては少しずつ進めている。
- https://github.com/styled-components/styled-components/pull/4117
- https://github.com/styled-components/styled-components/pull/4126
- https://github.com/styled-components/styled-components/pull/4127
まだリリースはされてないが、対応状況としては
完了しているもの
- CSSObject
- CSSProperties
- CSSPseudos
- CSSKeyframes
完了していないが取り込まれそうなもの
- AnyIfEmpty
- ThemeProps(
ExecutionContext
のエイリアスとして?) - ThemedStyledProps
- StyledProps
難しそうなもの
- FlattenInterpolation
- FlattenSimpleInterpolation
- InterpolationFunction
- SimpleInterpolation
- StyledComponentBase
といった具合になっている。 対応が難しそうなものは styled-components ではもちろん、自前で定義するのはかなり難しそうと感じる。というのも、依存する型が複雑すぎる。
これを解決するにはメンテナンスを頑張るか、型定義ファイルを 1 から書き直すかのどちらかになると思う。(またはマイグレーションガイドにこれらの型のサポートはしませんと追記するか)
react-select が使えない
https://github.com/styled-components/styled-components/issues/4074
これはランタイムエラーになるタイプのやつ。
ネットサーフィンをしていたらたまたま issue を見つけたので試したら再現した。
再現
spacing というトークンはデザインシステム側では使っていないので内部的な問題だと思われる。 https://github.com/voyagegroup/ingred-ui/issues/1349#issuecomment-1674211239
これに関しては今の自分の理解度ではよくわからないので、残念ながら何も対応ができないというのが正直なところ。
attrs のエラー
https://github.com/styled-components/styled-components/issues/4076
issue のコードを借りると、このような定義をした際に foo を定義しろという旨のエラーが出るというもの。
v5 まではこの挙動はエラーにならなかった。
const Test = styled.div.attrs<{ foo: number, bar: number }>({ foo: 1 })``
<Test bar={2} />
1
2
3
2
3
これはおそらく v5 でエラーになってなかったのがおかしいという話で、v6 になったことで型が厳密になり正常な挙動になったという話な気がする。
実際、以前プルリクエストでこのような 会話 がされていた。
「プロパティの不足」と「未定義のプロパティを渡す」ことは実際違うことだが、このコメントを見る限り厳密に扱いたいという意味では同じなのかもしれない。
INFO
実際、同じ状況のテストケースではプロパティをオプショナルにして対応している。
https://github.com/styled-components/styled-components/blob/b358494663e66a66ea62d72487ca38baeedc4325/packages/styled-components/src/test/types.tsx#L224-L236
これに関してはわかるんだけど、それが正の挙動として扱ってる人がそれなりにいることは想像ができるのでリリースノートなりマイグレーションガイドに書いておくべきだと思った。
型の不整合
これは issue にはないが、エラーメッセージは上の attrs
のものと同じ。型が合わないという旨のエラー。
自分の手元では react-transition-group を使用している箇所でエラーが出た。
- https://github.com/voyagegroup/ingred-ui/issues/1349#issuecomment-1667010855
- https://github.com/voyagegroup/ingred-ui/issues/1349#issuecomment-1667011397
具体的なコードは以下で、react-transition-group の CSSTransition を styled-components でラップしている箇所でエラーが出た。
import { CSSTransition as OriginalCSSTransition } from "react-transition-group";
export const CSSTransition = styled(OriginalCSSTransition)<CSSTransitionProps>`
/* styling */
`;
const Component: React.FunctionComponent<CSSTransitionProps> = ({
children,
...rest
}) => {
const nodeRef = React.useRef<HTMLDivElement>(null);
return (
// nodeRef でエラーが出る
<Styled.CSSTransition nodeRef={nodeRef} {...rest}>
<div ref={nodeRef}>{children}</div>
</Styled.CSSTransition>
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
原因は自分の力では掴みきれなかったが、これは nodeRef
の型が合わない旨のエラーなので Omit した後に再定義してあげることで一応型エラーは解消する。
import { CSSTransitionProps as OriginalCSSTransitionProps } from "react-transition-group/CSSTransition";
export type CSSTransitionProps = Partial<
Omit<OriginalCSSTransitionProps, "nodeRef">
> & {
nodeRef?: React.RefObject<HTMLElement>; // 不思議なことにこの定義はなくても tsc は通る、しかしコードジャンプはできなくなる
};
1
2
3
4
5
6
7
2
3
4
5
6
7
その他
自分が困ってる箇所を中心に書いたので、他にも不具合はあると思う。
今後
現状、移行は厳しい状況なので一旦待機する。
しかし、どこかで解決するとは思う。自分の力で解決するのもあり。
自分のスコープに限った話だと react-select 以外の箇所はなんとかなりそうなので、そこだけウォッチしつつ時が来たら移行しようと思う。(焦るようなものでもないし)
目処がたったらまた移行メモを書くかもしれない。