20210607のReactに関する記事は18件です。

それでも私がTailwind CSSではなく、CSS Modulesを推す理由

色々書き比べた結果Tailwind CSSにしたという話 こちらの記事がバズっていた(6/9現在 over 200likes)為、読ませて頂きました。 これまで主観的な印象と薄い議論で賛否が分かれていたTailwind CSSについてこれまでのcssの技術の変遷を踏まえて技術的にかなり踏み込まれた考察の上で選定の理由が書かれており、Tailwind CSSアンチ派の私にとっても非常に勉強になる記事でありました。リスペクト。 その上で、こちらの記事では私が『それでもCSS Modulesを推す理由』について書かせていただきたいと思います。 *2021 6/9追記 「Tailwind CSSこそCSSのあるべき姿でありフロントエンドの未来である」というフロントエンド界隈の感情が「急激に」高まりつつある現状に対しての一つの問題提起となれば幸いです。 Next.js,Gatsby などのフレームワークでBuild-inサポートされている CSS Modulesは上述のフレームワークなどを用いて開発をスタートした際、デフォルトでサポートされている為、追加のライブラリなどは不要です(とはいえsassなどはほとんどの場合追加すると思います)。 ちなみにNext.jsの開発元のVercelはstyled-jsxというCSS in JSライブラリーを管理しており、こちらもNext.jsではbuild-inでサポートされています。 とはいえ、Next.jsの公式サイトなどを見る限りあくまで公式の推しはCSS Modulesっぽいです。 CSS in JSと CSS Modulesの比較になりますが、CSS Modulesの方が、『ロードタイム・ランタイムともに、パフォーマンス面で有利』なようです。ソース ピュアなcssの知識さえあれば設定不要で書き始めることができるCSS Modulesですが、これ自体がTailwind CSSに対するCSS Modulesの一つのメリットとなります。 CSS Modulesを書くのに当たっては、Tailwind CSSのコンフィグ方法や、クラス名などを覚える必要がありません。また、Tailwind CSSそのものが今後進化していった場合などに、それらの変更に追従する保守コストもかかりません。 冒頭で紹介した記事では、webpack依存技術である点や、『ファイルが増えてめんどくさい』点についてCSS Modulesのデメリットとして述べられています。 しかし、webpack依存に関しては現代的なフロントエンド環境の95%以上がwebpackを使用している現状などから考えてそれほど憂慮することでもないのかなと思っています。 また、次世代のbuildツールとして話題になりつつあるViteなどでもCSS Modulesはサポートされているようです。 .BemTailwindBlock__Inner { @apply lg:flex w-full; } また、セマンティックなクラス名(上のコードではBEM命名方)とTailwind CSSを併用する例として、@applyを使用した例を載せていただいておりますが、@applyはTailwind CSSを使用しているときのみ機能します。ビルドプロセスからTailwind CSSを削除すると、そのCSSは機能せず、レイアウトは壊れてしまいます。つまり、@applyメソッドなどでセマンティックな命名をユーティリティクラスと使い分けたとしても、cssはtailwind依存になってしまうので、仮に将来cssフレームワークの交換などが発生してしまった場合、多大な作業コストがかかると思われます。 『ファイルが増えてめんどくさい』点に関してですが、これは『めんどくさい』です。CSS Modulesは一々CSSファイルをつくらないといけないという『めんどくささ』から逃れることは出来ません。 『状態管理とレンダリングロジック』と『スタイル』 の責務をそれぞれのファイルに分割するというアーキテクチャは、『ファイル分割のめんどくささ』とトレードオフになります。 しかし、個人的にですがjsxファイルは出来るだけ、『state,propsのデータの流れとレンダリングロジック』に集中できる見通しのいい状態が好ましいと考えています。 <button type="button" className=" relative py-2 w-1/2 sm:w-auto sm:px-8 text-gray-900 bg-white text-sm font-medium whitespace-nowrap border-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:z-10 " > このようなTailwindのクラス名で数行視界が奪われてしまうのは、私は絶対に避けたいと思っています。 Tailwindはコードのクリーンさと引き換えに、初速と最低限のデザインシステムを保障しますが、それは保守性の悪化というコストを将来に残すリスクを孕んでいることは選定の際に、考慮しなければならないと思います。 CSSファイルを増やさなければならないめんどくささと、jsxがtailwindのclass名に圧せられるツラミを天秤にかけると私はCSSファイルへの分割を選びます。 また、Tailwind CSSでユーティリティクラスをバンバン生やして快速にコーディングを進めていても、background-imageの画像パスを指定したり、::beforeや::afterなどの擬似要素を使用したくなったりと、ピンポイントでcssを書かざるを得ない場面は出てくるのではないでしょうか?そのときは諦めてglobalにcssを書きますか?必要になったときだけCSSファイルをつくるというのも行き当たりばったり的でアーキテクチャとしては弱い感じがします。 モーダルなど、classの付け替えで見た目が大きく変更するuiなどcssの記述量が多くなるコンポーネントについてもTailwind CSSのユーティリティクラスだけでの対処はしんどくなりそうなので、例外的な処理をしたくなるのではないでしょうか。Tailwindを使っていると、『Tailwindだけではしんどい場面』の回避方が必要になり、その度にハック的な処理が増えていくのではないでしょうか。 css modulesだとそういう場面的な対処は必要なく、一気通貫したアプローチが可能です。 Tailwindで出来ることはCSSで出来る(当たり前 Tailwind CSS好きな人が挙げるメリットとして、あらかじめ汎用クラスでサイズなどが設定されているので、クラスをどんどん当て込んでいったらある程度、統一性のあるデザインにまとまるというのがあるのではないでしょうか? Tailwind CSSのfont-sizeに関係するクラスの中身は例えば、このようなものになります。 .text-xs { font-size: .75rem; } .text-sm { font-size: .875rem; } .text-base { font-size: 1rem; } これは、SCSSなどで変数として定義して、要素に入れると次のように書けます $text-xs = .75em; $text-sm = .875em; .object-text{ font-size:$text-xs; } あるいは、Tailwind CSSのフォントサイズ周りのユーティリティクラスが便利ならそれだけコピーしてユーティリティクラスとしてそのまま使うのもアリです。ブレイクポイント、色、マージンなども同様です。変数・mixinだけのimport専用の共通cssファイルをうまく設計すれば、デザインシステムはTailwindを使わなくてもつくれます。 まぁ当たり前っちゃ当たり前なんですが、Tailwind CSSで出来ることはCSSで出来ます。(Ruby on Railsで出来ることはRubyで出来るくらい意味のない文章かもしれません) Tailwind CSSが流行した背景を考える しかし、これだけTailwind CSSが盛り上がってるのには何かそれ以前のReactなどのコンポーネントによるUI設計とCSSの設計によくある落とし穴というか問題があったのではと考えます。 私が推測するのは、『CSSクラスによるスタイルの過度の共通化』です。 あるいは、『CSSコンポーネントとReactコンポーネントの衝突』と言ってもいいかもしれない。 つまり、例えばCSSで .sample-button{ display: block; width: 100px; background: red; } というcssのクラスで見た目を定義し、そのクラスをそのまま複数のReactコンポーネントに適用すると問題は生じます。 スタイルは共通でも、機能の違うReactコンポーネントに同じCSSクラスを使い回してスタイルを当ててしまうと、後々、変更が入ったときに、CSSの影響範囲がわからなくなって、設計が破綻します。 そのような事態を招くくらいならば、もちろん、Tailwindのような汎用ユーティリティティクラスを全てのコンポーネントに個別で指定していった方がマシです。 このような問題を引き起こさないためにCSS ModulesでのCSS運用にはルールを設けた方がいいでしょう。 『Reactコンポーネント1つあたり、1つの紐づくCSSファイルを必ずつくり、別のコンポーネントのCSSファイルは絶対に使用しない。共通スタイルはmixinで定義し、それをインポートして使う。』などを提案させていただきます。 Tailwind CSSはスタート速度と、最低品質を保証した? 確かにTailwind CSSを使うとCSS設計そのものが不要なので『設計の破綻によるカオス』は防げますし、デザインシステムの設定なしにある程度の見た目を保証してくれるといったメリットはあるのかなと思います。それによってヨーイドンでUIをつくっていく効率は確かに高まるのかもしれない…。 しかし、Tailwind はやはり『理想』ではない‥ TailwindCSSの上述のメリットは、『コードが汚くなる・スタイリングが完全にTailwind依存になる』という『将来的な保守性の悪化リスク』とトレードオフになっています。 結局、Tailwindは、ありがちなCSSにまつわる問題を解決し、最低品質は保障してくれるかもしれない一方で、必ずしも『理想のフロントエンド』への道筋にあるものではないのかなと思っています。 しかし、『CSSを絶対に書きたくない』『CSSを書かないで済む世界線が理想』と考えている開発者にはTailwindこそ理想の方向を向いているのかもしれません。 Webのスタイリングは異なったサイズのディスプレイの対応などもあって、ボリュームが大きくなることが多いです。私は、スタイリング要素を完全にUI構造と紐づけて記述していく未来よりは、やはりCSSは分離して書く方が筋かなぁと思っているのですが、確信はありません。Tailwindがこれからより普及してスタンダードになっていく可能性もあると思っています。JS強い方々でTailwindを推している人が多いのは事実です。しかし、私はどうしてもTailwindをスッキリ飲み込めません。この記事が一つの意見として、ある種の『ブレーキ』として働いてくれると幸いです。 参考 Tailwind CSSが私には合わなかった理由
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React+Material UIで、スタイルをJS in CSSで書く方法まとめ

株式会社ユニフェイスの大竹です。 製造実行システムIB-MesのWebアプリ版・IB-Mes SaaSのフロントエンドを担当しています。 IB-Mes SaaSではReact,TypescriptとMaterial UIを使っているのですが、毎回JS in CSSの書き方に詰まってしまうので、まとめました 以下の内容を読んで、 わかること:JS in CSSを利用したCSSの書き方 わからないこと:上記以外の時のCSSの書き方 記事を書いているときの各種ツールのバージョンは、Material UI v4、React 17.0.2ですReact 16.8で追加されたフック(Hook)を利用しています グローバルにCSSを当てる グローバルにCSSを当てるときは、'@global'を使います以下の例では、MuiPickersSlideTransition-transitionContainerというクラスに、グローバルにCSSを適用しています 参考:https://stackoverflow.com/questions/58755118/global-styles-with-react-and-material-ui const useStyles = makeStyles((theme) => ({ "@global": { ".MuiPickersSlideTransition-transitionContainer.MuiPickersCalendarHeader-transitionContainer": { order: -1, }, ".MuiTypography-root.MuiTypography-body1.MuiTypography-alignCenter": { fontWeight: "bold", }, }, })) 子要素を指定してスタイルを当てる ol(番号付き箇条書き)の先頭を(1)のようなカッコつきの表記にするCSSで説明しますolの子要素のliを指定する時は、ol:{}の中に"& li":{}を入れます疑似要素を指定する時は、"&::before"のように書きますカッコつきの表記にするポイントは、contentに`"(" counter(cnt) ") "`を指定することです 参考:https://www.websuccess.jp/blog/archives/2711/の技 調整する①の内容をJS in CSSに書き換えました const useStyles = makeStyles((theme: Theme) => createStyles({ ol: { paddingLeft: 0, marginLeft: theme.spacing(4), "& li": { listStyleType: "none", counterIncrement: "cnt", display: "block", position: "relative", "&::before": { content: `"(" counter(cnt) ") "`, marginLeft: -theme.spacing(4), width: theme.spacing(5), position: "absolute", top: 0, left: 0, }, }, }, }) ) スタイルを当てる部分では以下のように指定します const classes = useStyles() <ol className={classes.ol}> <li>hoge</li> <li>bra</li> </ol> 直下の階層の子要素を指定する 直下の階層の子要素を指定する時は、子セレクタ > を利用することができます const classes = makeStyles((theme: Theme) => createStyles({ editButton: { flexGrow: 1, textAlign: "center", "& > *": { margin: theme.spacing(1), }, }, }) ) 疑似クラスの指定 疑似クラスの指定は、:(セミコロン1個)を利用します例えばホバー時の処理を書く場合は、"&:hover"となります以下のように指定すると、マウスカーソルを載せた時だけ背景の色が変わります。 const useStyles = makeStyles((theme: Theme) => createStyles({ icon: { "&:hover": { background: theme.palette.action.active, }, }, }) ) もし間違いなどありましたら、ご指導ご鞭撻のほどよろしくお願いしますReactとMaterial UIなどについて勉強中なので、お手柔らかにお願いします
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Material UIのスタイルをJS in CSSで書き換える方法まとめ

株式会社ユニフェイスの大竹です。 製造実行システムIB-MesのWebアプリ版・IB-Mes SaaSのフロントエンドを担当しています。 IB-Mes SaaSではReact,TypescriptとMaterial UIを使っているのですが、毎回JS in CSSの書き方に詰まってしまうので、まとめました 以下の内容を読んで、 わかること:JS in CSSを利用したCSSの書き方 わからないこと:上記以外の時のCSSの書き方 記事を書いているときの各種ツールのバージョンは、Material UI v4、React 17.0.2ですReact 16.8で追加されたフック(Hook)を利用しています グローバルにCSSを当てる グローバルにCSSを当てるときは、'@global'を使います以下の例では、MuiPickersSlideTransition-transitionContainerというクラスに、グローバルにCSSを適用しています 参考:https://stackoverflow.com/questions/58755118/global-styles-with-react-and-material-ui const useStyles = makeStyles((theme) => ({ "@global": { ".MuiPickersSlideTransition-transitionContainer.MuiPickersCalendarHeader-transitionContainer": { order: -1, }, ".MuiTypography-root.MuiTypography-body1.MuiTypography-alignCenter": { fontWeight: "bold", }, }, })) 子要素を指定してスタイルを当てる ol(番号付き箇条書き)の先頭を(1)のようなカッコつきの表記にするCSSで説明しますolの子要素のliを指定する時は、ol:{}の中に"& li":{}を入れます疑似要素を指定する時は、"&::before"のように書きますカッコつきの表記にするポイントは、contentに`"(" counter(cnt) ") "`を指定することです 参考:https://www.websuccess.jp/blog/archives/2711/の技 調整する①の内容をJS in CSSに書き換えました const useStyles = makeStyles((theme: Theme) => createStyles({ ol: { paddingLeft: 0, marginLeft: theme.spacing(4), "& li": { listStyleType: "none", counterIncrement: "cnt", display: "block", position: "relative", "&::before": { content: `"(" counter(cnt) ") "`, marginLeft: -theme.spacing(4), width: theme.spacing(5), position: "absolute", top: 0, left: 0, }, }, }, }) ) スタイルを当てる部分では以下のように指定します const classes = useStyles() <ol className={classes.ol}> <li>hoge</li> <li>bra</li> </ol> 直下の階層の子要素を指定する 直下の階層の子要素を指定する時は、子セレクタ > を利用することができます const classes = makeStyles((theme: Theme) => createStyles({ editButton: { flexGrow: 1, textAlign: "center", "& > *": { margin: theme.spacing(1), }, }, }) ) 疑似クラスの指定 疑似クラスの指定は、:(セミコロン1個)を利用します例えばホバー時の処理を書く場合は、"&:hover"となります以下のように指定すると、マウスカーソルを載せた時だけ背景の色が変わります。 const useStyles = makeStyles((theme: Theme) => createStyles({ icon: { "&:hover": { background: theme.palette.action.active, }, }, }) ) もし間違いなどありましたら、ご指導ご鞭撻のほどよろしくお願いしますReactとMaterial UIなどについて勉強中なので、お手柔らかにお願いします
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails(api) × react(frontend)のSPA環境構築 github pushまで

はじめに こんにちは、ゴンと申します。転職のために、railsとreactでSPAのポートフォリオを作成中です。 この記事では、railsとreactの環境構築と、githubにpushするまでを書いてみました。 最初の環境構築は情報が違ったりして迷うので、参考にして頂けたら幸いです!! 前提環境 ruby 2.7.1 node 14.3 rails 6.0.3.7 目次 プロジェクトフォルダ作成 railsファイル作成 reactファイル作成 github登録 事前準備 yarn node ruby,rails 以上のインストールができていることを前提にして進めます プロジェクトフォルダ作成 プロジェクトフォルダを作成 $ mkdir [フォルダ名] railsファイル作成 プロジェクトフォルダに移動 $ cd [フォルダ名] rails プロジェクトを作成します。(apiモードで作成する場合は2行目を実行) $ rails new api -d postgresql $ rails new api --api -d postgresql # apiモードで作成する場合はこちらを実行 reactファイル作成 reactプロジェクトを作成します。 (typescriptを作成する場合は2行目実行) console $ npx create-react-app frontend $ npx create-react-app frontend --template typescript // typescriptを導入したい場合はこちらを実行 確認 ファイル構成確認 下記の様なファイル構成になっていたら大丈夫です。 [プロジェクトフォルダ] ├ api └ frontend サーバーが動くか確認 rails サーバー console $ cd api $ rails s react サーバー 起動時にWould you like to run the app on another port instead?と聞かれますが、yesでOKです。 console $ cd frontend $ cd npm start それぞれ、以上のような画面になれば大丈夫です。 npm startの度にyesと打つのは面倒なので、.envファイルを作成して、以下のように記述しましょう。 frontend/.env PORT=3001 CORS設定 railsとreactのローカルサーバーでデータをやり取りするために、CORSの設定をしていきます。 CORSの概要については以下の記事を御覧ください。 gemfileに以下を記述し、bundle installします。 gemfile gem 'rack-cors' $ cd api $ bundle install api/app/config/initializersのcors.rbに以下を記述します。 (cors.rbがない場合は作成してください) api/app/config/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3001' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end これで、apiとフロント間でデータをやり取りすることができます。 以上で、railsとreactの環境構築ができたので、最後にgit hubにpushしていきましょう。 githubにpush プロジェクトフォルダで、git add .をすると以下のエラーが起きます。 error: 'api/' does not have a commit checked out これはapiフォルダに.gitのファイルが存在するため、エラーが起きてしまいます。なので以下のコマンドで削除します。 $ rm -rf /api/.git $ rm -rf /frontend/.git これで、gitコマンドが使えるようになりました。 ルートフォルダにgitignoreを作成しましょう。 以下のコードはrailsとreactのgitignoreをコピペしてディレクトリを編集しました。 プロジェクトフォルダ/.gitignore # See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. api/.bundle # Ignore all logfiles and tempfiles. /api/log/* /api/tmp/* /api/!/log/.keep /api/!/tmp/.keep # Ignore pidfiles, but keep the directory. /api/tmp/pids/* /api/!/tmp/pids/ /api/!/tmp/pids/.keep # Ignore uploaded files in development. /api/storage/* /api/!/storage/.keep /api/.byebug_history # Ignore master key for decrypting credentials and more. /api/config/master.key # production /frontend/build # misc /frontend/.DS_Store /frontend/.env.local /frontend/.env.development.local /frontend/.env.test.local /frontend/.env.production.local /frontend/npm-debug.log* /frontend/yarn-debug.log* /frontend/yarn-error.log* あとは、github上でレポジトリを作成し、ローカルでadd,commit,pushをしましょう。 最後に 以上で、rails × reactで環境構築 + gitのpushができました。 初学者の方の参考になれば幸いです!! 次の記事では、rails側でapiを実装し、react側で、データを表示する処理を紹介します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】 ミニマルなコードで見るstate - クラス、Hooks、Reduxそれぞれでの使い方と利点

はじめに Reactでコンポーネント内の情報を保持するには、stateを使う必要があります。 クラスコンポーネント、Hooks、Reduxどれを利用するかで手順が異なるため、 簡潔にそれぞれの書き方や利点についてまとめました。 極めて単純な「押すと数字が増えるボタン」のコードを例として扱います。 全体の動きを追えるよう、本来はファイルを分割すべき所でも結合されたコードを貼っています。 動作確認はCodeSandboxでしています。 クラスコンポーネントの場合 React16.8(2019/2)まで関数コンポーネントではstateが使えず、 React.Componentを継承したクラスコンポーネントを使う必要がありました。 この書き方にはthis、bind、constructor 等、 Hooksを使う場合と比べ冗長な部分が多く、現在ではあまり使われていないようです。 しかしながら、Hooksではクラスコンポーネントの動作を完全には再現できなかったり、 Reactについて調べていると、クラスを使っている文献がまだまだ多かったりするため、 クラスコンポーネントを使うメリットもあります。 関数コンポーネントで実装するのが難しい処理の作成を急ぐ場合等に、こちらを使えばよいと思います。 クラスコンポーネント import React from "react"; import ReactDOM from "react-dom"; class CounterComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.incrementCount = this.incrementCount.bind(this); } incrementCount() { this.setState({ count: this.state.count + 1 }); } render() { return <button onClick={this.incrementCount}>{this.state.count}</button>; } } ReactDOM.render( <CounterComponent />, document.getElementById("root") ); stateの宣言から書き換えまで this.state = { count: 0 }でstateの宣言・初期化を行い、 this.state.countで参照、 this.setState({ count: this.state.count + 1})で書き換え。 この超単純なコンポーネントを実装するのに、8回ものthisを書く必要があります。 また、onClickに渡す関数をbind(this)する必要があるなど面倒です。 Hooks(関数コンポーネント)の場合 現在主流の書き方です。 Hooksとは、関数コンポーネントの機能を拡張するため実装されたReactの機能のことです。 この中のuseStateにより関数内でstateを使えるようになりました。 クラスコンポーネントに比べ、副作用を抑えつつ、非常に簡潔に書けます。 Hooks import { useState } from "react"; import ReactDOM from "react-dom"; function CounterComponent(props) { const [count, setCount] = useState(0); const incrementCount = () => setCount(count + 1); return <button onClick={incrementCount}>{count}</button>; } ReactDOM.render( <CounterComponent />, document.getElementById("root") ); stateの宣言から書き換えまで const [count, setCount] = useState(0)でstateの宣言をしています。 分割代入によりcountという変数にstateの参照、setCountにstateを変更する関数を代入しています。 countで参照できています。 setCount(count + 1)で書き換えることができます。 Reduxの場合 React単体では、stateが各コンポーネントに分散しており、 該当のstateを持たないコンポーネントにpropsで情報を注ぎ込む必要があります。 そこで問題になってくるのが遠い親戚コンポーネント間での情報のやり取りです。 例えば、ひ孫コンポーネントに情報を渡す必要がある場合、 stateを持った親から子へ、子から孫へ、孫からひ孫へpropsを渡す必要がありました。 Reduxでは一つしか存在できないStoreと呼ばれる場所ですべてのstateを管理し、 結びつけたコンポーネントから直接アクセスできるため、propsのバケツリレーから解放されます。 Fluxの思想に従い、View、Action、Reducer等のパーツを作る必要があります。 そのため、単純なアプリケーションでは、React単体と比べコード量は多くなりがちですが、 情報の受け渡しが単純になるため、大規模アプリにおいて有用です。 Redux import ReactDOM from "react-dom"; import { createStore } from "redux"; import { Provider, connect } from "react-redux"; const INCREMENT_ACTION_TYPE = "INCREMENT"; const countReducer = (state = 0, action) => { switch (action.type) { case INCREMENT_ACTION_TYPE: return state + 1; default: return state; } }; const mapStateToProps = (state) => ({ count: state }); const mapDispatchToProps = (dispatch) => ({ incrementCount: () => dispatch({ type: INCREMENT_ACTION_TYPE }) }); const counterView = (props) => ( <button onClick={props.incrementCount}>{props.count}</button> ); const CounterComponent = connect( mapStateToProps, mapDispatchToProps )(counterView); const store = createStore(countReducer); ReactDOM.render( <Provider store={store}> <CounterComponent /> </Provider>, document.getElementById("root") ); stateの宣言から書き換えまで countReducerの引数初期化式state = 0で初期値を代入しています。 今回はstateに直接数値を入れていますが、 state = { count:0 }等、オブジェクトを渡す例が多いです。 mapStateToPropsで指定したcountを使い、props.countで参照をします。 mapDispatchToPropsで指定したincrementCountを使い、props.increment()で加算しています。 解説 ユーザーがボタンを押した時の動作の流れをコード上から追跡すると、 counterViewのonClickに登録されたprops.incrementCountが呼ばれる mapDispatchToPropsのincrementCountによりdispatchが呼ばれる dipatchによってcountReducerが呼ばれる countReducerの引数にはStoreにある現時点のstateと、Actionである{type:INCREMENT_ACTION_TYPE}が渡される countReducerによって変更後のstateが返され、Storeに格納される mapStateToPropsに書かれた通りStoreにあるstateがpropsとしてcounterViewに渡される Storeにアクセスするコンポーネントでは、 react-reduxの関数connect(mapStateToProps,mapDispatchToProps)(component)によって、 mapStateToPropsに参照するstate、 mapDispatchToPropsにstateを変動させるActionのDispatcherを登録し、 引数propsとしてというのが基本の流れとなります。 使い分け 基本的に関数コンポーネントを使い、Hooksでstateを管理し、 情報のやり取りが複雑化してきたらReduxを導入すればよいというのが私の考えです。 関数コンポーネントでどうしても実装できない処理に対する最終手段としてなら、 クラスコンポーネントを使うのもアリだと思います。 参考 (React公式) state とライフサイクル (React公式) ステートフックの利用法 まとめ 記事を投稿するのはこれが初めてなため、 改善点等ありましたら、是非教えてほしいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでReact+Typescriptの開発環境を構築する

