Reactの続きです。前回はstyled-componentsのcreateGlobalStyleでbodyタグにCSS設定をしました。今回はcss helperでスタイルを継承します。前回一回リセットされたCSSスタイルは今回復活できました♪Sass(SCSS)との比較も行います。それではいきましょう!
【目次】
- 前回のおさらい
- 既存コンポーネントにスタイル設定(エラー)
- Headerコンポーネント内でスタイルを設定してみる(エラー)
- 関数コンポーネントと別名のコンポーネントにスタイルを当てる(成功!)
- mixin設定の外部ファイルを作る
- css helperで継承スタイルを設定
- pageSize設定を継承
- Headerコンポーネントのスタイルを仕上げる
- Mainコンポーネントのスタイルを設定
- Innerコンポーネントのスタイル設定
- Footerコンポーネントのスタイル設定
- ファイル、フォルダを整理
- 最後に
※参考:前回記事
【React】styled-componentsのcreateGlobalStyleでbodyタグにCSS設定(Reactとメタ言語の比較-4) - クモのようにコツコツと
※参考:Reactを習得するためにやったことまとめ
qiita.com
前回のおさらい
styled-componentsのcreateGlobalStyleでbodyタグにCSS設定
いったんCSSスタイルはすべてリセットしたが、コンポーネントの外のbodyタグにスタイルを設定することはできた。
※参考:【React】styled-componentsのcreateGlobalStyleでbodyタグにCSS設定(Reactとメタ言語の比較-4) - クモのようにコツコツと
既存コンポーネントにスタイル設定(エラー)
次はいよいよコンポーネントにスタイルを当ててみる!
まず、「App.tsx」にstyled-componentsをインポート
import styled from 'styled-components';
変数Header
にHeaderコンポーネントのスタイルを設定
const Header = styled.header` background: "#eee"; `;
そうすると下記のHeader
コンポーネントにスタイルが当たるはず
function App() { return ( <div className="App"> <Helmet title={ titleText } meta={[ { name: 'description', content: descriptionText } ]} /> <Header /> <Main /> <Footer /> </div> ); }
アプリ起動
$ npm start
ありゃ、こんなエラーが…
./src/App.tsx SyntaxError: /(パス)/react-from-meta-lang/src/App.tsx: Identifier 'Header' has already been declared (12:6)
「識別子Header
はすでに宣言されています」と。
確かにHeader
は「Header.tsx」の中でも関数コンポーネントとして設定している。
function Header() { return ( <header className="header"> <h1 className="header__title">{ title }</h1> <p className="header__text" dangerouslySetInnerHTML={{ __html: text }}></p> </header> ); }
これと重複するということか。
Headerコンポーネント内でスタイルを設定してみる(エラー)
同じことをHeader
コンポーネント内でやってみる(ほぼダメ元、確認作業w)。
「Header.tsx」でstyled-components
をインポート
import styled from 'styled-components';
Header
スタイルを設定
const Header = styled.header`
background: #eee;
`;
結果は同じエラー
./src/Header.tsx SyntaxError: /(パス)/react-from-meta-lang/src/Header.tsx: Identifier 'Header' has already been declared (12:9)
でしょうねw
同じファイルでもコンポーネントと名前が被っているのは変わらないからな。
function Header() { return ( <header className="header"> <h1 className="header__title">{ title }</h1> <p className="header__text" dangerouslySetInnerHTML={{ __html: text }}></p> </header> ); }
なお、headerタグをHeader
コンポーネント化してみても結果は同じエラーだった
function Header() { return ( <Header> <h1 className="header__title">{ title }</h1> <p className="header__text" dangerouslySetInnerHTML={{ __html: text }}></p> </Header> ); }
関数コンポーネントと別名のコンポーネントにスタイルを当てる(成功!)
コンポーネントの名前被り問題、いやー、どうしたものか。わざわざstyled-componentsのために別名のコンポーネントを当てるのかな?例えばこんな感じに?
function App() { return ( <div className="App"> <Helmet title={ titleText } meta={[ { name: 'description', content: descriptionText } ]} /> <HeaderStyle> <Header /> </HeaderStyle> <Main /> <Footer /> </div> ); }
それってあまりエレガントではないな…。
いや、まてよ、そもそもこの<Header />
ってただ配置しているだけで、実際のスタイルをここに当てたいわけではないよな?
実際にスタイルを当てたいのはJSXの中のheaderタグなんだよな。
function Header() { return ( <header> <h1 className="header__title">{ title }</h1> <p className="header__text" dangerouslySetInnerHTML={{ __html: text }}></p> </header> ); }
そしたらこのheaderタグをHeader
とは別名のコンポーネントにすればいいのでは?
スタイル設定を変数HeaderTag
に変更してみる。
const HeaderTag = styled.header`
background: #eee;
`;
headerタグもHeaderTag
コンポーネントにしてみる。
function Header() { return ( <HeaderTag> <h1 className="header__title">{ title }</h1> <p className="header__text" dangerouslySetInnerHTML={{ __html: text }}></p> </HeaderTag> ); }
ブラウザを見ると、おお、スタイルに当たった!
「HeaderTag」コンポーネントはheaderタグになってランダムなclass名.kYPuzK
が当たっている!
よし、このやり方でいくか!
mixin設定の外部ファイルを作る
前回のCSS変数設定を「css-variables.json」という別ファイルで作った。コンポーネントを跨いで使いたかったので。mixin設定も同様なので別ファイルにしたい。
まず「style」フォルダを作って「css-variables.json」をその中に入れる。ファイル名は「variables.json」に改名。このファイルのインポート部分のパスも修正した。
修正前
import cssVariables from './css-variables.json';
修正後
import cssVariables from './style/variables.json';
次にこの「style」フォルダの中に「mixin.ts」を作成。
なお、JSXコンポーネントがないファイルの拡張子は「.tsx」ではなく「.ts」でいくことにした。
※参考:TypeScript - Reactを使うプロジェクト内のTypeScriptをすべて.tsxで作るデメリットについて|teratail
メタ言語スターターキットのSass(SCSS)では「_mixin.scss」にmixinを設定していた。
@mixin pageSize($width: 1000px) { width: 100%; max-width: $width; padding: 20px; margin: 0 auto; }
このスタイルをモジュールを超えて継承させてた。
※参考:【メタ言語】フロントエンド開発スターターキットを作った(EJS、Sass(SCSS)、TypeScript) - クモのようにコツコツと
css helperで継承スタイルを設定
styled-componentsのcss helper関数を使うとスタイルの継承ができる。
※参考:styled-components: API Reference
※参考:styled-components の mixin としての使い方が便利そう - Qiita
「mixin.ts」でstyled-components
のcss
機能をインポート
import styled, { css } from 'styled-components';
変数pageSize
でcss
を設定。タグは指定不要!
const pageSize = css`
width: 100%;
max-width: 1000px;
padding: 20px;
margin: 0 auto;
`;
pageSize
をエクスポート
export {pageSize};
{}
でくくると複数の変数や関数をエクスポートできる(今はpageSize
だけしか設定していないが、のちのちmixin全体はこの中にまとめる想定)。
※参考:ファイルモジュールの詳細 - TypeScript Deep Dive 日本語版
※参考:逆引きstyled-components 共通コンポーネントをうまく作る5つの小技 - mottox2 blog
pageSize設定を継承
「Header.tsx」で「mixin.ts」の中からpageSize
をインポートする
import { pageSize } from './style/mixin';
HeaderTag
でpageSize
を読み込む
const HeaderTag = styled.header` ${pageSize} background: #eee; `;
前回の変数と同様、継承でもプレースホルダー${ }
を使う。
ブラウザ確認 おお!Headerだけmixinのスタイルが当たった!
Headerコンポーネントのスタイルを仕上げる
SCSS(Sass)の「_header.scss」は下記のようなスタイル設定だった
/* header */ .header { @include pageSize(); text-align: center; background: $bg-color_g; padding: 20px; &__title { font-size: 2em; } }
背景色以外の設定も追加していく。
変数を読み込むので前回と同様に「variables.json」をインポート
import cssVariables from './style/variables.json'; const variable = cssVariables.variable;
HeaderTag
にスタイル追加、プレースホルダーで変数bgColor_g
も読み込む。
const HeaderTag = styled.header` ${pageSize} text-align: center; background: ${variable.bgColor_g}; padding: 20px; & h2 { font-size: 2em; } `;
Sass(SCSS)の時に命名したBEMのclass名を無くしてみる。
function Header() { return ( <HeaderTag> <h2>{ title }</h2> <p dangerouslySetInnerHTML={{ __html: text }}></p> </HeaderTag> ); }
ブラウザ確認 ヘッダーのスタイル完成!
h2やpタグにはclass名はないが…
headerタグのランダムなclass名.dacmuK
のスコープになっている!
Mainコンポーネントのスタイルを設定
次は「Main.tsx」を修正する。
修正前のMain
コンポーネントのJSX
function Main() { return ( <main> <section className="main"> <h1 className="main__title">{ title }</h1> <p className="main__title" dangerouslySetInnerHTML={{ __html: text }}></p> <Inner /> </section> </main> ); }
SCSS(Sass)の「_main.scss」はこのような設定だった
.main { @include pageSize(); &__title { font-size: 1.5em; } }
ここでもpageSize
を継承している。
styled-components
とpageSize
をインポート
import styled from 'styled-components'; import { pageSize } from './style/mixin';
SectionTag
でのスタイル設定、pageSize
を継承。
const SectionTag = styled.section` ${pageSize} & h1 { font-size: 1.5em; } `;
変更後のMain
コンポーネント
function Main() { return ( <main> <SectionTag> <h1>{ title }</h1> <p dangerouslySetInnerHTML={{ __html: text }}></p> <Inner /> </SectionTag> </main> ); }
sectionタグをSectionTag
コンポーネントにして、h1タグ、pタグはBEMのclass名を削除。
ブラウザを確認 やった、本文にもスタイルが当たった!
sectionタグにランダムなclass名.fjwMFH
があたって…
h1タグがスコープになっている♪
Innerコンポーネントのスタイル設定
次は「Inner.tsx」。以前、本文の中でmap()
でループしたsectionタグ部分。
※参考:【React】JSONデータをJSXに読み込んで表示する(Reactとメタ言語の比較-3) - クモのようにコツコツと
変更前のInner
コンポーネントのJSX
function Inner() { return ( <div className="inner"> {innerJson.map((innerJson, index) => <section key={ index }> <h2 className="inner__title">{ innerJson.title }</h2> <p className="inner__text" dangerouslySetInnerHTML={{ __html: innerJson.text }}></p> </section> )} </div> ); }
Sass(SCSS)の「_inner.scss」
.inner { &__title { font-size: 1.25em; color: $base-color; } }
今回は変数設定のみか。
styled-components
とcssVariables
をインポート
import styled from 'styled-components'; import cssVariables from './style/variables.json'; const variable = cssVariables.variable;
SectionTag
でsectionタグのスタイル設定、変数baseColor
も読み込み。
const SectionTag = styled.section` & h2 { font-size: 1.25em; color: ${variable.baseColor}; } `;
スタイルはh2タグのみだが、今後の拡張を考慮して外側のsectionタグをコンポーネント化した。
変更後のJSX。sectionタグをSectionTag
コンポーネントにしてBEMのclass名は削除。
function Inner() { return ( <div className="inner"> {innerJson.map((innerJson, index) => <SectionTag key={ index }> <h2>{ innerJson.title }</h2> <p dangerouslySetInnerHTML={{ __html: innerJson.text }}></p> </SectionTag> )} </div> ); }
ブラウザ確認
Inner
コンポーネントにスタイルが当たった!
sectionタグにランダムなclass名.iGTxTz
が当たっている
h2タグが.iGTxTz
のスコープになっている
Footerコンポーネントのスタイル設定
最後、「Footer.tsx」コンポーネントにスタイルを設定する。
変更前のFooter
コンポーネントのJSX
function Footer() { return ( <footer className="footer"> <p className="footer__text" dangerouslySetInnerHTML={{ __html: text }}></p> </footer> ); }
Sass(SCSS)の「_footer.scss」
.footer { @include pageSize(); text-align: center; }
mixinでpageSize
を読み込んでいる。変数はなし。
styled-components
とpageSize
をインポート
import styled from 'styled-components'; import { pageSize } from './style/mixin';
FooterTag
でfooterタグのスタイル設定。pageSize
を継承。
const FooterTag = styled.footer` ${pageSize} text-align: center; `;
変更後のJSX、footerタグをFooterTag
コンポーネントに。
function Footer() { return ( <FooterTag> <p dangerouslySetInnerHTML={{ __html: text }}></p> </FooterTag> ); }
ブラウザ確認 よっしゃ!フッターもCSS復活〜♪
footerタグにランダムなclass名.TbtuH
が当たっている
.TbtuH
にはpageSize
とFooterTag
のスタイルが当たっている
CSS設定は別ファイルではなく、ページ内のheadタグの中にあるようだった。
ファイル、フォルダを整理
ファイルがだいぶ増えてきたので整理したい。
まず「data」フォルダを作成し「data.json」をこの中に。インポートのパスも変更。
import Data from './data/data.json';
「modules/hello」フォルダを作り、「hello.js」をその中に入れる。インポートにパスも変更。
import './modules/hello/hello';
元々あった「App.css」と「index.css」はもう使わないので削除しちゃう。
コンポーネント以外のファイルがフォルダの中に格納された。
画面確認、問題ナシ!
メタ言語スターターキットではEJSと
Sass(SCSS)がほぼ同じ構成だった
※参考:【メタ言語】フロントエンド開発スターターキットを作った(EJS、Sass(SCSS)、TypeScript) - クモのようにコツコツと
Reactではstyled-componentsによってCSSがコンポーネントと一体化した。
ファイル構成がシンプルになった!
ソース(GitHub)
※参考:GitHub - ryo-i/react-from-meta-lang at 18ecb014d211d835aa99c8bfc26118df985c8462
プレビュー(GitHub Pages)
※参考:Reactとメタ言語の比較
最後に
ということで、styled-componentsによって無事にCSS設定が復活しましたー。変数や継承によってコンポーネントを超えた共通の値を設定することができました。
ファイル構成はEJSとSass(SCSS)の時はほぼ同じモジュール構成が二重に設定されており、モジュール構成を変えるたびに二箇所を変更する必要がありました。styled-componentsを使うとJSXとCSS in JSが同じファイルに設定できるので、ファイル構成はシンプルになりました!
また、styled-componentsがつけるランダムなclass名がスコープになるのでBEM記法のclass名は不要になりました。CSSの設定自体はSass(SCSS)とほぼ同じ書き方ができました。
CSS編はここまで。次回は「こんにちは、ふろんとえんど」というメッセージを表示しているJS部分をTypeScriptのモジュール化していきます。
それではまた!
※参考:Reactを習得するためにやったことまとめ
qiita.com