- 投稿日:2020-09-25T23:59:54+09:00
React-Redux 私的まとめ
こちらの記事は以下の書籍を参考にアウトプットとして執筆しました。
React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上までルーティング
実行パターン
URL遷移なし
Store内にpageプロパティを実装するなどで判断
URLハッシュ
取得はlocation.hash
ハッシュが変更をきっかけにしているhistory API
ブラウザの履歴情報を操作することができるAPIを使う手法
ルーティングライブラリ
react-router v4
API
history APIを使う
import { render } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux' import { BrowserRouter as Router, Route } from 'react-router-dom' // 略 render( <Provider store={store} > <Router> <Route path="/" component={App} /> </Router> </Provider>, document.getElementByid('root') )引用:React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで
BrowserRouterより下からRouteが使える
react-router-redux
react-routerを更にreduxに最適化できる
ルーティング情報をStoreのStateで管理Reduxの非同期処理
redux-thunkによる非同期処理
ReduxではAPIからのレスポンスはActionとして扱う
thunkミドルウェア
ミドルウェア適応
import { createStore, applyMiddleware } from 'redux' import logger from 'redux-logger' import thunk from 'redux-thunk' import reducers from './reducers' const middlewares = [logger, thunk] const store = createStore(reducers, applyMiddleware(...middlewares)) //createStoreの第2引数はStoreの初期オプションを与える export default store参考:React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで
import shortid from 'shortid' import * as types from '../types/todo' // 同期Actionクリエータ export function addTodo(title) { return { type: types.ADD_TODO, payload: { id: shortid.generate(), title, } } } // 非同期Actionクリエータ // 関数をリターン export function asyncAddTodo(title) { return (dispatch, getState) => { setTimeout(() => { dispatch(addTodo(title)) }, 1000); } }
- 投稿日:2020-09-25T17:45:54+09:00
VSCodeでwebpack-dev-server上のReactをデバックする
目標
webpack-dev-server上で実行したReactをVSCodeでデバックできるようにする。
環境
- OS
- macOS Catalina 10.15.6
- Windows 10
- VSCode: 1.49.2
- Debugger for Chrome: 4.12.11
- React: 16.13.1
- webpack: 4.43.0
- webpack-dev-server: 3.11.0
Webpackの設定
ソースマップの設定が必要になります。
ソースマップはバンドルする前と後のコードを関連付けてくれるものです。webpackのconfigにdevtoolsを設定するとソースマップが利用できるようになります。
例として自分が設定した値を載せます。webpack.config.jsmodule.exports = { ...略 devtools: 'inline-source-map', devServer: { ...他のパラメータに関しては以下を参考にしてください。
https://webpack.js.org/configuration/devtool/上記config設定後、実行時の引数として
-d
を付与すると、VSCodeでデバック可能なwebpack-dev-serverが立ち上がります。webpack-dev-server -dVSCodeの設定
以下デバックに必要なExtensionsのインストールと、launch.jsonの編集が必要になります。
Debugger for Chrome
https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome
launch.json{ "configurations": [ { "name": "Launch Chrome", "request": "launch", "type": "pwa-chrome", "url": "http://localhost:8080", "webRoot": "${workspaceFolder}" } ] }デバック方法
- webpack-dev-serverを起動
- デバック画面からlaunchを起動
- 起動したChromeで画面操作
- ブレークで止まった際にデバック画面を操作
おわりに
以上でVSCodeでwebpack-dev-server上で動く、Reactがデバックできるようになります。
バンドル化する前のコードで追いかけることが可能になりますので、かなり楽になるのではないでしょうか。
- 投稿日:2020-09-25T15:01:06+09:00
React + TypeScript + vte.cxで簡単なWebアプリを作ってみた④CSV出力編
はじめに
この記事はvte.cx(ブイテックス)というBaaSを使って登録した情報をCSV出力機能を実装してみた記事です。
今回実装した機能
今回実装したのは特定のURLにアクセスすると上記のようなCSVが出力される機能です。
今回出てくる難しいIT用語録(分かる人は読み飛ばして大丈夫です)
CSV is 何
名前と見た目はなんとなく知っていたのですがそれがどういうものかうまく説明できなかったので調べてみました。
comma-separated values(略称:CSV)は、いくつかのフィールド(項目)をカンマ「,」で区切ったテキストデータおよびテキストファイル。拡張子は .csv、MIMEタイプは text/csv。
「comma-separated variables」とも言う。広く普及した訳語はないが、「カンマ区切り」などとも呼ばれる。Microsoft Excelでは「CSV (カンマ区切り)」としている。
(中略)
引用:Wikipedia|Comma-Separated Valuesカンマ区切りされたファイルらしいですね。
ちなみにExcelで開けるそうなのですが、
CSVファイルはExcelでも参照・編集が可能です。しかし、CSVファイルをExcelで編集する時には注意が必要です。
CSVファイルには、ExcelやDBなどにあるデータ型の情報が入っていません。抽出元のシステムのデータ型が文字列型、数値型、日付型などに関係なく全て文字情報として出力されています。この様なCSVファイルをExcelで普通に開いた場合、Excelの解釈でデータ型が設定され、結果、下記のような元のCSVファイルの情報と違う形で表示されてしまいます。(例は上記のCSVファイルをExcelで開いた場合の例です)型は全て文字列なのでExcelに合わせる際に不具合がでる可能性があるそうです。
api is 何
「API」とは、「Application Programming Interface」の頭文字です。
大雑把に言うと「アプリケーションをプログラミングするためのインターフェース」という意味です。
インターフェイスとは、コンピュータ用語でいうと、「何か」と「何か」をつなぐものという意味を持ちます。例えば、USBも「パソコン」と「周辺機器」をつなぐものですので、インターフェイスの一つです。
つまり、APIとは、この「何か」と「何か」が「アプリケーション、ソフトウェア」と「プログラム」をつなぐもの、という意味になります。
アプリとアプリを繋げることによって、機能性を拡張させ、さらに便利に使えるようにし、欲を言えば両方のアプリにとってウィン・ウィンの状態を生み出すのがAPIの狙いです。
出典:データのじかん
簡単にいうと外部の人が作ったプログラムをとってきて自分のコードのなかで使うといったものです。
サーバーサイドレンダリング is 何
本来JavaScriptで行う画面の書き換え処理などをサーバー側で実行させて、ユーザーの待機時間を短くすること。
PHPなどを使ってwebアプリケーションを作成した場合もSSRではあるが、
あえて「SSR」と呼ぶときは、上記のようにJavaScriptと対比させて表現することが多い。画面表示など、ブラウザでやると時間のかかる処理を見えないところでやっちゃおうとったものですね。
手順
ではCSV出力の仕方を説明していきます。
vtecxを使ったcsv出力ではサーバーサイドJavascriptを使用する必要があります。
vte.cxによるバックエンドを不要にする開発(7.サーバサイドJavaScript)
こちらの記事を参考に進めていきます。
大まかな流れとしては
①プロジェクト直下のserverフォルダの中に{ファイル名}.csv.tsxという名前のファイルを作る。
②以下のコマンドでサーバにデプロイする。
npm run watch:server -- --env.entry=/server/ファイル名.tsx③ブラウザから
http://{サービス名}/s/ファイル名.csv
を開くでは実際作ったコードを使って一連の流れを進めていきます。
get-csv-users-information.csv.tsx(csv出力をするファイル)
server/get-csv-users-information.csv.tsximport * as vtecxapi from 'vtecxapi' const feed_data = vtecxapi.getFeed('/users') // 配列で帰ってくる const users_info_body = feed_data.map((entry: any) => { console.log(JSON.stringify(entry.users)) // 名前 const name = entry.users.name || '未登録' // 性別 const gender = entry.users.gender || '未登録' // 年齢 const age = entry.users.name || '未登録' // 住所 const address = entry.users.address || '未登録' // パスワード const password = entry.users.password || '未登録' // メールアドレス const email = entry.users.email || '未登録' // 郵便番号 const post_number = entry.users.post_number || '未登録' // 好きな住居形態 const like_residence_type = entry.users.like_residence_type || '未登録' // 役職 const position = entry.users.position || '未登録' // 使用言語 const language = entry.users.language ? `"${entry.users.language}"` : '未登録' const array: string[] = [] array.push(name) array.push(gender) array.push(age) array.push(address) array.push(password) array.push(email) array.push(post_number) array.push(like_residence_type) array.push(position) array.push(language) return array }) //ヘッダー const user_info_header = ['名前', '性別', '年齢', '住所', 'パスワード', 'メールアドレス', '郵便番号', '好きな住居形態', '役職', '使用言語'] let body: any = [user_info_header, ...users_info_body] // CSV出力 vtecxapi.doResponseCsv(body, 'summary_index_' + '.csv')まず最初に
server/get-csv-users-information.csv.tsximport * as vtecxapi from 'vtecxapi' const feed_data = vtecxapi.getFeed('/users'としてサーバーサイドレンダリングするためのapiをimportし、vtecxapi.getFeed()というメソッドを使い、エンドポイントに登録されているデータを取得します。
今回サーバーサイドJSということでブラウザの開発者ツールでconsole.logできなく、「どうすれば登録しているデータを確認することができるんだ...」と思っていましたが、
vtecxの管理画面に入り、ログを見ることで確認できました。
server/get-csv-users-information.csv.tsxconst users_info_body = feed_data.map((entry: any) => { // 名前 const name = entry.users.name || '未登録' // 性別 const gender = entry.users.gender || '未登録' // 年齢 const age = entry.users.name || '未登録' // 住所 const address = entry.users.address || '未登録' // パスワード const password = entry.users.password || '未登録' // メールアドレス const email = entry.users.email || '未登録' // 郵便番号 const post_number = entry.users.post_number || '未登録' // 好きな住居形態 const like_residence_type = entry.users.like_residence_type || '未登録' // 役職 const position = entry.users.position || '未登録' // 使用言語 const language = entry.users.language ? `"${entry.users.language}"` : '未登録' const array: string[] = [] array.push(name) array.push(gender) array.push(age) array.push(address) array.push(password) array.push(email) array.push(post_number) array.push(like_residence_type) array.push(position) array.push(language) return array }) //ヘッダー const user_info_header = ['名前', '性別', '年齢', '住所', 'パスワード', 'メールアドレス', '郵便番号', '好きな住居形態', '役職', '使用言語'] let body: any = [user_info_header, ...users_info_body] // CSV出力 vtecxapi.doResponseCsv(body, 'summary_index_' + '.csv')次に先ほど取得したデータをmapメソッドを使って必要なデータを配列に格納していきます。
(mapメソッドは取得したデータを自分の意図した通りにすることができるフロントエンド開発に必須のものなのでしっかりマスターしたいものです。)
取得したデータはmapメソッドによって
users_info_body = [['name','gender','age',.....'language'],['名前','未登録',.....'日本語']]といった構造になります。
それでここからがCSV出力の本題です。
vtecxapi.doResponseCsv(出力したい配列,CSVのファイル名)というメソッドを使うことでurlアクセス時にCSVを出力することができます。
出力したい配列なのですが、例えば
const date_array: any = ['集計期間'] let body: any = [date_array, ['a', 'b', 'c'], ['aa', 'bb', 'cc']] // CSV出力 vtecxapi.doResponseCsv(body, 'summary_index_' + '.csv')とすると、
このようなCSVファイルを出力することができます
配列一つが1行になって左づめで入力されていきます。
また第二引数のファイルネームはsummary_index.csvという名前で指定しているのでちゃんとその名前の通り出力されました。感想
登録された情報を取得してCSVに出力する機能はfirebaseにはない機能で、vte.cxというサービス内で用意されているのでかなり使い勝手が良いなと思いました。
集計処理をするアプリなどとても作りやすそうです。
- 投稿日:2020-09-25T14:38:14+09:00
Reactコンポーネントの状態を確認するにはReact Developer Toolsを使う
- 投稿日:2020-09-25T12:11:12+09:00
【Gatsby.js】jsonファイルのデータをGraphQL経由で取得する
この記事ではjsonファイルからGraphQL経由でデータを取得し、ページ内で利用する方法を紹介します。
jsonファイルを元に、複数ページを生成する方法を知りたい場合は昨日書いた以下の記事を参照。
【Gatsby.js】単一のjsonファイルを元に複数のページを生成する - Qiitajsonファイルを配置
$ mkdir src/data $ touch src/data/events.jsonsrc/data/events.json[ { "title": "Gatsby勉強会", "date": "2020/10/01" }, { "title": "React勉強会", "date": "2020/11/01" }, { "title": "JavaScript勉強会", "date": "2020/12/01" } ]プラグインをインストール
$ yarn add gatsby-transformer-json gatsby-source-filesystem
gatsby-config.jsmodule.exports = { plugins: [ `other-plugin-hoge`, //追記↓ `gatsby-transformer-json`, { resolve: `gatsby-source-filesystem`, options: { name: `data`, path: `${__dirname}/src/data/`, }, }, ], }クエリを作成
gatsby develop
で開発サーバーを起動し、http://localhost:8000/___graphql?query にアクセスしGraphiQLを開く。
まずはroot?にallEventsJson
という項目が追加されていることを確認。
allEventJson > edges > node
を開き、title
とdate
にチェックを入れてクエリを発行。データを使いたいページ内で、データを取得
ここでは
src/pages/events.js
というページでデータを利用します。$ touch src/pages/events.jssrc/pages/events.jsimport React from "react" import { graphql, useStaticQuery } from "gatsby" export default () => { const data = useStaticQuery(graphql` query { allEventsJson { edges { node { date title } } } } `) const events = data.allEventsJson.edges return ( <div> {events.map(e => ( <div> <h2>{e.node.title}</h2> <p>{e.node.date}</p> </div> ))} </div> ) }結果
参考
- 投稿日:2020-09-25T07:16:02+09:00
Styled-componentsでシンプルにグリッドレイアウトを実装する
はじめに
ReactとStyled-compomentsを使って、シンプルに12カラムのグリッドレイアウトを実装してみました。
カラムレイアウトにはFlexboxを使っています。
Reactの環境構築はcreate-react-appで行います。なお、Styled-compomentsの概要については省略します。
グリッドレイアウトの概要
- 12カラムのグリッドに沿って、横並びやレスポンシブのアイテムを配置していきます。
- 各カラムの左右にはガター(溝)のpaddingがあります。12カラム全体では左右に(ガター/2)分のpaddingとなります。
グリッドレイアウトについてあまり聞きなれない場合は下記リンク等が参考になると思います。
Bootstrapのグリッドシステムの使い方を初心者に向けておさらいする
Grid system - Bootstrap 4.5 - 日本語リファレンス1. Reactプロジェクトの準備
CRAをTypeScript付きでプロジェクト作成し、Styled-compoments関連のパッケージもインストールします。
$ npx create-react-app grid-layout-styled-components --typescript $ cd grid-layout-styled-components $ npm install --save styled-components $ npm install @types/styled-components $ npm install --save-dev babel-plugin-styled-components次にbabel-plugin-styled-componentsを使うための準備をします。
$ touch .babelrc.babelrc
{ "plugins": [ [ "babel-plugin-styled-components" ] ] }最後にindex.cssとApp.cssの中身を一旦空にしておきます。
2. グローバルスタイルの作成
グローバルに適用するスタイルを作成します。
$ touch src/GlobalStyle.tsはじめにリセットCSSを追加します。好みによりNormalizeCSSでも可能だと思います。
リセットCSSの中では個人的にEric Meyer氏のものがシンプルで好きです。GlobalStyle.tsimport { createGlobalStyle } from 'styled-components/macro'; export const GlobalStyle = createGlobalStyle` /* Reset CSS */ /* ===================================== */ /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } } `;次にタイプセレクタへのCSSを追加します。
ここで必須なのは*, *::before, *::after{box-sizing: border-box;}
になります。ついでに必要ないとは思いますが、フォントはいつでも綺麗にしておきたい性分なので
body
へのfont-family
設定も癖で追加しました。GlobalStyle.tsimport { createGlobalStyle } from 'styled-components/macro'; export const GlobalStyle = createGlobalStyle` /* Reset CSS */ /* ===================================== */ /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } /* Add Global CSS */ /* ===================================== */ *, *::before, *::after { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } `;3. 定数の作成
App.tsxに書いていきます。以下を用意します。
- ブレークポイント
- 各ブレークポイントに対応するコンテナの最大幅
- グリッドの溝
- カラム数
App.tsximport React from 'react'; import styled from 'styled-components/macro'; import { GlobalStyle } from './GlobalStyle'; import './App.css'; // configs const breakpoints: { sm: number; md: number; lg: number; xl: number } = { sm: 576, md: 768, lg: 992, xl: 1280, }; const containerMaxWidths: { sm: number; md: number; lg: number; xl: number } = { sm: 540, md: 720, lg: 960, xl: 1250, }; const gridColumns: number = 12; const gridGutterWidth: number = 32; // components function App() { return <div><GlobalStyle /></div>; } export default App;4. 汎用コンポーネント作成
汎用的に使うコンテナのコンポーネントと、グリッド行・グリッド列のコンポーネントを用意します。
実装にあたりBootstrapのscssやcssのソースを参考にしました。
全て理解するのは難解ですが、一つ一つは(Bootstrapを使うかに関係なく)CSSの設計に勉強になることが多いと感じます。App.tsximport React from 'react'; import styled from 'styled-components/macro'; import { GlobalStyle } from './GlobalStyle'; import './App.css'; // configs const breakpoints: { sm: number; md: number; lg: number; xl: number } = { sm: 576, md: 768, lg: 992, xl: 1280, }; const containerMaxWidths: { sm: number; md: number; lg: number; xl: number } = { sm: 540, md: 720, lg: 960, xl: 1250, }; const gridColumns: number = 12; const gridGutterWidth: number = 32; // components const Container = styled.div` max-width: 100%; @media (min-width: ${breakpoints.sm}px) { max-width: ${containerMaxWidths.sm}px; } @media (min-width: ${breakpoints.md}px) { max-width: ${containerMaxWidths.md}px; } @media (min-width: ${breakpoints.lg}px) { max-width: ${containerMaxWidths.lg}px; } @media (min-width: ${breakpoints.xl}px) { max-width: ${containerMaxWidths.xl}px; } padding-right: ${gridGutterWidth / 2}px; padding-left: ${gridGutterWidth / 2}px; margin-right: auto; margin-left: auto; `; const Row = styled.div` display: flex; flex-wrap: wrap; margin-right: ${-gridGutterWidth / 2}px; margin-left: ${-gridGutterWidth / 2}px; `; type ColProps = { sizeDefault: number; sizeSm?: number; sizeMd?: number; sizeLg?: number; sizeXl?: number; }; const Col = styled.div<ColProps>` flex: 0 0 ${(props) => (props.sizeDefault / gridColumns) * 100}%; max-width: ${(props) => (props.sizeDefault / gridColumns) * 100}%; @media (min-width: ${breakpoints.sm}px) { flex: 0 0 ${(props) => ((props.sizeSm || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeSm || props.sizeDefault) / gridColumns) * 100}%; } @media (min-width: ${breakpoints.md}px) { flex: 0 0 ${(props) => ((props.sizeMd || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeMd || props.sizeDefault) / gridColumns) * 100}%; } @media (min-width: ${breakpoints.lg}px) { flex: 0 0 ${(props) => ((props.sizeLg || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeLg || props.sizeDefault) / gridColumns) * 100}%; } @media (min-width: ${breakpoints.xl}px) { flex: 0 0 ${(props) => ((props.sizeXl || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeXl || props.sizeDefault) / gridColumns) * 100}%; } padding-right: ${gridGutterWidth / 2}px; padding-left: ${gridGutterWidth / 2}px; `; function App() { return <div><GlobalStyle /></div>; } export default App;ここで各コンポーネントの解説をします。
なお前提として、モバイルファーストで作っています。
Container
コンポーネント幅はブレークポイントに対応する最大幅を設定します。
中央寄せした上で、グリッド溝/2を左右のpadding
に与えます。
Row
コンポーネントはじめに
display
をflex
にセットして、
グリッド溝/2のネガティブマージンを左右のmargin
に与えます。
Col
コンポーネント幅は12グリッドのいくつ分を占めるかをpropsとして受け取れるようにします。
デフォルトの幅は必須で、sm/md/lg/xl用の幅は任意とします。※sizeDefaultの名前は最初sizeとする予定だったのですが、元々HTMLにsizeという属性があるようなので止めました。
flexの他にmax-widthにおいても幅を設定していますが、前者だけだと特定ブラウザで動かないようなので後者も設定する必要があるらしいです。(Bootstrapのソースコメントによると)
この辺りは申し訳無いのですが調べずにスルーしています。余談
なるべくシンプルにという方針で、多少1つのブロックにつき繰り返しは多くなりそうですが
css prop
を使わない(Sassでいうmixin
のような用法)方針で実装しました。5. 実際に使ってみる
前章のコンポーネントを使ってみます。
グリッドを適用する場合はRowを記述し、その子にColをサイズのpropsと共に指定します。App.css.side-border { border-right: 1px solid #000; border-left: 1px solid #000; }App.tsximport React from 'react'; import styled from 'styled-components/macro'; import { GlobalStyle } from './GlobalStyle'; import './App.css'; // configs const breakpoints: { sm: number; md: number; lg: number; xl: number } = { sm: 576, md: 768, lg: 992, xl: 1280, }; const containerMaxWidths: { sm: number; md: number; lg: number; xl: number } = { sm: 540, md: 720, lg: 960, xl: 1250, }; const gridColumns: number = 12; const gridGutterWidth: number = 32; // components const Container = styled.div` max-width: 100%; @media (min-width: ${breakpoints.sm}px) { max-width: ${containerMaxWidths.sm}px; } @media (min-width: ${breakpoints.md}px) { max-width: ${containerMaxWidths.md}px; } @media (min-width: ${breakpoints.lg}px) { max-width: ${containerMaxWidths.lg}px; } @media (min-width: ${breakpoints.xl}px) { max-width: ${containerMaxWidths.xl}px; } padding-right: ${gridGutterWidth / 2}px; padding-left: ${gridGutterWidth / 2}px; margin-right: auto; margin-left: auto; `; const Row = styled.div` display: flex; flex-wrap: wrap; margin-right: ${-gridGutterWidth / 2}px; margin-left: ${-gridGutterWidth / 2}px; `; type ColProps = { sizeDefault: number; sizeSm?: number; sizeMd?: number; sizeLg?: number; sizeXl?: number; }; const Col = styled.div<ColProps>` flex: 0 0 ${(props) => (props.sizeDefault / gridColumns) * 100}%; max-width: ${(props) => (props.sizeDefault / gridColumns) * 100}%; @media (min-width: ${breakpoints.sm}px) { flex: 0 0 ${(props) => ((props.sizeSm || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeSm || props.sizeDefault) / gridColumns) * 100}%; } @media (min-width: ${breakpoints.md}px) { flex: 0 0 ${(props) => ((props.sizeMd || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeMd || props.sizeDefault) / gridColumns) * 100}%; } @media (min-width: ${breakpoints.lg}px) { flex: 0 0 ${(props) => ((props.sizeLg || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeLg || props.sizeDefault) / gridColumns) * 100}%; } @media (min-width: ${breakpoints.xl}px) { flex: 0 0 ${(props) => ((props.sizeXl || props.sizeDefault) / gridColumns) * 100}%; max-width: ${(props) => ((props.sizeXl || props.sizeDefault) / gridColumns) * 100}%; } padding-right: ${gridGutterWidth / 2}px; padding-left: ${gridGutterWidth / 2}px; `; // Usage const Heading = styled.h1` font-size: 32px; font-weight: bold; padding: 24px 0; `; type InnerContentProps = { height: number; backgroundColor: string }; const InnerContent = styled.div<InnerContentProps>` height: ${(props) => props.height}px; background-color: ${(props) => props.backgroundColor}; `; function App() { return ( <div> <GlobalStyle /> <Container className="side-border"> <Heading>12 Grid system</Heading> <Row> {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((v, index) => ( <Col key={index} sizeDefault={1}> <InnerContent height={600} backgroundColor="deepskyblue"></InnerContent> </Col> ))} </Row> <Heading>Responsive 1</Heading> <Row> <Col sizeDefault={12} sizeMd={4} sizeLg={4} sizeXl={4}> <InnerContent height={200} backgroundColor="lightgray"></InnerContent> </Col> <Col sizeDefault={12} sizeMd={4} sizeLg={4} sizeXl={4}> <InnerContent height={200} backgroundColor="darkgray"></InnerContent> </Col> <Col sizeDefault={12} sizeMd={4} sizeLg={4} sizeXl={4}> <InnerContent height={200} backgroundColor="gray"></InnerContent> </Col> </Row> <Heading>Responsive 2</Heading> <Row> <Col sizeDefault={12} sizeMd={12} sizeLg={4} sizeXl={4}> <InnerContent height={300} backgroundColor="gold"></InnerContent> </Col> <Col sizeDefault={12} sizeMd={12} sizeLg={8} sizeXl={8}> <InnerContent height={300} backgroundColor="goldenrod"></InnerContent> </Col> </Row> <Heading>Responsive 3</Heading> <Row> <Col sizeDefault={12} sizeLg={8} sizeXl={8}> <Row> <Col sizeDefault={6}> <InnerContent height={150} backgroundColor="blue"></InnerContent> </Col> <Col sizeDefault={6}> <InnerContent height={150} backgroundColor="darkblue"></InnerContent> </Col> <Col sizeDefault={6}> <InnerContent height={150} backgroundColor="dodgerblue"></InnerContent> </Col> <Col sizeDefault={6}> <InnerContent height={150} backgroundColor="royalblue"></InnerContent> </Col> </Row> </Col> <Col sizeDefault={12} sizeLg={4} sizeXl={4}> <InnerContent height={300} backgroundColor="crimson"></InnerContent> </Col> </Row> </Container> </div> ); } export default App;上記を出力するとこのようになります。
実際の動きは下記GitHubからリンクを参照してください。