はじめに よく見る内容ではありますが、メンバーに配布できるようになってなくて困ったことがあってので改めて自分でやってみました。 環境 ~ % docker --version Docker version 20.10.2, build 2291f61 ~ % docker-compose --version docker-compose version 1.27.4, build 40524192 手順 node.jsのイメージをもとにコンテナ作成 Reactのプロジェクト作成 より使いやすく 1.node.jsのイメージをもとにコンテナ作成 node.jsのイメージをもとにコンテナを作成する [ docker-compose.yml ] version: '3' services: app: image: node:16.3.0-alpine3.11 volumes: - .:/app tty: true 動作確認 ~ % docker-compose exec app sh / # node Welcome to Node.js v16.3.0. Type ".help" for more information. > nodeコマンドが使用できることが確認できます / # ls app bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var / # cd app /app # ls docker-compose.yml ファイルがコンテナ内にマウントされていることも確認できました。 2.Reactのプロジェクト作成 先ほど作成したコンテナ内でReactのプロジェクトを作成します。 ~ % docker-compose exec app sh / # cd app /app # npx create-react-app sample_app --template typescript パッケージのインストールが走ってsample_appディレクトリが作られます。 . ├── docker-compose.yml └── sample_app Reactの初期ページを参照するにはポートの設定が必要です。 [ docker-compose.yml ] version: '3' services: app: image: node:16.3.0-alpine3.11 volumes: - .:/app ports: # 追加 - "8080:3000" tty: true yarn startで立ち上がるサーバーのポートは3000がデフォルトなので、それをもとに設定。http://localhost:8080/ でReactの初期ページが参照できます。 3. より使いやすく [ docker-compose.yml ] version: '3' services: app: image: node:16.3.0-alpine3.11 volumes: # ①変更 - ./sample_app:/app ports: - "8080:3000" working_dir: /app # ②追加 command: sh -c "yarn install && yarn start" # ③追加 ① マウントするディレクトリの変更 sample_app内以外のファイルをコンテナ内でいじる必要がないからね。 ② working_dirの変更 コンテナ入った時に一発でsample_app配下のファイルに行ける方が楽だよね。 ③ コマンドの追加 docker立ち上げのタイミングで yarn install && yarn start のコマンドが走るようになる。docker-compose upするだけでサーバーが立ち上がるからコンテナ入って yarn start叩く必要がないので楽。yarn installはメンバーと環境が合わせられるように。 おわりに ここまでご拝読いただきありがとうございました。少しでも役に立ちますように。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

