Skip to content

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 本体で管理されるようになった。

これは推測だが、おそらく移行の際に内部で使っている型定義しか移行しなかったのかなと思っている。(というよりできなかったのかもしれない)
実際、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

INFO

ジェネリクスがないじゃないか!となるが、それは この diff で追加される気配がある。

また、自分の会社のデザインシステムでは ThemedStyledProps を使っている箇所があるが、これは上記の ExecutionContext を使って一応再現することができる。
こんな感じのイメージ。

import { ExecutionContext } from "styled-components";

type ThemedStyledProps<P> = P & ExecutionContext;
1
2
3

型エラーが出ていた箇所もこれでなんとかなる。

対応状況

型定義の補充に関しては少しずつ進めている。

まだリリースはされてないが、対応状況としては

完了しているもの

  • 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

これはおそらく 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 を使用している箇所でエラーが出た。

具体的なコードは以下で、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

原因は自分の力では掴みきれなかったが、これは 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

その他

自分が困ってる箇所を中心に書いたので、他にも不具合はあると思う。

今後

現状、移行は厳しい状況なので一旦待機する。

しかし、どこかで解決するとは思う。自分の力で解決するのもあり。
自分のスコープに限った話だと react-select 以外の箇所はなんとかなりそうなので、そこだけウォッチしつつ時が来たら移行しようと思う。(焦るようなものでもないし)

目処がたったらまた移行メモを書くかもしれない。