thumbnail

TypeScriptでstyled-components〜基礎から発展的な使い方〜

この記事ではstyled-componentsをTypeScriptで利用する方法と代表的な機能をまとめています。変数、propsやattrsの使用方法、必要な型定義なども説明します。これら基本機能を利用すれば、プログラマブルに、条件分岐等を利用して動的にスタイリングを変更する記述も可能で、拡張性の高いコンポーネントが作成できます。

インストール

npmでのインストール:

npm install --save styled-components @types/styled-components

Typescriptで利用する場合は@types/styled-componentsも必要です。

基本的な使い方〜HTMLタグを元にスタイリング〜

もっとも基本的な使い方は、レンダリングするHTMLタグを指定してスタイルを記述する方法です。

レンダリングするコンポーネントの外側で定義することに注意が必要です。

// <h1>タグをレンダリングする<Title>コンポーネント
const Title = styled.h1`
  font-size: 26px;
  color: red;
  /* このようにCSSを記述していく */
`

// <section>タグをレンダリングする<Wrapper>コンポーネント
const Wrapper = styled.section`
  /* スタイリング */
`

export const App: FC = () =>  {
  return (
    <Wrapper>
      <Title>Hoge</Title>
    </Wrapper>
  )
}

次のようにSCSSライクに記述することも出来ます。&が使えるのでhoverなども楽にかけます。

const Link = styled.a`
  font-weight: bold;
  margin: 10px;
  span {
    color: red;    
  }
  &:hover {
    text-decoration: underline;
  }
`

外部の変数を参照する

外部で定義されている変数を利用することが可能です。

const linkColor = "green"
// linkColorを参照
const Link = styled.a`
  color: ${linkColor}
`
export const App: FC = () =>  {
  return (
    <Link>link</Link>
  )
}

propsを受け取る

styled-componentはpropsを受け取ることが可能で、propsの値によってスタイリングを変更することが可能です。
その際、TypeScriptではpropsの型をジェネリックで渡します。

// styled-componentのprops
type ButtonProps = {
  primary?: boolean
}
// propsを受け取るstyled-component
const Button = styled.button<ButtonProps>`
  background-color: ${({ primary }) => primary ? 'blue' : 'yellow'} ;
  color:  ${({ primary }) => primary ? 'white' : 'black'} ;
`
export const App: FC = () =>  {
  return (
    <Wrapper>
      <Button>Normal</Button>
      <Button primary>Primary</Button>
    </Wrapper>
  )
}

styled-componentの継承(拡張)

すでにあるコンポーネントを継承、拡張することが可能です。
新しいスタイルを追加したり、既存スタイルのオーバーライドも可能です。

const Button = styled.button`
  color: white;
  background-color: red;
`

// Buttonの拡張
const ExtendedButton = styled(Button)`
  color: #000;
  background-color: #fff; 
  border-radius: 0;
`

export const App: FC = () =>  {
  return (
    <Wrapper>
      <Button>Button</Button>
      <Extendedbutton>Extended</Extendedbutton>
    </Wrapper>
  )
}

【as】HTMLタグの種類を変更

asプロパティでHTMLタグを指定すると、レンダリングされるHTMLタグが変わります。
上記の例は<button>タグでレンダリングされていましたが、これを<span>や<a>タグ等に変換することが可能です。

// <Button>は <span>でレンダリングされる
// <ExtendedButton>は <a>でレンダリングされる
export const App: FC = () =>  {
  return (
    <Wrapper>
      <Button as="span">Button</Button> 
      <ExtendedButton as="a">Extended</ExtendedButton>
    </Wrapper>
  )
}

自作のコンポーネントに変更することも可能です。

type Props = {
  children: string
}
const UpperdButton: FC<Props> = ({
  children
}) => (
  <Button children={children.toUpperCase()} />
)
export const App: FC = () =>  {
  return (
    <Wrapper>
      <Button>Button</Button> 
      <Button as={UpperdButton}>Button</Button>
    </Wrapper>
  )
}

カスタムコンポーネントのstyled

classNameの型定義に注意

作ったコンポーネントにスタイルを当てることも可能です。
その際、スタイルを当てるコンポーネントはstring?型のclassNameプロパティを受け取れるようにする必要があります。

// className?と?をつけることに注意
type Props = {
  className?: string
  children: string
}
// スタイルをあてるコンポーネント
const Link: FC<Props> = ({
  className,
  children
}) => (
  <a className={className}>
    { children }
  </a>
)
// スタイリングされたコンポーネント
const StyledLink = styled(Link)`
  color: red;
  font-weight: bold;
`
export const App: FC = () =>  {
  return (
    <StyledLink>
      styled link
    </StyledLink>
  )
}

styled-componentsに渡せるpropsの種類

styledの対象がHTML要素ならば、そのHTML要素がそもそも受け取るプロパティはstyled-componentsになっても受け取ることが可能です。