APIキーを非表示にする方法

create-react-app にて、GitHubにコミットできるようAPIキーを非表示にする方法を簡単にまとめておきます。 ①プロジェクトのディレクトリのルートに .env ファイルを作成 ② .envファイル内で、選択したAPIキー名にREACT_APP_を追加して割り当てる。 .env 内に、下記を記入。 React_APP_OPENWEATHERMAP_API_KEY = (ここにAPIキーを書く) ※React_APP_ こちらを書き忘れないこと。 ③ .envファイルを.gitignoreファイルに追加。 gitignoreはGitで使われる特殊なファイル名で、このファイルに書かれたファイルは上から順に処理されて、Gitのトラッキングの対象外になる。 Gitのトラッキングの対象外になるということは、Gitで管理していたディレクトリの中にあっても無視されるということ。 # api keys .env ④使用する process.envオブジェクトを介してAPIキーにアクセスできる。 const APIKEY = process.env.React_APP_OPENWEATHERMAP_API_KEY; デプロイ時は... ページをデプロイする前に、.envファイルを削除し、プラットフォームのキー管理システムを使用する。 その他注意点 ○チーム開発時の注意点 githubには上がらなくなるから、git pullしても、他の人の環境には入らない。 APIキーは、別で教えないといけない。 ○.env を作ったあとは、再起動をすること。 ( yarn start やり直すの忘れない)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】初回レンダリングを回避したい

初回レンダリングされる 以下を読み込むと、初回レンダリングが走ってしまい、「1回押された」と表示されてしまいます。 本来はbuttonが押されたときのみ、処理をさせたい。。。!! なので、初回レンダリング時に読み込ませないようにします。 Hoge.jsx import React, { useState} from "react"; export const Hoge= () =>{ const [count,setCount] = useState(0); const handleHoge= () => { setCount(count+1) } return( <div> <div>{count}回押された</div> <button onClick={() => handleHoge()}>HOGEButton</button> </div> ) } 解決策 初回レンダリングかどうかのフラグを持たせて、制御する方法です。 Hoge.jsx import React, { useRef,useState,useEffect } from "react"; export const Hoge= () =>{ const [count,setCount] = useState(0); const renderFlgRef = useRef(false) useEffect(() => { console.log("初回レンダリング") renderFlgRef.current = true },[]) const handleHoge= () => { if (renderFlgRef.current){ setCount(count+1) } } return( <div> <div>{count}回押された</div> <button onClick={() => handleHoge()}>HOGEButton</button> </div> ) } 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでサブディレクトリにWebアプリを置くときの設定まとめ

利用シーン Reactでフロントを作ったとき、https://site.domainではなく、 https://site.domain/sub_dirctoryにindex.jsやindex.htmlを設置する場面。 そのままでは、大量のcontentsが404 Not Foundになります。 たくさんURLを書き換えるのは骨が折れるので、数か所の変更で済む方法はないのか? 情報源がいろいろあって分かりにくかったので、1ページにまとめました。 ※下記で./はプロジェクトルートディレクトリを指します。 package.jsonを設定 ./package.jsonに"homepage": "/sub_dirctory/"を追加します。 ./package.json { "name": "project001", "version": "0.1.0", "private": true, // -- any things // -- any things + "homepage": "/sub_dirctory/" } historyを追加 Routingを行っているjsファイルにて、Router系のJSXのpropsであるhistoryに設定を行います。 この時、ライブラリhistory/createBrowserHistoryが必要です。 historyを使っていない場合は、ここで新規にインストールします。 npmを使ってインストールする場合のコマンドです。 terminal npm install history npmではなくyarnの場合です。 terminal yarn add history 次に、該当箇所に変更を入れます。 ./src/app.js // -- any things import MiniApp_001 from 'miniapp_001'; import MiniApp_002 from 'miniapp_002'; import { Router, Route, Switch } from 'react-router-dom'; import { createBrowserHistory } from 'history'; // -- any things const customizedhistory = createBrowserHistory({ basename: '/sub_dirctory' }); // -- any things class App extends React.Component { render() { return ( // -- any things <Router history={customizedhistory}> <Switch> <Route exact path="/" component={MiniApp_001} /> <Route exact path="/test" component={MiniApp_002} /> <Route component={NotFound} /> </Switch> </Router> // -- any things ); } } export default App; 上から順に説明していくと、 まず、先ほどインストールしたhistoryからcreateBrowserHistoryをインポートします。 import {createBrowserHistory} from "history" 次に、createBrowserHistoryを使って、/subdirectoryを指定するためのオブジェクトcustomizedhistoryを用意します。 const customizedhistory = createBrowserHistory({ basename: '/sub_dirctory' }); JSXオブジェクトであるRouterのpropsに、historyがあります。このhistoryの値にcustomizedhistoryを設定します。 <Router history={customizedhistory}> この例ではRouterを用いていますが、他にBrowserRouterやHashRouter、MemoryRouter、StaticRouterがあります。いずれも同様に設定可能です。 .htaccessを設定する Apache の場合にはhtaccessの設定を必要とします。 ./public/.htaccess RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ ./index.html [QSA,L] Rewrite系構文のドキュメント RewriteEngine RewriteCond RewriteRule nginx.confを設定する nginxの場合はnginx.confに下記設定を追記する必要があります。 nginx.conf location ^~ /sub_dirctory{ alias /var/www/project001/build; try_files $uri $uri/ /sub_dirctory/index.html; } まとめ 多くのWebフレームワークでは、ドメインのルートに設置する前提で設計されています。もちろんReactも例外ではありません。デフォルトでは/に設定されています。しかし、いくつか設定すれば簡単に変更できます。 多くのWebフレームワークがDRYの原則から生まれたようなもの。これが出来るのは当たり前品質かもしれませんね。少しぐらいの誤差は吸収できるように、ロバストなコードを使えばサボれる。サボりは大切です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最新の技術記事を自動配信するSlackアプリを作って公開してみた

はじめに この度、各社のテックブログを中心に、最新技術記事を配信するSlackアプリWinkieを作って公開しました。 毎日定時に最新の技術記事がお使いのSlackワークスペースのチャンネルに配信されます。 現在Winkie FrontendtとWinkie Backendの二種類があり、それぞれフロントエンド、バックエンドの情報が配信されます。 開発チームの技術力向上に、個人の情報収集に、お使いください。 編集部からのおすすめポイントの解説もあるので、読む前の参考になります。 この記事では、当サービスWinkieがどのような技術を使って開発されているかをご紹介したいと思います。 Slackアプリの開発に関心のある方は特に必見です。 サービス構成図 以下がサービスの構成図です。 管理画面 投稿する記事の登録や、編集部からのおすすめの文章を編集する管理画面が必要になるため、その管理画面のフロントをReact+Typescriptで実装しています。基本的には記事の追加・編集・削除等の単純で軽量なものとなっているためRedux等のツールは導入していません。デザインも、とりわけ凝ったものにはしておらず、Material UIを使って実装しています。 APIサーバー 記事の登録や配信、slack情報の取得などを行うAPIサーバーはScala+PlayFrameWorkで実装しています。 DatabaseはPostgresを使用していて、Slickを使用してデータベースへのアクセスを行なっています。 アーキテクチャについてはDDD(ドメイン駆動開発)で構築しています。 デプロイ デプロイは管理画面・サーバーともにHerokuを使ってデプロイを行なっています。 また、記事配信のための定期実行もherokuで行なっています。heroku schedulerで実装 つまずいたポイント 今回このSlackアプリWinkieを開発するにあたってつまずいたポイントをいくつか列挙したいと思います。同じ問題に苦しむ方の参考になれば幸いです。 slack event api Botがアンインストールされたり、channelに招待されたりなどといったイベントが起こった際に、それにhookしてapiリクエストを飛ばしてくれる仕組みが用意されているのですが、イベントごとにリクエストURLの指定ができず、全ての受信イベントが1つのURLにアクセスします。 そのため、そのイベントがなんのイベントであるかを受け取ったサーバー側で判定する必要がありました。 Winkieのサーバーではadapter層で送られてきたJsonに型をつけたり、バリデーションを挟んだりしています。 そのためリクエストのbodyのJsonからどの型か(どのリクエストか)をその処理の前に判定しています。 Scalaにはapplicativeという概念があり、これを使って型の判定、振り分けを行いました。 やり方としては同一のEventBodyというtraitを各々が継承してその後型のパターンマッチングでどの処理を走らせるか決めています。 sealed trait EventBody final case class AppUninstalledEventBody( teamId: String, apiAppId: String, eventType: "app_uninstalled") extends EventBody final case class AppHomeOpenedEventBody( channelId: String, userId: String, eventType: "app_home_opened" ) extends EventBody def mapToEventCommand = case body: AppUninstallEventBody => // 後続する処理 case body: AppHomeOpenedEventBody => // 後続する処理 リクエストのJson形式 Slack event apiは日本語ドキュメントが用意されていて非常にわかりやすいのですが、たまにドキュメントとは異なる形でJsonが送られてくる場合がありました。ドキュメント通りのJsonを想定して実装を進めるとフォーマットが異なると怒られてしまうケースがあります。 もしおかしいなと思ったら、一度リクエストをそのままログ出力してみるなどして確認してみるといいと思います。 Block Kit Builderを活用する Block Kit Builderを使うと、送信するメッセージのプレビューを確認しながらUI構築が可能です。 画面左でぽちぽちで操作すると、そのUIを構築するのに必要なJsonが自動で画面右に生成されます。画面最左のメニューから、どんなUIテーマが用意されているかを確認することも可能ですね。 それに加えて例えば、ユーザーがセレクトボックスから何か選択した時にどういったリクエストが飛んでくるかもここから確認ができ、非常に便利です。 画面中央上にある、Action Previewのタブをクリックした上で、左側のUIプレビュー画面で実際に操作することで確認できます。 ちなみに右上のSend to Slackボタンから実際に送信できます。 終わりに いかがでしたか。外部APIを多く利用するようなサービスはそのAPIの仕様や制約に結構引っ張られてしまいがちなので、やはりそこが難しいですね。 Winkieはhttps://winkie.app/ から簡単にインストールができます。ぜひインストールしてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + Typescript + Lint + Hook のプロジェクトテンプレート

React + Typescript + Hook のプロジェクトテンプレート 自分用に作ったものですが、せっかくなので公開します。 こんな人に向けた記事 React開発やりたいけど、TypescriptとかLintの設定がイイカンジに設定されてるプロジェクトテンプレートがほしい人 Redux、Redux-Sagaとか、そろそろいらないんじゃ?ってことでHookで書いたらどうなるのかななめ読みで知りたい人 やったこと TODOアプリを作った 触れるサンプル:GitHub.io ソース:GitHub ポイント解説 useResource hookを別ファイルに書けます。これにより、JSXとロジックを分割することができます。 非同期処理 async/await 脱Redux-Sagaのため、async/await を利用します。 今回はサンプル実装のため、いろいろ足りていません。業務用などで使うなら、エラーハンドリングなどを共通メソッド化してそちらで集約してアレコレやるといいです。 stateの永続化 今回はlocalStorageを利用しています。 ログイン情報(ログインAPIなどでを外部サーバ化した場合のIDなど)はサーバ側でCookie利用しましょう。ローカルデータはセキュリティがザルってことを忘れてはいけない。 番外編:おすすめしないこと というか、とある現場で遭遇したバグの愚痴。ある意味で実体験に基づくアンチパターン集 anyだらけ Typescriptを全否定する攻め?のスタイル。 Lintエラー放置 4桁のエラーがあったりすると、放置しちゃいけないエラーも無視することになる。 でかいソース 目的のコードを探すため、延々とソース追いかけるはめになる。経験則的に600行以上は分割対象。 デフォルト値がtrueを期待するboolean イラっとします。そんでもって初期化忘れて画面ぐちゃぐちゃ。 useMemoは、基本やめとけ 裏の値と画面表示が一致しないとかの、やべーバグを修正させられるこっちの身にもなって。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】ReactでSPA実装(初歩:React Routerでページ内遷移)

