styled-componentsにはv4からcss propという機能があり、これを用いるとちょっとしたスタイルの修正を、コンポーネントの作成無しで実施できる。
しかしこのcss prop, どうも一筋縄では動かないらしい。特にTypeScript環境では以下のようにハマる人も多いようだ。
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
https://github.com/styled-components/styled-components/issues/2528
私もこの点で大ハマりし半日を潰す羽目になった。
ネット上の記事には断片的な情報しか無く、同じハマり方をする人もいると思うので、備忘録も兼ねて記録を残す。
私の知識が足りないだけなのだと思うが、この件ではネットの記事を見ながら「せめて何処に書くか位ちゃんと書けや!!!」とか「ここまで書いてあってなんであの情報ないんだよ!(バージョン差分の影響かもしれない)」と感じたことが多々あったので、本記事ではチュートリアル形式で、可能な限り再現しやすい形をとる。
具体的にはcreate-react-appでReactアプリを作成するところから、styled-componentsをインストールし、css propの機能によりスタイルを適用するまでの手順を示す。同じ悩みにぶち当たった人の助けになれば。
前提
本チュートリアルは以下の環境を前提とする
- react: 16.13.0
- create-react-app: 3.3.0
- styled-components: 5.0.1
おそらくこの手順は "create-react-app"を用いた環境でのみ有効。
css prop動作確認手順
create-react-app
--typescript
オプションを使用する
$ create-react-app trycssprop --typescript
styled-componentのインストール
$ yarn add styled-components
型定義ファイルも同時にインストールする
$ yarn add @types/styled-components
yarn start でアプリが正常にビルドされることを確認
$ yarn start
styled-componentの基本動作確認
スタイルを付加したコンポーネントを作成する。
diff --git a/src/App.tsx b/src/App.tsx
index a53698a..df0b1c0 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,11 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
+import styled from 'styled-components'
+
+const Test = styled.div`
+ color: red;
+`
function App() {
return (
@@ -18,6 +23,7 @@ function App() {
>
Learn React
</a>
+ <Test> TEST!!!!!! </Test>
</header>
</div>
);
ここからが本題! css propを用いてスタイルを当てる
まず何もせずにcss propを使ってみる。任意のコンポーネントにcss="**some style**"
の形でpropを設定することで、スタイルを適用できる。
https://styled-components.com/docs/api#css-prop
diff --git a/src/App.tsx b/src/App.tsx
index df0b1c0..f057b5c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -23,7 +23,7 @@ function App() {
>
Learn React
</a>
- <Test> TEST!!!!!! </Test>
+ <Test css="border-bottom: solid 5px white"> TEST!!!!!! </Test>
</header>
</div>
);
ビルドすると以下のようなエラーが表示される。
これはcss="**some style**"
がJSXの拡張であり、現状の設定ではBabelが読み取れないことに起因する(らしい)。
Failed to compile.
trycssprop/src/App.tsx
TypeScript error in trycssprop/src/App.tsx(26,15):
No overload matches this call.
Overload 1 of 2, '(props: Pick<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style" | "title" | ... 252 more ... | "onTransitionEndCapture"> & { ...; }, "style" | ... 254 more ... | "onTransitionEndCapture"> & Partial<...>, "style" | ... 254 more ... | "onTransitionEndCapture"> & { ...; } & { ...; }): ReactElement<...>', gave the following error.
Type '{ children: string; css: string; }' is not assignable to type 'IntrinsicAttributes & Pick<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style" | ... 253 more ... | "onTransitionEndCapture"> & { ...; }, "style" | ... 254 more ... | "onTransitionEndCapture"> & Partial<...>, "style" | ... 254 more ... | "onTransitionEndCapture"> & { ...; } & { ...; }'.
Property 'css' does not exist on type 'IntrinsicAttributes & Pick<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style" | ... 253 more ... | "onTransitionEndCapture"> & { ...; }, "style" | ... 254 more ... | "onTransitionEndCapture"> & Partial<...>, "style" | ... 254 more ... | "onTransitionEndCapture"> & { ...; } & { ...; }'.
Overload 2 of 2, '(props: StyledComponentPropsWithAs<"div", any, {}, never>): ReactElement<StyledComponentPropsWithAs<"div", any, {}, never>, string | ... 1 more ... | (new (props: any) => Component<...>)>', gave the following error.
Type '{ children: string; css: string; }' is not assignable to type 'IntrinsicAttributes & Pick<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style" | ... 253 more ... | "onTransitionEndCapture"> & { ...; }, "style" | ... 254 more ... | "onTransitionEndCapture"> & Partial<...>, "style" | ... 254 more ... | "onTransitionEndCapture"> & { ...; } & { ...; }'.
Property 'css' does not exist on type 'IntrinsicAttributes & Pick<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style" | ... 253 more ... | "onTransitionEndCapture"> & { ...; }, "style" | ... 254 more ... | "onTransitionEndCapture"> & Partial<...>, "style" | ... 254 more ... | "onTransitionEndCapture"> & { ...; } & { ...; }'. TS2769
24 | Learn React
25 | </a>
> 26 | <Test css="border-bottom: solid 5px white"> TEST!!!!!! </Test>
| ^
27 | </header>
28 | </div>
29 | );
型定義ファイルへのimportの追加
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
の記載を参照して、
import {} from 'styled-components/cssprop'
を型定義ファイルに追記する。
なぜ型定義ファイルに上記のimportを追記するのか? これに関しては大変申し訳ないのだが、私自身理解していない。
試行錯誤の結果、型定義ファイルに記載すれば良さそうということだけ分かった。
この辺りが気になる方は、「styled-components css props typescript」あたりでググってみてほしい。
いくつかの記事から、この推論にたどり着くことがなんとなく理解いただけると思う。
もしくはこの理由をご存知の方は、是非ともコメントをいただきたい。
当初'node_modules/@types/react/index.d.ts'に追記していたが、
gitで管理する必要等も考えると 独自に型定義ファイルを作成するのが良さそうだ。
私は以下を参考に作成した。
https://qiita.com/mtgto/items/e30d1529ca298e49557e
具体的には、
- types/cssprop/index.d.tsを作成
- 上述の
import {} from 'styled-components/cssprop'
を追記 - tsconfigに独自作成の型定義ファイルの場所を指定
を実施した。具体的なコードは以下の通り。
これでひとまずビルドは通るようになる。
diff --git a/src/types/cssprop/index.d.ts b/src/types/cssprop/index.d.ts
new file mode 100644
index 0000000..02edd53
--- /dev/null
+++ b/src/types/cssprop/index.d.ts
@@ -0,0 +1 @@
+import {} from 'styled-components/cssprop'
diff --git a/tsconfig.json b/tsconfig.json
index f2850b7..e335802 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,7 +17,12 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "react"
+ "jsx": "react",
+ "baseUrl": "./",
+ "typeRoots": [
+ "types",
+ "node_modules/@types"
+ ]
},
"include": [
"src"
利用する'styled'関数の変更
ひとまずbuildは通るようになった。が、"border-bottom"が適用されていない。つまりcss propで設定したスタイルはまだ適用されていない。
<Test css="border-bottom: solid 5px white"> TEST!!!!!! </Test>
ここで再度公式ドキュメントに戻ると以下の記載が確認できる。
You can use the Babel macro to make this work in create-react-app. Unfortunately, Babel macros only run when imported so the import can not be added automatically. The above code works perfectly if you add the import to the macro manually:
// 公式ドキュメントより抜粋
import styled from 'styled-components/macro'
<div
css={`
background: papayawhip;
color: ${props => props.theme.colors.text};
`}
/>
<Button
css="padding: 0.5em 1em;"
/>
どうも create-react-app環境では、Babel macroを利用することが出来、その際はimport styled from 'styled-components/macro'
を用いる必要があるようだ。この記載にしたがってimportを修正する。
diff --git a/src/App.tsx b/src/App.tsx
index f057b5c..a813609 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
-import styled from 'styled-components'
+import styled from 'styled-components/macro'
const Test = styled.div`
color: red;
これで期待通りにcss propによるスタイルが適用されるようになった。
さいごに
この件は半日で解消はしたものの、不要に苦しんだという印象が強い。本記事で同じ苦しみを味わう人が少しでも減ればうれしい。