LoginSignup
25
22

More than 3 years have passed since last update.

styled-componentsのドキュメントを読んで基本を勉強してみた

Posted at

styled-componentsのドキュメントを読んでみましたので投稿させていただきました。間違いなどありましたらご指摘お願いします。m(_ _)m

私が実際に試してみたコードはこちらです。

試したコードをstorybookで公開してみた

styled-componentsを使う準備

公式ドキュメントを読む前にreact、styled-componentsの実行環境を作成します。

VSCodeプラグインで見やすくする

こちらのプラグインを使うと、styled-componentsにシンタックスハイライトをつけてくれます。

Before

Screen Shot 2019-07-21 at 5.02.52.png

After

Screen Shot 2019-07-21 at 5.03.10.png

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
webpack.config.js
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
  }
};
.babelrc
{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}
index.html
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Document</title>
<script src="dist/main.js" defer></script>
<div id="root"></div>
src/index.jsx
import React from "react";
import { render } from "react-dom";
import App from "./App";

render(<App />, document.getElementById("root"));
src/App.jsx
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でブラウザを開く

Screen Shot 2019-07-21 at 5.06.25.png


この実行環境で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;

Screen Shot 2019-07-21 at 5.36.19.png

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;

Screen Shot 2019-07-21 at 5.46.49.png

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;

Screen Shot 2019-07-21 at 6.22.30.png

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;

Screen Shot 2019-07-21 at 6.24.13.png

自分で作成したコンポーネントを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;

Screen Shot 2019-07-21 at 6.27.14.png

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;

Screen Shot 2019-07-21 at 6.56.01.png

Passed props

既存のタグであればstyled.divstyled.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;

Screen Shot 2019-07-21 at 7.07.40.png

Coming from CSS

How do Styled Components work within a component?

css-modulesstyled-componentsを比較して解説してくれていました。

  • css-modulesを使った場合
webpack.config.js
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
  }
};
styles.css
.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;

Screen Shot 2019-07-21 at 7.39.52.png

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;

Screen Shot 2019-07-21 at 8.04.19.png

私たちが使っているプリプロセッサ、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&apos;t you think?</Thing>
      <div className="something-else">
        <Thing>Splendid.</Thing>
      </div>
    </>
  );
};

export default App;

Screen Shot 2019-07-21 at 8.13.19.png

アンパサンドなしでセレクタを配置すると、それらはコンポーネントの子を参照します。

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;

Screen Shot 2019-07-21 at 8.34.35.png

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>&lt; 💅 &gt;</Rotate>;
};

export default App;

45HHih9Gt1.gif

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;

Screen Shot 2019-07-21 at 14.07.00.png

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;

Screen Shot 2019-07-21 at 14.35.41.png

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;

Screen Shot 2019-07-21 at 14.52.18.png

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;

Screen Shot 2019-07-21 at 14.57.55.png

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;

Screen Shot 2019-07-21 at 15.09.16.png

Security

// ユーザー入力の場合だと、不正なURLとなる場合がある
const userInput = '/api/withdraw-funds'

const ArbitraryComponent = styled.div`
  background: url(${userInput});
  /* More styles here... */
`

CSS.escape()か、そのpolyfillを使うと解決できる

Screen Shot 2019-07-21 at 15.23.17.png

Existing CSS

styled-componentsは、classを持つ実際のスタイルシートを生成し、className propを介してそれらのclassをスタイル付きコンポーネントのDOMノードに結び付けます。 実行時に、生成されたスタイルシートをドキュメントの<head>の末尾に挿入します。

  • NG例
Issues with specificity

グローバルクラスをスタイル付きコンポーネントクラスと一緒に適用すると、期待した結果にならない可能性があります。

styles.css
.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;

Screen Shot 2019-07-21 at 15.44.13.png

一つの解決策は、詳細度を上げるようにする

styles.css
.red-bg {
    background-color: red;
}

Screen Shot 2019-07-21 at 15.50.16.png

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;

MHBZQcOlsc.gif

<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;

Screen Shot 2019-07-21 at 16.31.14.png


最後まで読んでいただいてありがとうございましたm(_ _)m

25
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
22