はじめに ReactでSPA(シングルページアプリケーション)を作成する上で初歩となる、React Routerに関する記事になります。 前提 この記事では環境構築、「create-react-app」でまでの作業が完了していることを前提に進めます。 1、react-router-domのインストール まず今回使用するreact-router-domライブラリをインストールします。 npm install react-router-dom 2、遷移先用のjsxを用意する 今回はまずcssもなにもない、ただ文字が表示されるだけの関数コンポーネントが記述されたjsx(hello.jsx、seeyou.jsx)を、 src > page(任意のフォルダ) 配下に用意します。 jsファイルでも問題ありません。 hello.jsx import React from 'react'; function hello(){ return ( <div> hello </div> ) } export default hello; seeyou.jsx import React from 'react'; function seeyou(){ return ( <div> seeyou </div> ) } export default hello; 3、App.jsの編集 実際の表示ページであるApp.jsでは、「BrowserRouter」、「Route」、「Link」をインポートします。 BrowserRouterを親要素として、その中にRoute、Linkを記述します。 Routeタグの中ではpathでURLを指定していて、「exact」はpathに指定したURLが完全一致した時のみ表示できるようになります。 componentでは、そのパスに対して表示するコンポーネントを指定しています。 App.js import React from 'react'; import { BrowserRouter, Route, Link } from "react-router-dom"; import hello from './page/hello'; import seeyou from './page/seeyou'; function App() { return ( <BrowserRouter> <ul> <li><Link to="/hello">こんにちは</Link></li> <li><Link to="/seeyou">さよなら</Link></li> <li><Link to="/">リセット</Link></li> </ul> <Route path="/hello" exact component={hello} /> <Route path="/seeyou" exact component={seeyou} /> </BrowserRouter> ); } 4、npm startで表示確認 npm startでlocalhost:3000を開くと以下のように表示されます。 まずは「こんにちは」をクリックします。 URLがlocalhost:3000/helloに変わり、「hello」が表示されます。 次にリセットをクリックします。 URLが戻り、helloが消えます。 「さよなら」をクリックします。 URLがlocalhost:3000/seeyouに変わり、「seeyou」が表示されます。 このように非同期処理で表示を切り替えることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Max React Sec9 メモ

99 ・stateは一つのelementに従属するので,rootelementが必要になる。 ・配列でreturnすることもできるが、keyが要求される ・解決する方法はchildrenを返すだけのカスタムコンポーネントを作る const Wrapper = props => { return props.children; }; export default Wrapper; ・またはReact.Fragmentを使う(同じ) で囲む 103 ・css的にはmodalのelementは全体のaboveであるべきだが上のやり方ではできない。 ・なんとindex.htmlにroot divを追加する <div id="backdrop-root"></div> <div id="overlay-root"></div> <div id="root"></div> ・さらにreactDom使ってしていしてreturnする デフォルトだとid root内にrenderされるのを指定して強制してる感じかな import ReactDOM from 'react-dom'; const Backdrop = props => { return <div className={classes.backdrop} onClick={props.onConfirm} />; }; const ModalOverlay = props => { return ( <Card className={classes.modal}> <header className={classes.header}> <h2>{props.title}</h2> </header> <div className={classes.content}> <p>{props.message}</p> </div> <footer className={classes.actions}> <Button onClick={props.onConfirm}>Okay</Button> </footer> </Card> ); }; const ErrorModal = (props) => { return ( <React.Fragment> {ReactDOM.createPortal( <Backdrop onConfirm={props.onConfirm} />, document.getElementById('backdrop-root') )} </React.Fragment> ); } export default ErrorModal;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Typescript メモ #2 axiosを使ってget, useEffect, hook, map

あ、すいません 過去に投稿された記事、現在進行系で書いている記事についてですが あくまでも、私のメモなので チュートリアル、ドキュメントではないので、注意してください それでも、よかったら見てください 今回は axios っていうのを使って api から getする方法のメモで~す jsonをgetする方法を書きます、というかjsonをgetして実際に使う方法も書いときます 初めに axios をインストールしてください、というかそれぐらいはわかるでしょう yarn, npm なんでもいいです json {title:[1,2,3,4,5]} Ax.tsx import React from 'react' import axios from 'axios' function Ax() { const [getjson, setGetjson] =React.useState([]) const titlemap = getjson.map((i) => <li>{i}</li> ) React.useEffect(() => { axios.get(url※ここにjsonを取得できるapi) .then(res => { setGetjson(res.data.title) } ) },[]) return ( <div className="ax"> <ul> {titlemap} </ul> </div> ) } export default Ax とりあえず、これをもとにメモ書いときます まず const [getjson, setGetjson] =React.useState([]) これはHookっていうらしいです https://ja.reactjs.org/docs/hooks-intro.html 「フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。」 って書いてあるけど、わからないよね多分 私も正直わからないけど、これは他のプログラミング言語やってる人向けのメモになっちゃうけど 変数みたいな感じですね 私の解釈なので間違いかもしれません、すいません React.useState()の括弧の中に最初に入れたいものを入れます "" とか 0 とか [] 用途で変えてください "" 文字 0 数字 [] は jsonから取得したやつを入れたり? とか、詳しくはしりません だいたい、どこからでも参照上書きができる変数みたいな感じです 上書きしたいときは setGetjson(※上書きしたいもの) でできます ( 重要なのは [getjson, setGetjson] ですね getjsonは読み込み専用 setGetjsonは上書き専用 みたいなかんじです 左は読み込み専用、右は上書き専用って覚えると良いかも で、命名は自由だけど、上書きする際につかうやつはみんなだいたい最初にsetをつけてその次の文字を大文字にしてます よくわからなければ、この方法で命名しとけばなんとかなります ) ここでやってますね React.useEffect(() => { axios.get(url※ここにjsonを取得できるapi) .then(res => { setGetjson(res.data.title) } ) },[]) 次にこのメモです これはなにかというと、useEffectっていうものです笑 詳しくはしりません、私が ページを読み込みしたときにapiからjsonをgetするようにコードを書いたとき 無限にgetしていることに気づき、なんとかならないのかと思い調べた結果これがでてきて、これを使えば私が思い描いたとおりに動きました React.useEffect(() => { この中にやりたいことを書きます。    },[]) で、[]って何って思うでしょう これは、初回レンダリング時のみに中に書いたことを実行するようにするために、必要なものです。 あと、[]って[]以外になにを書くのかって言うと、 ここに書いたものが更新されたときに再レンダリングされる らしいです、たとえば、titleがあったとしてtitleが更新されたときにページが再レンダリングされるらしいです 長くなりましたが、メイン axios の使い方です axiosはなんか async awaitっていうやつを使って使う方法もあるらしいです、自分にはよくわからなかったので 別の方法を使いました、それがこれです axios.get(url) これはそのままです、axiosのgetってやつを使います 他にもありますが、今は使わないので書きません ていうか、まだ使ってません urlに 叩いたらjsonを出してくれるapiを入れてください、json以外は今回はしりません 今回、改行してるんですけど、多分改行しなくても大丈夫かも 気になる人は改行してね で つぎは .then(res => { setGetjson(res.data.title) } ) これです、.thenは成功したとき、ifで言うtrueのときですね falseもありますが、今回は書きません resは結果です .dataは中身です 他にもありますが、今回は書きません .titleは {title:[1,2,3,4,5]} のtitle:です これを指定すれば、キー(title)の値がとりだせます 要するに、res.data.titleには [1,2,3,4,5] が入ってます で、それを使うために [1,2,3,4,5] を setGetjson に上書きしてます const titlemap = getjson.map((i) => <li>{i}</li> ) mapってなに?って思ったでしょう forに似たやつです、配列を回せます 今回はこれを使いました、forのことは自分で調べてね、ここでは必要ないので書きません (forのことを調べてないとは言えない) 配列を回せますとかって言われてもわからないよね多分 今回は getjson を使ってます、ようやく登場ですね getjsonってなに????ってなると思うんですけど [1,2,3,4,5] です setGetjsonを使って上書きしたやつです で、(i)ってなに????ってなると思うんですけど (i) は pythonで例えると for i in [1,2,3,4,5]: print(i) の i です [1,2,3,4,5]の中の一つが入っています つぎは <li>{i}</li> これ、これは説明しなくてもいいですよね ググれば出ます、すぐに で、最後 return ( <div className="ax"> <ul> {titlemap} </ul> </div> ) これは return です、前の記事に書いてます https://qiita.com/I999m/items/48fbaa8d8303ee75f48e そっちをよかったら見てください {titlemap} は 一応書いときますね これは、 const titlemap = getjson.map((i) => <li>{i}</li> ) を呼び出してます ていう感じです、本当にこれはほとんど私が読むためのメモなので 結構雑に書いてます、というか私が文を作るのが下手すぎて雑になってる可能性もあります 今回はこれで終わりです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

色々書き比べた結果Tailwind CSSにしたという話