例えば、styled.inputはinputが受け取ることが出来る「type」や「placeholder」などのプロパティを受け取ることが可能です。

const StyledInput = styled.input`
  padding: 0.5em 1em;
  margin: 0;
  border: 1px solid #ccc;
`
export const App: FC = () =>  {
  return (
    <StyledInput
      type="text"
      placeholder='入力'
    />
  )
}

childrenというプロパティに子要素を渡すことも可能です。

<Button children="Hey!"/>

それ以外のstyled-componentsには任意のpropsを渡すことが可能です。

【attrs】styled-componentsでプロパティを定義

styledを定義するとき、attrsコンストラクタをつけるとプロパティを一緒に定義することが可能です。

const StyledInput = styled.input.attrs({
  type: 'number',
  placeholder: "1",
  min: 1,
  max: 10
})`
  padding: 0.5em 1em;
  margin: 10px;
  border: 1px solid #ccc;
`
export const App: FC = () =>  {
  return (
    <StyledInput/>
  )
}

attrsには関数を渡すことも可能で、動的にプロパティを変更することもできます。
次の例では静的なtypeプロパティに加えて、sizeという動的プロパティを独自に定義して利用しています。

// 関数を取るattrsで動的なプロパティを設定
const StyledInput = styled.input.attrs(props => ({
  type: 'text',
  size: props.size || "1em"
}))`
  padding: ${props => props.size};
  margin: ${props => props.size};
  border: 1px solid tomato;
`
export const App: FC = () =>  {
  return (
    <StyledInput placeholder="input" size="3em"/>
  )
}

attrsをもつコンポーネントを拡張したコンポーネントでは、attrsの上書きが可能です。

// 関数を取るattrsで動的なプロパティを設定
const StyledInput = styled.input.attrs(props => ({
  type: 'text',
  size: props.size || "1em"
}))`
  padding: ${props => props.size};
  margin: ${props => props.size};
  border: 1px solid tomato;
`
// typeがnumberで上書きされる
const MoreStyledInput = styled(StyledInput).attrs({
  type: "number"
})`
  border: 1px solid skyblue;
`
export const App: FC = () =>  {
  return (
    <MoreStyledInput placeholder="3" size="1em"/>
  )
}

【keyframes】アニメーション

keyframesの設定も可能です。

const anime = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg)
  }
`
const Rotate = styled.div`
  animation: ${anime} 1s linear infinite;
`

【css“】独立したcssブロック

css``で独立したCSSのブロックを定義できます。これによって、条件によって適用するCSSを丸ごと切り替える処理などが可能となります。

propsとcssを使用した条件分岐によるスタイリング

type ButtonProps = {
  size: string
  isBold?: boolean
}

const Button = styled.button<ButtonProps>`
  border: 3px solid #000;
  ${({ isBold }) => isBold && css`font-weight: bold` };
  ${({ size }) => {
    switch (size) {
      case 's':
        return css`
          font-size: 12px;
          padding: 6px 12px;
        `
      case 'l':
        return css`
          font-size: 20px;
          padding: 10px 24px;
        `
      default:
        return css`
          font-size: 16px;
          padding: 8px 18px;
        `
    }
  }}
`

export const App: FC = () =>  {
  return (
    <Wrapper>
      <Button size="s">Small</Button>
      <Button size="m">Medium</Button>
      <Button size="l">Large</Button>
      <Button size="l" isBold>Large & Bold</Button>
    </Wrapper>
  )
}

※あくまで機能説明のためのコード例。

【createGlobalStyle】グローバルスタイルの定義

コンポーネントを横断した全体のスタイルはcreateGlobalStyleで作成することが可能です。

import { createGlobalStyle } from "styled-components"
const GlobalStyle = createGlobalStyle`
  html,
  body
  {
    margin: 0;
    padding: 0;
    width: 100%;
    font-size: 18px;
    color: #333;
  }
`
export const App: FC = () =>  {
  return (
    <>
      <GlobalStyle/>
      <SomeComponent/>
      <SomeComponent/>
      <SomeComponent/>
    </>
  )
}

他のstyled-componentsを参照

次のように、styledで定義したコンポーネントは他のコンポーネント内で参照できます。

const Link = styled.a`
  color: red;
  cursor: pointer;
`
const Text = styled.span`
  ${Link}:hover & {
    text-decoration: underline;
  }
`
export const App: FC = () =>  {
  return (
    <Link>
      <Text>Hoge</Text>
    </Link>
  )
}

ただし、参照できるのはstyled-componentsで定義したコンポーネントだけで、カスタムコンポーネントを代入することは不可能です。

テーマ

次の記事で解説しています。

まとめ

以上styled-componentsの代表的な使い方を見てきました。スタイルを拡張したり、プロパティを動的に変更することでスタイルが条件分岐する、プログラマブルなスタイリングが可能であることが理解できたかと思います。

参考