styled-componentsのドキュメントを読んでみましたので投稿させていただきました。間違いなどありましたらご指摘お願いします。m(_ _)m
私が実際に試してみたコードはこちらです。
試したコードをstorybookで公開してみた
styled-components
を使う準備
公式ドキュメントを読む前にreact、styled-componentsの実行環境を作成します。
VSCodeプラグインで見やすくする
こちらのプラグインを使うと、styled-components
にシンタックスハイライトをつけてくれます。
Before
After
webpackで3分で動かせるサンプル
yarn init -y
yarn add react react-dom styled-components
yarn add -D webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react
module.exports = {
mode: process.env.NODE_ENV || "development",
module: {
rules: [
{
test: /\.jsx?$/,
use: ["babel-loader"]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
devServer: {
port: 9000,
contentBase: "./",
publicPath: "/dist/",
open: true
}
};
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Document</title>
<script src="dist/main.js" defer></script>
<div id="root"></div>
import React from "react";
import { render } from "react-dom";
import App from "./App";
render(<App />, document.getElementById("root"));
import React from "react";
import styled from "styled-components";
const App = () => {
return <Container>hello</Container>;
};
const Container = styled.div`
background: pink;
width: 100%;
`;
export default App;
yarn webpack-dev-server #localhost:9000でブラウザを開く
この実行環境でstyled-components
を試していきます。
Basics
Getting Started
styled-components
はタグ付きテンプレートリテラルを利用してコンポーネントをスタイルします。
import React from "react";
import styled from "styled-components";
const App = () => {
return (
<Wrapper>
<Title>Hello World!</Title>
</Wrapper>
);
};
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
export default App;
Adapting based on props
propsを渡すことができます。
import React from "react";
import styled from "styled-components";
const Button = styled.button`
/* Adapt the colors based on primary prop */
background: ${props => (props.primary ? "palevioletred" : "white")};
color: ${props => (props.primary ? "white" : "palevioletred")};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const App = () => {
return (
<div>
<Button>Normal</Button>
<Button primary>Primary</Button>
</div>
);
};
export default App;
Extending Styles
styled()
を使うことで継承することができる
import React from "react";
import styled from "styled-components";
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
const App = () => {
return (
<div>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</div>
);
};
export default App;
as
を使うことでタグ名を指定することができる。
as="a"
と指定している部分はanchorタグで表示してくれています。
import React from "react";
import styled from "styled-components";
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
const App = () => {
return (
<div>
<Button>Normal Button</Button>
<Button as="a" href="/">Link with Button styles</Button>
<TomatoButton as="a" href="/">Link with Tomato Button styles</TomatoButton>
</div>
);
};
export default App;
自分で作成したコンポーネントをas
に指定しても問題ありません。
import React from "react";
import styled from "styled-components";
const Button = styled.button`
display: inline-block;
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
display: block;
`;
const ReversedButton = props => <button {...props} children={props.children.split('').reverse()} />
const App = () => {
return (
<div>
<Button>Normal Button</Button>
<Button as={ReversedButton}>Custom Button with Normal Button styles</Button>
</div>
);
};
export default App;
Styling any component
styled
でコンポーネントを渡す場合はclassName
で渡すとうまく機能してくれます。
import React from "react";
import styled from "styled-components";
const Link = ({ className, children }) => (
<a className={className}>{children}</a>
);
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
const App = () => {
return (
<div>
<Link>Unstyled, boring Link</Link>
<br />
<StyledLink>Styled, exciting Link</StyledLink>
</div>
);
};
export default App;
Passed props
既存のタグであればstyled.div
やstyled.input
でpropsを渡すことができる
import React from "react";
import styled from "styled-components";
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: ${props => props.inputColor || "palevioletred"};
background: papayawhip;
border: none;
border-radius: 3px;
`;
const App = () => {
return (
<div>
<Input defaultValue="@probablyup" type="text" />
<Input defaultValue="@geelen" type="text" inputColor="rebeccapurple" />
</div>
);
};
export default App;
Coming from CSS
How do Styled Components work within a component?
css-modules
とstyled-components
を比較して解説してくれていました。
-
css-modules
を使った場合
module.exports = {
mode: process.env.NODE_ENV || "development",
module: {
rules: [
{
test: /\.jsx?$/,
use: ["babel-loader"]
},
{
test: /\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true
}
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
devServer: {
port: 9000,
contentBase: "./",
publicPath: "/dist/",
open: true
}
};
.counter {
background: pink;
}
.paragraph {
font-weight: bold;
text-align: center;
}
.button {
height: 30px;
width: 50px;
}
import React, { useState } from "react";
import styles from "./styles.css";
const App = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<div className={styles.counter}>
<p className={styles.paragraph}>{count}</p>
<button className={styles.button} onClick={increment}>
+
</button>
<button className={styles.button} onClick={decrement}>
-
</button>
</div>
);
};
export default App;
-
styled-components
を使った場合
import React, { useState } from "react";
import styled from "styled-components";
const StyledCounter = styled.div`
background: pink;
`;
const Paragraph = styled.p`
font-weight: bold;
text-align: center;
`;
const Button = styled.button`
height: 30px;
width: 50px;
`;
const App = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<StyledCounter>
<Paragraph>{count}</Paragraph>
<Button onClick={increment}>+</Button>
<Button onClick={decrement}>-</Button>
</StyledCounter>
);
};
export default App;
Define Styled Components outside of the render method
renderメソッドの外側でスタイル設定されたコンポーネントを定義することが重要です。そうしないと、レンダリングパスごとに再作成されます。
- 良い例
import React from "react";
import styled from "styled-components";
const StyledWrapper = styled.div`
/* ... */
`;
const Wrapper = ({ message }) => {
return <StyledWrapper>{message}</StyledWrapper>;
};
const App = () => {
return <Wrapper message="hello" />;
};
export default App;
- 悪い例
import React from "react";
import styled from "styled-components";
const Wrapper = ({ message }) => {
// WARNING: これは非常に悪いと遅いです、これをしないでください!
const StyledWrapper = styled.div`
/* ... */
`;
return <StyledWrapper>{message}</StyledWrapper>;
};
const App = () => {
return <Wrapper message="hello" />;
};
export default App;
Pseudoelements, pseudoselectors, and nesting
擬似セレクタなども使うことができる
import React from "react";
import styled from "styled-components";
const Thing = styled.button`
color: blue;
::before {
content: "🚀";
}
:hover {
color: red;
}
`;
const App = () => {
return <Thing>Hello world!</Thing>;
};
export default App;
私たちが使っているプリプロセッサ、stylisは、スタイルを自動的にネストするためのscss風の構文をサポートしています。
stylisについてはこちら
&
を使ってメインコンポーネント参照することができる
import React from "react";
import styled from "styled-components";
const Thing = styled.div.attrs((/* props */) => ({ tabIndex: 0 }))`
color: blue;
&:hover {
color: red; // <Thing> when hovered
}
& ~ & {
background: tomato; // <Thing> as a sibling of <Thing>, but maybe not directly next to it
}
& + & {
background: lime; // <Thing> next to <Thing>
}
&.something {
background: orange; // <Thing> tagged with an additional CSS class ".something"
}
.something-else & {
border: 1px solid; // <Thing> inside another element labeled ".something-else"
}
`;
const App = () => {
return (
<>
<Thing>Hello world!</Thing>
<Thing>How ya doing?</Thing>
<Thing className="something">The sun is shining...</Thing>
<div>Pretty nice day today.</div>
<Thing>Don't you think?</Thing>
<div className="something-else">
<Thing>Splendid.</Thing>
</div>
</>
);
};
export default App;
アンパサンドなしでセレクタを配置すると、それらはコンポーネントの子を参照します。
import React from "react";
import styled from "styled-components";
const Thing = styled.div`
color: blue;
.something {
border: 1px solid; // an element labeled ".something" inside <Thing>
display: block;
}
`;
const App = () => {
return (
<Thing>
<label htmlFor="foo-button" className="something">
Mystery button
</label>
<b id="foo-b">What do I do?</b>
</Thing>
);
};
export default App;
Attaching additional props v2
styled-components
にpropsを渡すこともできる
import React from "react";
import styled from "styled-components";
const Input = styled.input.attrs(props => ({
// we can define static props
type: "password",
// or we can define dynamic ones
size: props.size || "1em"
}))`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed prop */
margin: ${props => props.size};
padding: ${props => props.size};
`;
const App = () => {
return (
<>
<Input placeholder="A small text input" />
<br />
<Input placeholder="A bigger text input" size="2em" />
</>
);
};
export default App;
Animations
import React from "react";
import styled, { keyframes } from "styled-components";
// Create the keyframes
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
// Here we create a component that will rotate everything we pass in over two seconds
const Rotate = styled.div`
display: inline-block;
animation: ${rotate} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
const App = () => {
return <Rotate>< 💅 ></Rotate>;
};
export default App;
Advanced Usage
Theming
<ThemeProvider>
を使うと、コンテキストを使用してその下にあるすべてのReactコンポーネントにテーマのpropsを渡すことができる
import React from "react";
import styled, { ThemeProvider } from "styled-components";
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
/* theme.mainで枠線とテキストに色を付け */
color: ${props => props.theme.main};
border: 2px solid ${props => props.theme.main};
`;
// ボタンのデフォルトテーマ
Button.defaultProps = {
theme: {
main: "palevioletred"
}
};
const theme = {
main: "mediumseagreen"
};
const App = () => {
return (
<>
{/* ThemeProviderを使用していないボタン */}
<Button>Normal</Button>
{/* ThemeProviderを使用しているボタン */}
<ThemeProvider theme={theme}>
<Button>Themed</Button>
</ThemeProvider>
</>
);
};
export default App;
Function themes
theme propを親テーマから受け取ることができる
import React from "react";
import styled, { ThemeProvider } from "styled-components";
const Button = styled.button`
color: ${props => props.theme.fg};
border: 2px solid ${props => props.theme.fg};
background: ${props => props.theme.bg};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
`;
// テーマに `fg`と` bg`を定義します
const theme = {
fg: "palevioletred",
bg: "white"
};
//このテーマは `fg`と` bg`を入れ替えます
const invertTheme = ({ fg, bg }) => ({
fg: bg,
bg: fg
});
const App = () => {
return (
<ThemeProvider theme={theme}>
<div>
<Button>Default Theme</Button>
<ThemeProvider theme={invertTheme}>
<Button>Inverted Theme</Button>
</ThemeProvider>
</div>
</ThemeProvider>
);
};
export default App;
Getting the theme without styled components
高次コンポーネント、またはuseContext
でthemeの値を取得することができる
useContext
の例
import React, { useContext } from "react";
import { ThemeProvider, ThemeContext } from "styled-components";
const Button = ({ children }) => {
const themeContext = useContext(ThemeContext);
console.log("Current theme: ", themeContext);
return <div>{children}</div>;
};
Button.defaultProps = {
theme: {
main: "palevioletred"
}
};
const theme = {
main: "mediumseagreen"
};
const App = () => {
return (
<ThemeProvider theme={theme}>
<Button>Themed</Button>
</ThemeProvider>
);
};
export default App;
The theme prop
theme propsを使用してテーマをコンポーネントに渡すこともできます。
import React from "react";
import styled, { ThemeProvider } from "styled-components";
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
/* Color the border and text with theme.main */
color: ${props => props.theme.main};
border: 2px solid ${props => props.theme.main};
`;
const theme = {
main: "mediumseagreen"
};
const App = () => {
return (
<div>
<Button theme={{ main: "royalblue" }}>Ad hoc theme</Button>
<ThemeProvider theme={theme}>
<div>
<Button>Themed</Button>
<Button theme={{ main: "darkorange" }}>Overidden</Button> {/* themeを上書き */}
</div>
</ThemeProvider>
</div>
);
};
export default App;
Refs
refを使うと、元となっているDOMを取得することができる
styled.div
としている場合は<div>
を取得できる
import React, { useRef } from "react";
import styled from "styled-components";
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: palevioletred;
background: papayawhip;
border: none;
border-radius: 3px;
`;
const App = () => {
const inputRef = useRef(null);
return (
<Input
ref={inputRef}
placeholder="Hover to focus!"
onMouseEnter={() => {
inputRef.current.focus();
}}
/>
);
};
export default App;
Security
// ユーザー入力の場合だと、不正なURLとなる場合がある
const userInput = '/api/withdraw-funds'
const ArbitraryComponent = styled.div`
background: url(${userInput});
/* More styles here... */
`
CSS.escape()か、そのpolyfillを使うと解決できる
Existing CSS
styled-componentsは、classを持つ実際のスタイルシートを生成し、className propを介してそれらのclassをスタイル付きコンポーネントのDOMノードに結び付けます。 実行時に、生成されたスタイルシートをドキュメントの
<head>
の末尾に挿入します。
- NG例
Issues with specificity
グローバルクラスをスタイル付きコンポーネントクラスと一緒に適用すると、期待した結果にならない可能性があります。
.red-bg {
background-color: red;
}
import React, { useRef } from "react";
import styled from "styled-components";
import "./styles.css";
const MyComponent = styled.div`
background-color: green;
`;
const App = () => {
const inputRef = useRef(null);
return (
// red-bgで上書きされず、緑色になってしまう
<MyComponent className="red-bg">Hoge</MyComponent>
);
};
export default App;
一つの解決策は、詳細度を上げるようにする
.red-bg {
background-color: red;
}
Referring to other components
他のコンポーネントを参照できます
import React from "react";
import styled from "styled-components";
import "./styles.css";
const Link = styled.a`
display: flex;
align-items: center;
padding: 5px 10px;
background: papayawhip;
color: palevioletred;
`;
const Icon = styled.svg`
flex: none;
transition: fill 0.25s;
width: 48px;
height: 48px;
${Link}:hover & {
fill: rebeccapurple;
}
`;
const Label = styled.span`
display: flex;
align-items: center;
line-height: 1.2;
&::before {
content: "◀";
margin: 0 10px;
}
`;
const App = () => {
return (
<Link href="#">
<Icon viewBox="0 0 20 20">
<path d="M10 15h8c1 0 2-1 2-2V3c0-1-1-2-2-2H2C1 1 0 2 0 3v10c0 1 1 2 2 2h4v4l4-4zM5 7h2v2H5V7zm4 0h2v2H9V7zm4 0h2v2h-2V7z" />
</Icon>
<Label>Hovering my parent changes my style!</Label>
</Link>
);
};
export default App;
<Link>...</Link>
の中であれば、どこをhoverしてもIconの色を変えることができました
Style Objects
CSSの文字列ではなく、オブジェクトで渡すこともできる
import React from "react";
import styled from "styled-components";
// Static object
const Box = styled.div({
background: "palevioletred",
height: "50px",
width: "50px"
});
// Adapting based on props
const PropsBox = styled.div(props => ({
background: props.background,
height: "50px",
width: "50px"
}));
const App = () => {
return (
<>
<Box />
<PropsBox background="blue" />
</>
);
};
export default App;
最後まで読んでいただいてありがとうございましたm(_ _)m