Twitterでこういう発言を見かけまして Tailwind CSSはデザインに凝ってるサイトでは使えない こだわりが無い場合に向いている は?何いってんの? って思ったので、自分がいろいろ試した結果、Tailwind CSSを選んだ話を書きます。 はじめに 以前、Tailwind CSSは結構いいぞって話を書いたんですが、この記事の立ち位置的にはその続きみたいなものなので、以下の記事を始めにご参照いただけるとより分かりやすいかもしれないです。 この記事では、前回記事を書いた後、個人仕事でWebサイトをGatsbyで作り、その中で、どうやってCSSを書くのが良いのか模索した結果、自分はこれを選んだっていうのを、同じUIを色々な方法で書き比べたコードを並べつつ、どうのこうの筆者の考えを述べていきます。 その仕事はほとんど筆者が「まかせてくださいよーいい感じに作りますよー。デザインそろってないっすね。揃えます!」ってノリで、自分が好き勝手、やりたいようにやったやつです。ちなみにGatsby + Prismicでやりました。Movable Typeでやるとか言ってたので辞めさせてそれにしました。 はじめに、最終的にCSSを書くために使った技術を言っておくと 8割方Tailwind CSS Tailwind CSSで書きづらい部分は @emotion/css + twin.macro です。 サンプルコード なんか書いてたらどんどん長くなってしまって、うわっ、私の記事長すぎ……って思ってるのですが、この記事を書くために書いたコードは以下に置いておきました。 create-react-app で作ってて、以下にデプロイしてあります サンプルコードって言っても、ほぼひたすら同じUIを別の方法で書いたやつが並んでるだけですが。このページに並んでいるのは、 スモールスクリーン時は縦にテキストと画像が積まれる ワイドスクリーン時は横にテキストと画像が並ぶ という、CSSを書いてる人であれば100万回ぐらい書いたであろうものを、色んな方法で書いたものです。ブラウザを縮めたり広げたりしてご確認頂ければと思います。 コンポーネントはこのディレクトリに一通り入ってます。 ピュアにTailwind CSSで書いたモノ それでははじめましょー。ReactでどうCSS設計をしたら良いのかの旅ってことで。 とりあえず初めは、純粋にTailwind CSSで書いたらこうなるぞというものです。 コードとしてはこんな感じになります。 <div className="lg:ml-[-40px]"> <div className="lg:flex w-full"> <div className="pb-[20px] space-y-[20px] lg:pl-[40px] lg:w-1/2"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </div> <div className="lg:pl-[40px] lg:w-1/2"> <img className="block w-full" src="https://placekitten.com/400/300" alt="" /> </div> </div> </div> まず、前回の記事でも書いたことなんですが、ReactやらVueなりで書くわけでもナシに、HTMLとCSSを手書きで書いているような状況だとします。 このUIが1回だけ出てくるんであればこれでいいんですが、このUIなんて汎用的に使われそうなやつですし、2回以上出てくる時に、これで書いてしまうとキツいですね。 Tailwind CSSで書けば、CSS書かなくてもスタイル当てられて楽だ〜ってなるんですが、これをコピペして画面を増やしていったら、使われてる箇所を全部直して回らないといけないです。地獄……。なので、HTML+CSSだけ書いているような人にとっては 「うーんTailwind CSSってこういうやつか〜。私は使わないなー」 って思うんじゃないかと思います。まーそれはそうですね。自分も手でHTMLを量産していくようなケースにおいては、Tailwind CSSは選ばねーなと思います。 救世主BEM そんなこんなでHTML+CSSを書く人たちを救ってくれたと言うか、あ!これこれ!これいいね!って流行ったのがBEMなわけなんですが、そのBEMで書くと、以下のような感じになります。 <div className="BemBlock"> <div className="BemBlock__Inner"> <div className="BemBlock__TextWrapper"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </div> <div className="BemBlock__ImgWrapper"> <img className="BemBlock__Img" src="https://placekitten.com/400/300" alt="" /> </div> </div> </div> /* BemBlock */ .BemBlock__TextWrapper { padding: 0 0 20px; } .BemBlock__TextWrapper > * + * { padding: 20px 0 0; } .BemBlock__Img { display: block; width: 100%; } @media (min-width: 768px) { .BemBlock { margin: 0 0 0 -40px; } .BemBlock__Inner { display: flex; width: 100%; } .BemBlock__TextWrapper { padding: 0 0 0 40px; width: 50%; } .BemBlock__TextWrapper > * + * { padding: 20px 0 0; } .BemBlock__ImgWrapper { padding: 0 0 0 40px; width: 50%; } } BEMについてはここで突っ込んで書かないですが、これでコンポーネントっぽくできたねーという感じです。ただ、色々問題があります。 まずひとつは、このCSSが書かれているのが、サイト全体で読み込んでいるCSSであるということ。色々やりようはありますが、基本的には、多くの画面において、その画面で使っていないCSSのルールが大量に含まれた巨大CSSを読み込むことになります。 そしてもうひとつは、これはコンポーネントらしきモノにすぎないということです。スタイルを変えたかったら、確かにCSSをいじればそれで済むんですが、変えたい内容によってはdivやらspanやらを追加したり、要素の順番を入れ替えたり追加したりしないとならないです。コンポーネントを夢見たHTMLとCSSって感じです。今からすると。 まぁ、色々問題はあるものの、みんながそれぞれの考え方で好き好きにCSSを書いていた時代と比べると、こんな風にわかりやすいルールが広まって、共通言語みたいになったというのは、すごいことだなーとは思います。自分も手書きでHTMLをガンガン書いていくような状況では、積極的にBEMを選ぶと思います。 BEM + Tailwind CSS そんなBEMですが、このBEMベースの設計にTailwind CSSを混ぜることは出来ます。コードとしてはこんな感じです。HTMLはさっきとほぼ一緒な感じ。 <div className="BemTailwindBlock"> <div className="BemTailwindBlock__Inner"> <div className="BemTailwindBlock__TextWrapper"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </div> <div className="BemTailwindBlock__ImgWrapper"> <img className="BemTailwindBlock__Img" src="https://placekitten.com/400/300" alt="" /> </div> </div> </div> /* BemTailwindBlock */ .BemTailwindBlock { @apply lg:ml-[-40px]; } .BemTailwindBlock__Inner { @apply lg:flex w-full; } .BemTailwindBlock__TextWrapper { @apply pb-[20px] space-y-[20px]; @apply lg:pl-[40px] lg:w-1/2; } .BemTailwindBlock__ImgWrapper { @apply lg:pl-[40px] lg:w-1/2; } .BemTailwindBlock__Img { @apply block w-full; } どうでしょう、だいぶスッキリしてます。 この場合のTailwind CSSの役割としては、色々と短く書けるってことですね。あと、Tailwind CSSではデザインシステム的なことをすることができて、それを活かせるというのもあるんですが、これについては後で書きます。 こういう使い方はナシじゃないんですが、Tailwind CSSの指向している「ユーティリティーファースト」とは違うんですよね。はじめはこういう風にTailwind CSSを使うのは違うんですよって思って書いたんですが、なんか実際書いてみると悪くはないですね……。手でHTMLを書いていく場合にはまぁこういう使い方を検討してみてもいいんではないでしょうか。 とはいえ!前述のBEMの問題は何も解決されていないです。このコードでapplyしてる部分はただTailwind CSSで定義されているプロパティと値のセットに置き換わるだけなので、実質、Sass的に言うと「便利なmixinがいっぱい使える」みたいな状態ですね。 なので、こういう使い方を想像されているなら、 「うーん、学習コストそれなりにあるし、別にいっかな〜〜〜」 は、まぁそうだろうなとは思います。 これは主観ですが、Tailwind CSSを書くのって、フツーにCSS書いてる感じとだいぶ感覚が違うんですよね。それはこの後書くTailwind CSSの設計思想によるところが大きいからだと思うんですが、何にせよ、こういう風に書くのはナシではないとは思います。BEMっぽく書いてTailwind CSSを使うなら、PostCSSのプラグインをちょいちょい組み合わせると色々快適になります。 CSS Modules まぁここまでがReactやらVueナシの世界の話でして。 HTMLとCSSだけ書いてると、CSS設計的にはその先に行けないなーというか。筆者的にはその先にあるのがReactだのVueだと思ってるんですよね。これは、ReactとかVueはそういうものだというわけではなく、私はReactやVueを、そういうCSS設計でBEMの先にある存在として扱おうと考えてるってことです。Reactはコンポーネント指向の「ライブラリ」なので、それをそいういう風に扱いたいなーと考えている筆者が居る感じといいますか。まぁこのあたりの話は前回の記事を読んでいただくとして。 ここからは、じゃーReactでどう書く?ってのを、アレやコレやためしたというのを書いていきます。 まずはCSS Modulesです。 まずReactで何かCSS書く時にどうする?って選択肢として挙がるのがCSS Modulesだと思います。GatsbyでもCSS Modulesオススメよって書いてあります。 まぁ筆者的にはもうこれはオススメすべきやつじゃないんじゃないのって感じではあるんですが。 何はともあれ、どんな感じか、例のUIをCSS Modulesで書いてみたコードが以下です。 App.js <CssModulesBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </CssModulesBlock> css-modules-block.js import styles from './css-modules-block.module.css'; export const CssModulesBlock = ({ children, src }) => ( <div className={styles.container}> <div className={styles.inner}> <div className={styles.textWrapper}> { children } </div> <div className={styles.imgWrapper}> <img className={styles.img} src={src} alt="" /> </div> </div> </div> ); css-modules-block.module.css .textWrapper { padding: 0 0 20px; } .textWrapper > * + * { padding: 20px 0 0; } .img { display: block; width: 100%; } @media (min-width: 768px) { .container { margin: 0 0 0 -40px; } .inner { display: flex; width: 100%; } .textWrapper { padding: 0 0 0 40px; width: 50%; } .textWrapper > * + * { padding: 20px 0 0; } .imgWrapper { padding: 0 0 0 40px; width: 50%; } } ざっくり言うと、CSSをファイルをimportすると、そのCSSファイルの中にあるクラスセレクタに、ランダムな文字を足してくれて、それを各要素のclassNameに指定していくって感じです。headの中にはそれに対応するセレクタが突っ込まれます。こんな感じに。 <style> .css-modules-block_textWrapper__JEfrZ { padding: 0 0 20px; } .css-modules-block_textWrapper__JEfrZ > * + * { padding: 20px 0 0; } .css-modules-block_img__5jb8q { display: block; width: 100%; } @media (min-width: 768px) { .css-modules-block_container__2xV22 { margin: 0 0 0 -40px; } .css-modules-block_inner__1Fwz- { display: flex; width: 100%; } .css-modules-block_textWrapper__JEfrZ { padding: 0 0 0 40px; width: 50%; } .css-modules-block_textWrapper__JEfrZ > * + * { padding: 20px 0 0; } .css-modules-block_imgWrapper__JeJhQ { padding: 0 0 0 40px; width: 50%; } } </style> そしてHTMLの方はこうなります。 <div class="css-modules-block_container__2xV22"> <div class="css-modules-block_inner__1Fwz-"> <div class="css-modules-block_textWrapper__JEfrZ"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </div> <div class="css-modules-block_imgWrapper__JeJhQ"> <img class="css-modules-block_img__5jb8q" src="https://placekitten.com/400/300" alt=""> </div> </div> </div> この結果、ほとんどこのコンポーネント内でスコープを作ったみたいな状態にできます。 なるほど悪く無さそう。それにこれ、なるべくCSSは純粋なCSSとして扱おうとしているアプローチがいい感じに見えますね。さらに、このCSSの中ではさっきBEM + Tailwind CSSでやったみたいに、@applyが使えたりもします。PostCSSが使えるってことです。CSS in JSとかハックっぽい感じがしますし、そんなことする必要ないでしょ?これでよくない?って思ってました。初めは。 最終的に自分はTailwind CSSとCSS in JSを選ぶんですけど、Tailwind CSSではどうしても書きづらい部分があって、そういう部分はCSS Modulesを使うつもりでいました。どういうやつが書き辛いのかは追って書いていきます。 でも結局CSS Modulesを使うのはやめました。理由としては以下のような感じです。 ファイルが増えてめんどくさい CSS Modulesを使うと、1コンポーネント毎に1つ、CSSファイルを作らないといけないんですよね。「えー?そんなの大したこと無くない?」「CSSなんだからCSSファイルに書くのは当然でしょ」って最初は思ってたんですが、やってみるとこれ、だいぶゴチャゴチャして書き辛いと感じました。特に、ただ3,4行のCSSを書きたいだけの時にファイルが増えていくのがなかなか辛いなーと。CSS Modulesの対抗馬となるのは各種CSS in JSなんですが、これは対抗馬であるCSS in JSの方がスッキリ読みやすいと思ったというのがまずひとつ。 順番が保証されないとか怒られる Gatsbyでやってると、別の画面に遷移した時、その画面でコンポーネントの読み込み順が異なると、「CSSの読み込み順が異なるからスタイルが意図したとおりに反映される保証ねーぞ」みたいにwebpackに怒られました。こんな感じです。 これ、すごいややこしいんですが、CSS Modulesって、CSSを追加で読み込むんで、コンポーネントをimportする順番が違ってると、これはまずいんじゃない? CSSの順序違ったら、想定している描画結果変わっちゃわない? っていちいち警告してくれるんですよね。 なんかこれを回避するには、importの順番を揃えるとかせねばならないらしく、いやーめんどくさい……ってなりました。まぁこれは無視しても良かったんですが、とりあえず筆者的には「うーんCSS Modulesじゃないほうがいいのかなー」って思わされた事件でした。 webpack依存技術である なんか最初はCSS標準技術に近いやつだなって思ってたんですけど、結局の所これ、webpackをゴニョゴニョして無理矢理スコープっぽくしてるだけなんですよね。GatsbyだのNext.jsは、webpackに乗っかっているReactのその上に存在するレイヤーなので、なんかこのwebpackの機能に依存しているCSS Modulesという存在が、フレームワークとうまいこと噛み合ってねーなと感じました。この後書くCSS in JSもほぼほぼ同じことをやっているんですが、JSファイルの中でチョロっと書けてしまって。それに対してCSS Modulesはお行儀よくCSSファイルにしないといけないんですよ。そしてwebpack依存技術だぞっと。 まぁそんなこんなで、端的に言うとCSS in JSのほうがベターであると考え、筆者はCSS Modulesを使うのはやめました。ありがとうさようならCSS Modules。 BEM混ぜます? CSS Modulesじゃないとするとどうしようかなーと思って、その一つの選択肢が、一部だけBEM的に書く。グローバルな感じのCSSに一部BEMで書いたCSSを書いちゃうという方法です。最初はこれで良いんじゃないかなーと思ってやってたんですが、結局やめました。なぜかと言うと、さっき書いたみたいな、グローバルなCSSがどんどん増えるっていう問題がまずあるんですが、それよりも、 Reactコンポーネント化したUI BEMで組んだUI が混在することが、管理上辛いなーと感じたためです。 こっちはReactコンポーネントで完結してて、こっちは一部だけグローバルな部分にCSSが書いてあるみたいな状態、純粋に全部BEMで書いてあるよりカオスな感じがしてきたので、この方法はやめました。設計的によろしくない気がします。さようならBEM。Reactの世界ではやはりいらなかった。 CSS in JSどれがいい選手権 そしたら必然的に選択肢として、CSS in JSにするかなー。あんまり乗り気ではないけど……と思うものの、どれを選んだら良いのかわからぬ。そもそもCSS in JSを選ぶこと自体に筆者はしっくり来ておりません。 CSS in JSどうなのよって思うポイントは色々ありました。 まず種類が多すぎるところ。なんかいっぱいあってどれが良いかよくわからんし、いちいち全部調べたくないんですが……というか。そもそもなんかそういう標準技術でもなんでも無いやつにCSSまるっと任せちゃうのが不安ってのもあります。2年後には無くなってそうだなーとか思っちゃいますね。 そして、そもそもJSでCSSを当てるというアプローチ自体どうなんですか?それイケてる実装なんですか?というのが全然しっくり来ない。昔はCSSが利用できない環境でも使えるようにとかあったけど、ああいうのはもう気にしなくていいの? 直にstyle書いちゃうのってなんかダメな理由無かったっけ? とかなんか色々モヤモヤが。 そんなCSS in JSに対するモヤモヤを解消するべく、なんか色々プレゼンを見たりなどしました。(まじめだな) 見た中で結構良かったやつは以下2つです。 この2つは結構わかりやすいかなーと思ったんで、まぁ興味ある人がいたらオススメです。 1つ目は、Styled Componentsを作ってる人のプレゼン、2つめは何かでかいカンファレンスでCSS in JSと素のCSSを比較してpros and consを話してるやつです。 まずStyled Componentsの人のプレゼンの方は、今に至るまでのCSS in JSの歴史みたいなものを紹介していて、今後どうなるかというのが話されてます。ざっくり内容をまとめると、 今のCSS in JSライブラリはお互いに真似し合ってて同じ様なAPIがある 今後どうなるかはみんなでアイデアを出し合っていこうね みたいな感じです。なんか個人的にはベストワンが知りたかったので、Styled Componentsがいいんだぜ的なノリを期待していたんですが、ライブラリ作ってる側としてもそうかーまぁ先のことは分からんよねということですね。そして、「どのライブラリも真似し合ってて大体同じなんすよね」みたいなことを言っていて、これには深く納得してしまいました。 この後、CSS in JSのコードを紹介していきますが、たしかにどれも似た感じで書けるんですよね。似てるっていうかほぼ同じAPIです。そしてどれも、基本的なCSSの書き方をそこそこリスペクトしたというか、あんまり飛び抜けたAPIにはしていない感じです。 なので、「JSでCSSを書く」っていうよりかは、「JSの中にフツーに書いたCSSを混ぜられる」って感じになってますね。自分の印象としては。このプレゼンを見た後に自分は色々試すんですが、どれを選んでも対して変わらんなというのを思ってます。 CSS in JSのメリット/デメリット CSS in JSを使うことのメリット/デメリットについてサラッと書いておきます。 まず1つ言えるのがscopedな感じにスタイルを当てられること。具体的にどういう感じかはこの後の例で紹介しますが、端的に言うと、さっきCSS ModulesがやってみせたことをJSでやる。ただそれだけです。なのでまぁCSS Modulesと同様、コンポーネント内でスコープを作った感じになります。これは当然、コンポーネント中心で設計するには大きなメリットといえます。 コンポーネント化されることで得られるメリットとして他に挙げられるのが、コンポーネントのスタイルが、そのコンポーネントのコードに内包されるというところです。BEMで書くと、100個のBEMブロックがあれば、そのCSSがグローバルな部分に突っ込まれたり、もしくは自分でうまいこと管理せねばならんですが、CSS in JSで書けば、Reactコンポーネントを書いた、そのJSファイルに内包される形にできるので、このコンポーネントが使われる時に初めて読み込ませる感じにしやすいです。シンプルにcode splittingが実現できます。 個人的に書いてみても、この「JSファイルの中にCSS書けちゃう」という点は、結構デカいと感じました。プレゼンの中では、こういう風にCSSを書けるから、ルールに沿わないCSSの書き方がそもそもできなくなるというような事を言ってたりしました。これは自分も大いに納得です。 どーでしょうか。BEMを理解してくれている人がいたとして、その人にBEMで書いてくださいと伝えても、どのCSSファイルにどうやって書くとかは別途考えねばならんのです。でもCSS in JSなら、フツーに書けばそのコンポーネントのJSファイルの中に書くことになります。こういうケースでは、サイト全体でCSSがカオスになるみたいなことが、そもそも起こらないぞってことなんです。この安心感は大きいですよ。 あとはまぁ、Reactはコンポーネントで考えるように指向して作られているので、その設計思想にも合ってるかなーという雰囲気を感じます。 メリットはそんなところで、デメリットとして挙げられるのは、 標準技術じゃないので寿命がよくわからん 再レンダリングのコストがかかる ランタイムでスタイルを当てることになる JSで混ざってごっちゃになりがち とかです。これら、どれも納得ではあるんですが、レンダリング周りについては、Next.jsとかGatsbyとかでSSG/SSRするようなアプローチでは、発生しない問題なんですよね。 SSGとは、乱暴に言うと、SPAの最初のレンダリング結果で作ったHTMLを作って返すという仕組みなので、CSS in JSで書いたコードは、JSに依存しない、ただのHTMLとCSSとしてブラウザは受け取ります。なので、CSS in JSとは言えども、SSGする上では別にJSが有効になっていなければ使えない訳ではないし、管理面のプラスが大きいので、CSS in JSでいいかなーという感じです。 CSS in JSでいいかなーというよりか、さっき書いたようなCSS Modulesの問題がCSS in JSでは発生しないので、どちらを選ぶかと言われればCSS in JSかなって感じです。あとは、JSの処理を気軽に混ぜられるのもいいですね。Sass scriptみたいなのをいちいち書きたくないですし、そんなのもはやいらんでしょという感じです。ありがとうさようならSass。まぁSassは使おうと思えば使えますが、Reactで書く上で、あえてSassを選ぶ理由はほとんど無いんじゃないでしょうか。 goober 「おうおう、じゃあそのCSS in JSとやら、見てやろうやないかい!」 ってことで、gooberというCSS in JSのライブラリ を使ってさっきのUIを書いたのが以下です。 App.js <GooberBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </GooberBlock> goober-block.js import { css } from "goober"; const styles = { container: css` @media (min-width: 768px) { margin: 0 0 0 -40px; } `, inner: css` @media (min-width: 768px) { display: flex; width: 100%; } `, textWrapper: css` padding: 0 0 20px; @media (min-width: 768px) { padding: 0 0 0 40px; width: 50%; } > * + * { padding: 20px 0 0; } `, imgWrapper: css` @media (min-width: 768px) { padding: 0 0 0 40px; width: 50%; } `, img: css` display: block; width: 100%; `, }; export const GooberBlock = ({ children, src }) => ( <div className={styles.container}> <div className={styles.inner}> <div className={styles.textWrapper}>{children}</div> <div className={styles.imgWrapper}> <img className={styles.img} src={src} alt="" /> </div> </div> </div> ); 書いてる内容はCSS Modulesの時と大して変わんないんですが、コンポーネントのJSファイルの中にCSSが入ってる感じになってるのが分かるかと思います。 この結果どういうHTMLができるのかと言うと、こうです。 <div class="go2403310744"> <div class="go3735464671"> <div class="go1875143406"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </div> <div class="go2277549489"><img class="go2950335646" src="https://placekitten.com/400/300" alt=""></div> </div> </div> goうんたらという謎クラスがgooberが作ってくれるやつで、head要素内に以下が追加されます。 <style id="_goober"> @media (min-width: 768px) { .go2403310744 { margin: 0 0 0 -40px; } } @media (min-width: 768px) { .go3735464671 { display: flex; width: 100%; } } .go1875143406 { padding: 0 0 20px; } @media (min-width: 768px) { .go1875143406 { padding: 0 0 0 40px; width: 50%; } } .go1875143406 > * + * { padding: 20px 0 0; } @media (min-width: 768px) { .go2277549489 { padding: 0 0 0 40px; width: 50%; } } .go2950335646 { display: block; width: 100%; } </style> なるほどこれでコンポーネント内だけにスタイルを当てることが可能ということですね。gooberはTagged template literalで、生成したclassNameを返してくれるので、それをdivなりに指定すればスタイルを当てられます。 どーでしょう。これなら素でCSS書いてるのとあんまり変わらない感じでかけるし、スコープ化も実現されているのが分かるかと思います。 ちなみにNext.jsとかGatsbyなんかでSSGした場合、今紹介したHTMLとstyle要素がはじめっからHTMLファイル上に書き出された状態になってます。だから、CSS in JSってJS依存技術なんでしょ?って思っている方がいましたら、SSGする状況ではそれは誤解です。ランタイムでスタイルを当てるわけじゃないってことです。まーややこしい話ですが。 Styled ComponentsなAPI 次、またgooberです。 gooberには別のAPIも用意されていて、同じ内容を以下のように書くことができます。 App.js <GooberStyledBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </GooberStyledBlock> goober-styled-block.js import React from "react"; import { styled, setup } from "goober"; setup(React.createElement); const Container = styled("div")` @media (min-width: 768px) { margin: 0 0 0 -40px; } `; const Inner = styled("div")` @media (min-width: 768px) { display: flex; width: 100%; } `; const TextWrapper = styled("div")` padding: 0 0 20px; @media (min-width: 768px) { padding: 0 0 0 40px; width: 50%; } > * + * { padding: 20px 0 0; } `; const ImgWrapper = styled("div")` @media (min-width: 768px) { padding: 0 0 0 40px; width: 50%; } `; const Img = styled("img")` display: block; width: 100%; `; export const GooberStyledBlock = ({ children, src }) => ( <Container> <Inner> <TextWrapper>{children}</TextWrapper> <ImgWrapper> <Img src={src} alt="" /> </ImgWrapper> </Inner> </Container> ); これは、たぶんStyled ComponentsのAPIを真似た書き方だと思います。 一つ前に紹介したやつは、あくまでclassNameを作るというものだったんですが、これはまぁなんというか、どうせ君らはclassNameを指定した要素が欲しいんだろ?ってことで、そこまでをまるっと実装したReactコンポーネントをチョチョイと作ってくれちゃう感じです。 さっき、CSS in JSはお互いに真似しあってるって書いたんですけど、そーなんです。これ、Styled Componentsっていう別の著名なCSS in JSのライブラリで採用されている書き方なんですが、この後紹介するEmotionでも、ほぼ同じ感じで書けるようになっています。 ぐはっ。なんか色々書き方があってCSS in JSめんどくせぇ〜〜 と思うかも知れませんが、まーそうですね……。筆者もそう思います。でも自分で書くぶんには1つ選んで使えばいいだけですし、大差無いので書いていけばすぐ慣れる感じではあると思いますよ。 まぁそんな感じで、CSS in JSでも色々な書き方が用意されてたりしますね。Sassで言えば、scss形式かsass形式かみたいな感じに思ってもらえればいいかなと。 ちなみに筆者はこのStyled Components式の書き方は選びませんでした。基本はTailwind CSSで書いていくことにしたので、classNameを指定してスタイルを当てていくやり方と親和性が薄いと感じたためです。どっちが良いとか悪いとか言う話ではなく、Tailwind CSSと組み合わせるにはイマイチかもなって感じです。 変数とかmixinとかそういうやつ さて、ここまでで色々と紹介してきたわけですが、SassとかPostCSSで書いてた時に使ってたほら、変数とかmixinとか使いたくなりません?なりますよね? あーなった。今なったよ。 ということで、gooberの中で、変数やmixin的なふるまいを書いてみたのが以下です。 App.js <GooberThemeBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </GooberThemeBlock> theme.js const normalize = (remValue) => { return remValue.toFixed(4).replace(/\.?0+$/, ""); }; const toRem = (value) => { const px = parseInt(value); const rem = px / 16; return `${normalize(rem)}rem`; }; export const theme = { screen: { lg: `@media (min-width: 768px)`, }, fontSize: { sm: ` font-size: ${toRem("14px")}; line-height: 1.8; `, base: ` font-size: ${toRem("18px")}; line-height: 1.8; `, lg: ` font-size: ${toRem("22px")}; line-height: 1.8; `, xl: ` font-size: ${toRem("30px")}; line-height: 1.8; `, }, colors: { red: "#ff0000", green: "#00ff00", blue: "#0000ff", }, spacing: { xs: toRem("3px"), sm: toRem("5px"), md: toRem("10px"), lg: toRem("20px"), xl: toRem("40px"), "2xl": toRem("60px"), }, }; import { css } from "goober"; import { theme } from "../theme"; const styles = { container: css` ${theme.screen.lg} { margin: 0 0 0 -${theme.spacing.xl}; } `, inner: css` ${theme.screen.lg} { display: flex; width: 100%; } `, textWrapper: css` padding: 0 0 ${theme.spacing.lg}; ${theme.screen.lg} { padding: 0 0 0 ${theme.spacing.xl}; width: 50%; } > * + * { padding: ${theme.spacing.lg} 0 0; } `, imgWrapper: css` ${theme.screen.lg} { padding: 0 0 0 ${theme.spacing.xl}; width: 50%; } `, img: css` display: block; width: 100%; `, }; export const GooberThemeBlock = ({ children, src }) => ( <div className={styles.container}> <div className={styles.inner}> <div className={styles.textWrapper}>{children}</div> <div className={styles.imgWrapper}> <img className={styles.img} src={src} alt="" /> </div> </div> </div> ); theme.jsは、単純にひとつのobjectをexportしてるだけ。色だの文字サイズだの余白だのをまとめ、その中でpxをremに変換したりもしてるんです。これ、別になんかすごいことをしてるわけじゃないです。gooberではCSSとして評価される文字列を作ればいいので、使いまわしたい文字列をまとめとこうぜってだけですね。 どーですか?結構便利じゃないですか?これ。弊社内で、CSS in JSどう書いてるみたいな話をしてたらこう書いたってのを教えてもらったんですが。結構シンプルでしょう? しかしこれ、めっちゃ変数だらけになりますよね。 でもこうなるのって必然だと思うんです。サイト内で何度も共通使用される数値なり色なりを共通化するのって。 Tailwind CSSで書くとこう 次に紹介するのはなんと、じゃじゃーん。 やっとのことでTailwind CSSというフレームワークです。 「フレームワーク」です。ここポイントです。 今みたいな共通化は一旦おいておいて、例のUIをTailwind CSSで書くとこうです。 App.js <TailwindBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </TailwindBlock> App.js export const TailwindBlock = ({ children, src }) => ( <div className="lg:ml-[-40px]"> <div className="lg:flex w-full"> <div className=" pb-[20px] space-y-[20px] lg:pl-[40px] lg:w-1/2 "> {children} </div> <div className="lg:pl-[40px] lg:w-1/2"> <img className="block w-full" src={src} alt="" /> </div> </div> </div> ); な、なんて短さ……!CSSはどこいった?って感じなんですが、とりあえず、ここまでであえてスルーしてた、JITモードについて少し触れておきます。JITモードっていうのはこれです。 v2.1にプレビュー版として実装された機能で、ちょっぱやでコンパイルできるやつなんですが、これをオンにすると、pb-[20px]とか、括弧で囲むと、その値が指定されたCSSルールを柔軟に作ってくれます。これで書けばとりあえずその値で好きにお手軽にスタイルが当てられる感じですよ。Tailwind CSSではArbitrary value supportとか呼んでます。以下に例があります。 このAribitary valueで書くことを想定されているのは、そこだけ取り立ててユニークな値とかですね。たとえば、余白は基本30pxにしてるんだけど、1px分ボーダーがあるので29pxにしたいとか、あとは何かアイコンが飛びてて、その飛び出てる距離がそこだけ固有の34pxとかそういうやつです。 最初の話に戻りますけど、デザインにこだわりがある場合に使えないとか言ってる人のは、この機能をきっと知らないですよね?まぁv2.1ではまだプレビューなんですが、このまま入るのはほとんど間違いないかと思います。とりあえずこれで好きな値でもサクサクかけたりするわけです。 Tailwindで変数とかmixinとかヤツ さて、ここからが本題なんですが、Tailwind CSSで、さっきgooberのコードで変数なりを使ったやつを再現するとこうなります。 App.js <TailwindThemeBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </TailwindThemeBlock> tailwind.config.js const normalize = (remValue) => { return remValue.toFixed(4).replace(/\.?0+$/, ""); }; const toRem = (value) => { const px = parseInt(value); const rem = px / 16; return `${normalize(rem)}rem`; }; module.exports = { mode: "jit", purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], darkMode: false, // or 'media' or 'class' theme: { extend: { screens: { lg: "768px", }, fontSize: { sm: [toRem("14px"), "1.8"], base: [toRem("18px"), "1.8"], lg: [toRem("22px"), "1.8"], xl: [toRem("30px"), "1.8"], }, colors: { red: "#ff0000", green: "#00ff00", blue: "#0000ff", }, spacing: { xs: toRem("3px"), sm: toRem("5px"), md: toRem("10px"), lg: toRem("20px"), xl: toRem("40px"), "2xl": toRem("60px"), }, }, }, }; tailwind-theme-block.js export const TailwindThemeBlock = ({ children, src }) => ( <div className="lg:-ml-xl"> <div className="lg:flex w-full"> <div className=" pb-lg space-y-lg lg:pl-xl lg:w-1/2 "> {children} </div> <div className="lg:pl-xl lg:w-1/2"> <img className="block w-full" src={src} alt="" /> </div> </div> </div> ); Tailwind CSSって、コンフィグファイルに色々さっきやってたような変数なりを定義し、それがそのままユーティリティークラスになるって感じなんですよ。 この場合だと例えばpb-lgとかいうクラスがありますが、これはコンフィグのspacing.lgとして指定されているtoRem("20px")を参照し、padding-botto: 1.25remになるって感じですね。 こんな風に、Tailwind CSSは、さっきみたいな、デザインシステム的なアプローチがbuilt inなんです。というかむしろ、先にそれがあったんですが、そこでなお、ユニークな値もカバーできるようにJITモードみたいのが加わった感じですね。gooberでほとんど同じことしてますよね? だから、どうせこういうことをするならTailwind CSSを選んでおいたら良いんじゃない?って思うのです。 Tailwind CSSは、そもそもCSS in JSとはちょっと毛色が違いまして、ユーティリティークラスを作る「フレームワーク」なんですよ。 An API for your design system 筆者はこのTailwind CSSが好きで、なんで好きなのかっていうところなんですが、それはこの、 デザインシステムを定義する それを使って画面を作っていく というアプローチをベースにしているからなんですよね。 Taiwind CSSは凝ったデザインに向かないみたいなことをいう人はきっとこうなんでしょう。 「好きにCSSを書くにはいちいちコンフィグをいじらないといけない」 「へんなユーティリティークラスも覚えないといけないし不便なだけでしかない」 「決まった値で作るだけのデザインのこだわりのないサイトなら向いてるんじゃない?」 いやーーちょっとまって〜 お前は何も分かっていない…… Tailwind CSSのことを1mmも分かっていない! と思うわけなんですよね〜〜〜 Tailwind CSSのアプローチは、基本的に用意したユーティリティークラスで組んでいき、その中で例外的にユニークな値を使うということなんですよね。これは束縛です。トップページに書いてあるんですが。 constraint-based An API for your design system. って。これはなかなかいいキャッチコピーだと思いますよ。「制限ベース」「あなたのデザインシステムのAPI」なんですよ。 この制限を「凝ったデザインには向いていない」と感じするその感覚。なんかそれが、あーこの人はデザインに興味ないねって感じてしまいますね〜〜筆者的には。 そんな事言われても現実の仕事では無理じゃん? そ~いうことを書くとですね、あー意識高い人なのねみたいな風に思われるんですが。 「いやーTakazudoさんのおっしゃりたいことは分かりますよ」 「でも私がやってる仕事ってそういうやつじゃないんですよね〜〜」 って。いやーね、デザインの工程と実装の工程は2つで1つなんですよ。余白が揃ってなかったら揃えるし、色がブレてたらまとめるみたいな役割は考えないんですかね? デザインカンプで1pxずれてたら、それをそのまま愚直にCSSに書いちゃうんですか? それがこだわってるデザインだとでも? こだわりがないのはあんたのCSS設計だろー?って。 というわけで、私がTailwind CSSは凝ったデザインに向かないみたいな呟きを見て「は?何いってんの?」と思ったのはそういう理由です。Tailwind CSSが凝ったデザインに向いていないなんていうことは1mmも無い。むしろ、凝ったデザインをうまく実現することを指向して作られていて、あんたはその事に気付いてすらいない。のだ。 まぁ、私が誰かに実装を頼むとしたらそういうところまで見てCSS設計をして欲しいですね。それはそこそこ難しいことは分かっていて書いてますが。 めちゃくちゃclass長くて読みづらいんだけど まー私の個人的な意見はそのくらいにしておくとして、ここで2つほど、Tailwind CSSで書くとここが辛いみたいなのを緩和するTips的な話を2つします。 まず1つ目はクラス名について。 Tailwind CSSで書いていると、className長すぎでやってらんないんだけどと思われることはあると思います。こんな感じ。 <button type="button" className="relative w-1/2 bg-white border-gray-200 rounded-md shadow-sm py-2 text-sm font-medium text-gray-900 whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:z-10 sm:w-auto sm:px-8" > まぁ確かにわからんし嫌になるなというのは分かります。 そういう場合には自分はこうしてます。 <button type="button" className=" relative py-2 w-1/2 sm:w-auto sm:px-8 text-gray-900 bg-white text-sm font-medium whitespace-nowrap border-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:z-10 " > なんとなく関わりがあるプロパティ毎に纏めとく感じですね。テキスト周り、幅関係、フォーカス時の処理とかそういう感じで。class属性値に改行とかホワイトスペースとか混ざりまくってていいわけ?ってちょっと気になって調べたんですが、仕様的には問題無さそうです。 それと、Netlifyで、Tailwind CSSを使ってリニューアルしたぞって話があって、 その中で紹介されている@netlify/classnames-template-literalsがいい感じです。 以下みたいに書くと、 import ctl from "@netlify/classnames-template-literals"; const Button = ({ active=false }) => { const buttonClasses = ctl(` bg-black text-white p-1 rounded-sm ${active && 'border border-pink-600`} `); return <button className={buttonClasses}>Go!</button>; } activeがtrueがfalseな時に、buttonClasssesにfalseだとかundefinedだとかが混ざってきたりしてめんどくさいんですが、このctlは、それをpurgeして、余分なホワイトスペース/改行を詰めてくれます。Tailwind CSSのオトモにおすすめです。 そもそもややこしいCSSはコンポーネント分割して良いのでは もひとつ。コンポーネントの書き方についてです。 Tailwind CSSとは直接的に関係無いんですが、こうやってReactコンポーネントでかける環境下に置いて、スモールスクリーン時/ワイドスクリーン時で大きくレイアウトが変わるUIを、mediaquery達人芸みたいな方法で組むのはわかり辛いのでは?というのも考えました。 例えば例のUIは以下のようにも書けます。 App.js <TailwindSeparateBlock src="https://placekitten.com/400/300"> <p>The quick brown fox...</p> <p>The quick brown fox...</p> <p>The quick brown fox...</p> </TailwindSeparateBlock> App.js const VerticalBlock = ({ children, src, className }) => ( <div className={className}> <div className="pb-lg space-y-lg">{children}</div> <img className="block w-full" src={src} alt="" /> </div> ); const HorizontalBlock = ({ children, src, className }) => ( <div className={`-ml-xl ${className}`}> <div className="flex w-full"> <div className="pb-lg space-y-lg pl-xl w-1/2">{children}</div> <div className="pl-xl w-1/2"> <img className="block w-full" src={src} alt="" /> </div> </div> </div> ); export const TailwindSeparateBlock = (props) => ( <> <VerticalBlock className="lg:hidden" {...props} /> <HorizontalBlock className="hidden lg:block" {...props} /> </> ); CSS Grid Layoutを使えばもっと短く書けるぜ?みたいのはあるとは思うんですけど、単純にスクリーンサイズで大きくレイアウトが変わる場合、mediaqueryを使ってあれやこれやを打ち消したりなどするよりか、バッサリ分割してしまったほうが見やすいかとか思ったんですがどうでしょう。なんかアクセシビリティ上の問題とかあるかな?とか考えたけど思いつかなかったので。 コンポーネントの振る舞いというかインターフェースというか、そういう部分は、こういう風に分割されていようと無かろうと変わらないので、こういう書き方法がわかりやすいかな?と思ったのですがどうでしょう。いやそれはまずいぜみたいな事があれば教えていただけるとありがたいです。 まーこう書いたほうが良いんじゃないというよりかは、こう書いてもアリかな?って思ったのでこの記事の内容として混ぜました。 Tailwind CSSで書き辛いヤツら さて、まぁそんなわけでTailwind CSSで書くのが良いというか、CSS in JSなどで書いていっても、最終的にはTailwind CSSのコンフィグのようなしくみを自作することになるであろう故、Tailwind CSSを俺は選ぶぜというのが筆者の技術選択なんですが、最初の方でちょっと触れたように、Tailwind CSSではどうにも書き辛いというのがあります。 それは、WYSIWYGで入力されたHTMLや、markdownをコンバートして作られたHTMLを混ぜるときです。 そういうHTMLはこういう感じになるでしょう。 <h2>heading</h2> <p>paragraph paragraph paragraph...</p> <h3>heading</h3> <p>paragraph paragraph paragraph...</p> <p>paragraph paragraph paragraph...</p> <ol> <li>list</li> <li>list</li> </ol> <p>paragraph paragraph paragraph...</p> ウッ…… classNameが指定できない…… それはそうですね。コンポーネントの中にこういうコードを突っ込む場合、これ、どういうふうにCSSを書きます?フツーに考えれば、子供セレクタとか子孫セレクタでスタイルを当てるんじゃないでしょうか。こんな風に。 .richtext h2 { font-size: ...; padding: ...; } .richtext h3 { font-size: ...; padding: ...; } .richtext p { padding: ...; } Tailwind CSSだとこういうのはできないんですよね。残念ながら。どうしよう。 自分はこういう箇所にだけCSS in JSを使うことにしました。さっきのgooberでやったようにです。しかし、そうすると、Tailwind CSSのコンフィグに定義した諸々を活かしづらいです。そこで便利なのがtwin.macroというちょっとしたライブラリです。 これは何かって言うと、CSS in JSの処理の中にTailwind CSSのユーティリティークラスを混ぜられるというモノです。コードを見たほうが速いので、コードを紹介しましょう。こんな感じです。 App.js <WysiwygBlock src="https://placekitten.com/400/300"> <h2>The quick brown fox</h2> <p>The quick brown fox...</p> <h3>The quick brown fox</h3> <p>The quick brown fox...</p> <ol> <li>The quick brown fox jumps over the lazy dog. </li> <li>The quick brown fox jumps over the lazy dog. </li> <li>The quick brown fox jumps over the lazy dog. </li> </ol> <p>The quick brown fox...</p> <ul> <li>The quick brown fox jumps over the lazy dog. </li> <li>The quick brown fox jumps over the lazy dog. </li> <li>The quick brown fox jumps over the lazy dog. </li> </ul> </WysiwygBlock> 今回はWYSIWYGのコードが突っ込まれるという想定で、Reactコンポーネントは、そんな要素らを受け取ると想定します。 以下、childrenが突っ込まれるdivをルートとし、Emotionでスタイルを当てます。 wysiwyg-block.js import { css } from "@emotion/css"; import tw from "twin.macro"; const richtext = css` h2 { ${tw`text-xl`} ${tw`pb-md`} } h3 { ${tw`text-lg`} ${tw`pb-md pt-md`} } p { ${tw`pb-lg`} } ul { ${tw`ml-xl list-disc`} ${tw`space-y-sm pb-lg`} } ol { ${tw`ml-xl list-decimal`} ${tw`space-y-sm pb-lg`} } `; const Small = ({ children, src, className }) => ( <div className={className}> <div className={`pb-lg ${richtext}`}>{children}</div> <img className="block w-full" src={src} alt="" /> </div> ); const Large = ({ children, src, className }) => ( <div className={`-ml-xl ${className}`}> <div className="flex w-full"> <div className={`pb-lg pl-xl w-1/2 ${richtext}`}> {children} </div> <div className="pl-xl w-1/2"> <img className="block w-full" src={src} alt="" /> </div> </div> </div> ); export const WysiwygBlock = (props) => ( <> <Small className="lg:hidden" {...props} /> <Large className="hidden lg:block" {...props} /> </> ); twin.macroからimportしているtwというTagged functionが、Tailwind CSSのクラスを評価し、その内容を展開してくれるやつです。これを使うことにより、CSS in JSで書きつつも、その中でTailwind CSSのユーティリティーを使うというマジックが実現できます。 twin.macroは主にStyled ComponentsとEmotionに対応しているので、筆者は@emotion/cssを使うことにしました。 Emotionは、端的に言って、さっきのgooberと同じ感じです。Emotionを使うにも色々なAPIが用意されていて選択肢が色々あるんですが、webpack依存がイヤだしclassName管理に合ってるということで、@emotion/css を選びました。この構成なら、twin.macroはnpm installしてimportするだけで使える気がします。たぶん。 これでTailwind CSSで書きつつ、書き辛いコンポーネントもなんとか打倒することができました。BEMコンポーネントがゼロ、全てのスタイルはそれぞれのコンポーネントの中に閉じ込められました。CSS in JSの役割もclassName作ってくれるだけにとどめました。やった! ちなみにtwin.macroやさっき紹介したNetlifyのctlでTailwind CSSのクラスを使うと、VSCodeが補完してくれないんですが、以下設定をすればできるのでVSCodeの人はやるといいです。 とりたててユニークなUI Tailwind CSSで書きづらいなーというのがもう一つ。それは、とりたててユニークな見た目をしているUIです。そのデモを作ってみました。こんな感じです。 なんというか、数値的に具体的に何ピクセルとか何%とか、ここでしか使わない値がボンボン出てきて、className="って書こうとして手が止まる感じのヤツです。 こういうのは、そう思った時点でもう諦めて最初からCSS in JSで書くと良いよって思って自分でも書いてみたんですが、JITモードオンであれば、そこそこTailwind CSSでも書けますね。こんな感じになりました。 App.js <ComplicatedBlock /> complicated-block.js import { css } from "@emotion/css"; const styles = { gradient1: css` background: rgb(34, 193, 195); background: linear-gradient( 0deg, rgba(34, 193, 195, 1) 0%, rgba(253, 187, 45, 1) 100% ); `, gradient2: css` background: rgb(63, 94, 251); background: radial-gradient( circle, rgba(63, 94, 251, 1) 0%, rgba(252, 70, 107, 1) 100% ); `, gradient3: css` background: rgb(238, 174, 202); background: radial-gradient( circle, rgba(238, 174, 202, 1) 0%, rgba(148, 187, 233, 1) 100% ); `, shadow: css` box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; `, }; export const ComplicatedBlock = () => ( <div className=" relative pb-[20px] mb-[20px] lg:mb-[50px] " > <div className={` mx-auto w-full lg:w-[80%] h-[200px] lg:h-[300px] ${styles.gradient2} ${styles.shadow} `} ></div> <div className={` absolute w-[20%] h-[180px] left-0 bottom-0 hidden lg:block ${styles.gradient1} ${styles.shadow} `} ></div> <div className={` absolute w-[20%] h-[180px] right-0 bottom-0 hidden lg:block ${styles.gradient3} ${styles.shadow} `} ></div> <p className=" absolute top-[40px] right-0 left-0 text-5xl lg:text-7xl font-bold text-center " > Drink Bar is <br /> 299 Yen </p> </div> ); 自分が書いたやり方としては、基本classNameで指定していって、なんかめんどくさそうなやつはCSS in JSの方でやるという感じです。こういうgradientとかって、以下みたいなサイトで作ったりしませんか? そういうのはTailwind CSSの土台に乗せると若干めんどくさいので、こんな風に部分的にCSS in JSでclassNameを作るのが良いかなーと思ってます。あとは次に話す、Tailwind CSSのユーティリティークラスとして自分で追加するかが良いかなと思います。 Tailwind CSSをもうちょい突っ込んで使う ハイ。 そんな感じで、Tailwind CSS8割+2割emotion + twin.macroがいいんではないかなーという結論に今のところはなった自分ですが、そうは言ってもなるべくTailwind CSSで完結させようと、ここまでで触れていないTailwind CSSの機能を試したりしました。それらについても少し触れておきます。 ユーティリティクラスの追加 Tailwind CSSのコンフィグにて、完全オリジナルのユーティリティクラスを追加することが出来ます。以下に書いてあるやつです。 これは、前述した、グラデーションやbox-shadowを共通化する時に使いました。 スモールスクリーン時にだけ表示させる ワイドスクリーン時にだけ表示させる 段階的にフォントサイズを大きくさせる みたいのを作ろうと思ったんですが、ユーティリティを定義する中で、他のユーティリティーを呼ぶやり方がよく分からなかったので断念しました。Tailwind CSSに最初から含まれているユーティリティークラスらは、このAPIを使って書かれている ので、あんまりこう、ユーティリティ同士を組み合わせるような想定では無いのかな?と感じました。できるかも知れないんですが、複雑だしちょっと面倒そうかなと。なので、グラデやbox-shadowとかの、単独で完結しているものらに使うと良さそうです。 コンポーネントの追加 Tailwind CSSでは、ボタンとかのちっさな粒度のCSSルールのまとまりをコンポーネントとして登録できるようになってるんですが、これも使おうとしましたが結局やめました。 なんかこれは、コンフィグの中に書くんですけど、ここで定義するには、いちいち全部JSのオブジェクト形式にしないといけなくて、その時点で書くのがめんどくさいなーという印象です。そして、ここでも他のユーティリティーを使えるのかよくわからず、たとえ使えたとしても、そこまでしたらもはやそれはReactコンポーネントでやるべきであろうと感じたので、少なくともReactで実装するようなプロジェクトでは、この機能を使うことはなかろうと思われました。 疑似要素 ::beforeとか::afterとかでなんかデコりたい時、Tailwind CSSだと書きづらいという問題があります。これは一応解決法があります。そういう記法を有効にするプラグインがあって、それを使うというものです。 これを使えば<li className="before:empty-content">...</li>とかいうノリで書けるらしいんですが、自分はこれはやめて、ただspanを突っ込むことにしました。 今回記事を書くに際し調べたんですが、空っぽのspanを使ってもアクセシビリティ上問題ないみたいです。 筆者的には、疑似要素だろうとspanだろうと大して変わらないのであれば、spanの方が分かり安かろうという判断です。Reactコンポーネントの振る舞い/インターフェースとしては同じだし、疑似要素でデコるのもspanでデコるのも、CSSの表現力を補完するための方法が異なるだけで、どんぐりの背比べかなと思ったため。 子供セレクタ WYSIWYGのところで、そこだけCSS in JSを使うと書きましたが、場所によっては、> *をclassName="children:..."と書けるプラグインがあります。 これは使うかもと思ったんですが、結局使う機会がありませんでした。例えばliを繰り返すような状況は、大抵Reactコンポーネントの内外でArrayにしてからループするので、わざわざ子供セレクタを使ってもややこしくなるだけで、かといってWYSIWYGのように子供のバリエーションが多いと今度はそれだけでは足りなくなるので、結局使わないで終わりました。 あと、あんまり野良のTaiwind CSSプラグインを増やしてしまうと、PostCSSのよくわからんプラグインを増やすみたいで、俺の考えた最強のTailwind感が強くなってよろしくないかと考えました。どうしても子供セレクタを使いたいという時はCSS in JSでやるかなって感じです。 最後に 以上で筆者TakazudoがGatsby上でCSSどうするかを考えた話は終わりです。 Tailwind CSSは、ユーティリティーファーストで画面を実装するためのフレームワークなんで、そのへんを理解して使うと違うんではないでしょうか。 あと、多分特にWeb制作界隈の方は、この記事を読んで、「私はHTMLとCSS書いてるけど使う気機会が無さそうだな」とか思ってしまう方が多いみたいなのですが、いや〜〜自分から何もアクションせず、Tailwind CSSを使うような機会は、この業界でフツーに働いていても、ほとんどの人に訪れないんじゃないかなーと思います。Tailwind CSSがものすごいメジャーなものとならない限り。 この技術を使えば○○が効率的にできる、○○の問題を解決できると考え、自分から動いてアクションした結果、そういう仕事が依頼されたり、高い給与で雇われたり、高品質なWebサイトやWebサービスが完成するだけではないですか? そういうマインドセットがいいんじゃないかなーと筆者は考えております。筆者的にはもう手作りでHTMLを増やしていくのはやめたく、それができるようになったなーって感じてます。 筆者は普段は受託開発をしていてCodeGridというところで記事を書いていたりするので、よろしければよろしくどうぞ。 CodeGrid 株式会社ピクセルグリッド
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Async Iteretor]ちょっとずつ変化するコンポーネントを作りたい

はじめに 1 秒ごとに処理に応じてちょっとずつ変化していくコンポーネントが作りたかった です プログレスバーの拡張版みたいな... setInterval を使って実装して見たけど、setInterval の引数に渡した関数内では State が参照できないようで、以下のように setState 内で無理矢理条件分岐を作らないといけないです const intervalValue = setInterval(() => { setCount((count) => { if (count === 5) { clearInterval(intervalValue) return count } return count + 1 }) }, 1000) もっといい方法はないか考えていたらふと Generator の事を思い出したので実装してみることにしました 実装 お試しなのでシンプルにカウントアップするものを実装 作るもの ボタンを押すと 1 秒おきにカウントアップされる 比較のため Genetretor+Iteretor を使ったものと Interval を使ったものどっちも実装してみる コード See the Pen poeVOGB by あずは (@azuha) on CodePen. 解説 非同期ジェネレータ関数を利用して、count が 5 になるまで 1 秒毎に yield でカウントアップしていきます ジェネレータ関数を利用するとその戻り値は Generetor 型となり、yield で指定した値を持つ、いわゆるイテラブルなオブジェクトとして返却されます wait 関数を自作し、await で待機処理を作るために async 関数にしています const countUp = async function* (first) { let count = first; while(count < 5) { await wait(1000) count++ yield count } } 非同期ジェネレータ関数の戻り値は AsyncGeneretor 型となり、受け取ったイテラブルなオブジェクトは for await...of 文で取り出すことができます。 for await (const value of countUp(0)) { setCount(value) } ES2018 まで for 文の中で非同期処理は行えなかったみたいですが、for await...of 文が追加されてからは上記のような書き方ができるようになりました Interval を使用する場合 setInterval の戻り値を保持する State を用意しないといけなかった反面、ジェネレータ関数を利用すると State は必要なく簡潔に書けることがわかると思います 後書き 今まであまり触れてこなかったジェネレータ関数を利用することで欲しかった「ちょっとずつ変化していくコンポーネント」を実装することができました 割と最近出た構文(だと思っている)ですが、こういう新しい技術は覚えておくと今まで難しかったことがあっさり解決することがあるんだな〜と勉強になりました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでテトリスをつくる⑧_CSSデザイン

見た目を良くする まず背景を付けた <div onKeyDown={(e) => {keydown(e)}} tabIndex="0" style={{'backgroundColor': '#0e086dc7','position':'absolute','left':'100','top':'100','width':'350px','height':'600px'}}> <Flow disp={disp} colorNum={colorNum}/> <Static base={base}/> </div> 描画コンポーネントは、カラー番号で、色が分かれるようにした function Dot(props){ const color=['transparent','mediumblue','gold','deepskyblue','magenta','lawngreen','','','','gray'][props.colorNum] return <div style={{'width': '28px', 'height': '28px', 'backgroundColor':color,'position':'absolute', 'top':props.y,'left':props.x, 'border': '2px ridge #666'}}></div> } カラー番号を描画コンポーネントに渡せるようにした const Static = React.memo(({...props})=> { let d = [] for(let i = 0; i < props.base.length; i++) { for(let j = 0; j < props.base[i].length; j++) { (props.base[i][j] > 0) ? d.push(<Dot x={j*30} y={i*30} colorNum={props.base[i][j]}/>) : null } } return ( <> {d} </> ) }) 配列の番号で色を変えられるようにした。9が最大値で、9の色はグレー。 const Base=[ [9,9,9,0,0,0,0,0,0,9,9,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,0,0,0,0,0,0,0,0,0,0,9], [9,9,9,9,9,9,9,9,9,9,9,9], ] 色番号を管理するuseStateを追加した const [colorNum, setColorNum]=useState(Math.floor(Math.random() * 5) + 1) だんだん、テトリスらしくなってきている。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ui-componentsに18個のコンポーネントを追加した

こんにちは、Yuiです。 引き続き週イチ発信をしていきます。 過去の週イチ発信は以下 【React + Typescriptで顔認識】tensorflowを使って画像にマスクをかけるアプリを作った 【React + Typescript】ボタン一つでコンポーネントのscssをコピーできるサイトを作った 今回は、新しいアプリのリリースではないのですが、前回作ったui-componentsをより強くするべく、新しいコンポーネントを20個追加と少しバグ修正を行ったのでその報告です。 行ったことは以下です。 バグ修正 4つのボタンを追加 2つのインプットを追加 新しくテキストアニメーションの項目を追加 新しく矢印の項目を追加 新しく吹き出しの項目を追加 中身のコードに関してはこの記事では扱いませんが、サイト上から各コンポーネントをクリックすればコードが見れるので、興味のある人はそこからクリックしてみて下さい。(宣伝) ボタン一つでscssをコピーできるコンポーネント集作りました!個人的によく実装するけど忘れがちなものを集めました。素材はこれからどんどん増やしていきます!頑張って作ったので拡散してもらえると嬉しいです。https://t.co/q9mYT3W4aL pic.twitter.com/xK4Lz8rbyl— Yui ? Yuiko Ito (@yui_active) May 30, 2021 バグ修正 行ったバグ修正は以下です - モーダルが開いているときにどこをクリックしてもモーダルが閉じてしまう 以下の部分です。 今回コードを表示するという性質上、モーダルを開いた状態で一部分だけコピーしたりすることがあるとは思うのですが、今のままではモーダルを開いてどこをクリックしても閉じてしまいます。 それを今回の修正で以下のようにしました。 モーダル内部をクリックしても閉じないように。 ただし、モーダル外部をクリックしたら閉じるというのは個人的に欲しかったので残したままにしてます。 実装方法としては子要素から親要素のクリックを伝播させないようにすれば良いので、内部の子要素に以下の関数をonClick要素で追加しただけの簡単実装です。 const stopPropagation = (e: { stopPropagation: () => void }) => { e.stopPropagation(); }; 参考→https://ja.reactjs.org/docs/events.html ボタンの追加 以下の4つを追加しました インプットの追加 以下の2つを追加しました もっと追加しようと思ったんですが、input要素のレイアウトってそんなにないですね笑 たまに凝ったデザインが来るときがあって、そのときは結構悩んだりしますが、いざ書くとなるとあんまり思いつきませんでした。 テキストアニメーションの追加 新しくTextページを用意して、以下の4つを追加しました 個人的に最後のウネウネしてるやつは気に入っています(どこで使うんだ感もありますが笑) ちなみに今回spanタグで一文字ずつ区切ってるんですが、一文字ずつ等間隔でアニメーションをずらすために毎回&:nth-child(1)、&:nth-child(2)とかでそれぞれ書くのが嫌だったので、なにかいい方法ないかなと調べたら@forを使えるというのを今更ながら知りました。 @for $i from 1 through 5 { &:nth-child(#{$i + 1}) { $delay: $i * 0.1 + s; animation-delay: $delay; } } 参考→SCSS 内でのループ処理 (@for, @while) ただ、さすがにspanで区切ってるとhtml部分が長くなってしまったので、そろそろhtml自体もscssみたいにシンタックスハイライトで見やすくできるようにして、モーダル内でタブ切り分けをできるようにしたほうが良いかもしれないなとは思いました。(イメージはcodepenみたいな感じ) そのうちやると思います。 矢印の追加 以下の4つを追加しました。 アニメーションはないので一気にしょぼくなったように感じます。笑 簡単だけど微妙に実装がめんどいと感じるので入れてみました。 吹き出しの追加 吹き出し実装微妙にめんどいですよね ということで以下の4つを追加しました。 まとめ というわけで、週イチ発信3週目は先週作ったサービスの中身を充実させるという感じになりました。 できれば20個追加とかにしてキリの良い数字で締めたかった感はありますが、もう力尽きたので次回にまわします。 今後も色々追加していこうと思うので、なにかリクエストなどあればお待ちしております!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む