JavaScript
reactjs
React
reactnative
styled-components

styled components documentation日本語訳①ーBasicsー

概要

styled componentsのdocumentation日本語訳がなかったので翻訳する。
Basics、Advancedは翻訳確実、ほかは気が向いたら翻訳する。
以下翻訳。

Basics

動機

styled-componentはReact componentシステムにおけるCSSをどうやったら強化できるだろうかと悩んだ結果である。
個々の利用例にフォーカスし、開発者とエンドユーザの両方にとって最適な体験を構築することができた。

開発者向けに改善された体験に加えて、styled-componentsは以下の特徴を提供する。

  • Automatic critical CSS:
    styled-componentsはページにどのcomponentsがrenderされたかを追跡するとともに、彼らが保持するstyleだけを注入する。完全に自動的に。コードが分離できることにより、ユーザーは必要に応じて最小限のコードのみをロードする。

  • No class name bugs:
    styled-componentsは各々のスタイルにユニークなnameを生み出す。重複について心配する必要はもうない。

  • Easier deletion of CSS:
    コードベースのどこかであるクラス名が使われているかを知ることは難しい。styeld-componentsはこれを明らかにする。なぜならどのstylingもある特定のcomponentに紐付いているからだ。
    もしcomponentが利用されなくなり(toolingが特定できる)、デリートされたとき、componentとともに紐づくstyleもすべてデリートされる。

  • Simple dynamic styling:
    componentのstyleをpropsやglobal themeに基づいて適用することはたくさんのclassesを手動で管理する必要のないシンプルで直感的なものである。

  • Painless maintenance:
    ファイルをまたいでcomponentに影響するstyleを探し出す必要はもうない。したがってどれだけコードベースが大きくてもメンテナンスは朝飯前だ。

  • Automatic vendor prefixing:
      現行基準のCSSを書き、残りはstyled-coponentsにまかせてしまおう。

各々のcomponentsに紐付けるだけで、熟知した愛すべきCSSを書いていることがこれらすべての恩恵を受けられる。

インストール

訳す価値ないので飛ばす。
ここを見てください。

Gettin Started

styled-componetsはcomponentsをsylingするためにタグのリテラルを利用する。
これによりcomponentとstyle間のマッピングが不要になる。
すなわち、styleを定義するとき実際には普通のReact componentを作っているのと変わらない。ただし、それはstyleを持っている。

この例はwrapperとtitleという2つのシンプルなstyle付きcomponentを作成している。

// Create a Title component that'll render an <h1> tag with some styles
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

// Use Title and Wrapper like any other React component – except they're styled!
render(
  <Wrapper>
    <Title>
      Hello World!
    </Title>
  </Wrapper>
);

styled component1.png

documentationでは実際にコードに触れるので遊んで見てほしい。以下同様。

注意:ベンダープレフィックスはstyled-componentsよって自動適用されている。

propsの適用

functionをテンプレート構文を用いてstyled-componentsのリテラルに渡し、propsに基づいてstyleを適用することができる。

このボタンは色を変えるためのprimary stateを持っている。
primary propをtrueに設定すると、backgroundとtext colorをスワップすることができる。

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

render(
  <div>
    <Button>Normal</Button>
    <Button primary>Primary</Button>
  </div>
);

styled component 2.png

styleの拡張

たった一回の使用のためだがわずかにcomponentを変化させ使いたいというときが頻繁にあると思う。
今まではテンプレート構文でfunctionを渡し、いくつかのpropsに基づきcomponentを変化させていたはずだ。
しかしそれにはstyleを一回変えるだけに多大な労力を要している。

別のcomponentをstyleを受け継ぐ新しいcomponentを簡単に作成するためには、ただstyled() constructorでラップしてあげれば良い。
ここでは一つ前の章で作ったボタンをベースに色のcolorのstyleを拡張した特別なボタンを作ってみる。

// The Button from the last section without the interpolations
const Button = styled.button`
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

// A new component based on Button, but with some override styles
const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

render(
  <div>
    <Button>Normal Button</Button>
    <TomatoButton>Tomato Button</TomatoButton>
  </div>
);

styled component 3.png

2つのruleを付け加えるだけで、Buttonに似ている新しいTomatoButtonを作ることができた。

いくつかの場面でstyled componentがrenderするtagやcomponent
を変えたいときがある。
個々にstylingされているanchor linkやbuttonが入り交じるnavigation barを構築するときなどが典型例だ。

こういう場合のため回避法を用意している。
"as"というポリモーフィックを司るpropを使えば作成したstyleを受け取る要素を動的に変更することができる。

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

const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

render(
  <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>
);

styled component 4.png

カスタムcomponentに対しても完璧に動作する!

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

const ReversedButton = props => <button {...props} children={props.children.split('').reverse()} />

render(
  <div>
    <Button>Normal Button</Button>
    <Button as={ReversedButton}>Custom Button with Normal Button styles</Button>
  </div>
);

styled component 5.png

Styled Component

styleのターゲットが単純な要素(styled.divなど)なら、styled-componentsはHTMLの属性と知られるすべての値をDOMにパスする。
カスタムReact component(styled(Mycomponent)など)なら、styled-componentsはすべてのpropsをパスする。

この例はReact elements同様、Input componentのすべてのpropsがどのようにしてマウントされたDOM nodeに受け渡されるかを示している。

// Create an Input component that'll render an <input> tag with some styles
const Input = styled.input`
  padding: 0.5em;
  margin: 0.5em;
  color: ${props => props.inputColor || "palevioletred"};
  background: papayawhip;
  border: none;
  border-radius: 3px;
`;

// Render a styled text input with the standard input color, and one with a custom input color
render(
  <div>
    <Input defaultValue="@probablyup" type="text" />
    <Input defaultValue="@geelen" type="text" inputColor="rebeccapurple" />
  </div>
);

styled compnent 6.png

これがinputColor propがDOMに渡されないが、typedefaultValueはDOMに渡される仕組みである。
それが自動的に標準でないattributesをフィルタしてくれるstyled-componentsである。

Coming from CSS

コンポーネントないでStyled Componentがどのように動いているか

もしcomponentsにCSSをインポートすること(CSSModulesなど)に精通しているなら、このような操作に慣れているだろう。

import React from 'react'
import styles from './styles.css'

export default class Counter extends React.Component {
  state = { count: 0 }

  increment = () => this.setState({ count: this.state.count + 1 })
  decrement = () => this.setState({ count: this.state.count - 1 })

  render() {
    return (
      <div className={styles.counter}>
        <p className={styles.paragraph}>{this.state.count}</p>
        <button className={styles.button} onClick={this.increment}>
          +
        </button>
        <button className={styles.button} onClick={this.decrement}>
          -
        </button>
      </div>
    )
  }
}

Styled Componentはstyleするelementとruleの組み合わせだから、Counter をこのように書こうと思う。

import React from 'react'
import styled from 'styled-components'

const StyledCounter = styled.div`
  /* ... */
`
const Paragraph = styled.p`
  /* ... */
`
const Button = styled.button`
  /* ... */
`

export default class Counter extends React.Component {
  state = { count: 0 }

  increment = () => this.setState({ count: this.state.count + 1 })
  decrement = () => this.setState({ count: this.state.count - 1 })

  render() {
    return (
      <StyledCounter>
        <Paragraph>{this.state.count}</Paragraph>
        <Button onClick={this.increment}>+</Button>
        <Button onClick={this.decrement}>-</Button>
      </StyledCounter>
    )
  }
}

ここで"Styled"という接頭辞をStyledCounterに加えたことに注意してほしい。
これはReact componentのCounterとStyled ComponentのStyledCounterの名前が衝突せず、かつRect Developer ToolsとWeb Inspectorで用意に判別できるようにするためのものである。

Styled Componentはrenderメソッドの外側で定義しよう

renderメソッドの外でstyled componentsを定義することが重要である。
さもなければrenderの過程ごとに作り直されることになる。
renderメソッドの中にstyled componentを定義することはキャッシュを妨げrenderのスピードを劇的に低下させる。
したがって回避すべきことだ。

このようにstyled componentsを書くことが推奨される。
``javascript
const StyledWrapper = styled.div

/* ... */
`

const Wrapper = ({ message }) => {
return {message}
}
```

こんな感じではなく。

const Wrapper = ({ message }) => {
  // WARNING: THIS IS VERY VERY BAD AND SLOW, DO NOT DO THIS!!!
  const StyledWrapper = styled.div`
    /* ... */
  `

  return <StyledWrapper>{message}</StyledWrapper>
}

Recommended reading: Talia Marcassaが実際の利用法について有益な実践的洞察、代替案との比較について述べた素晴らしいレビューを書いている。
Styled Components: To Use or Not to Use?

疑似要素、疑似セレクタ、ネスト

私達が使用しているプリプロセッサのstylisはネストするstylingに自動的に対応するためのscssライク文法をサポートしている。
例を出そう。

const Thing = styled.div`
  color: blue;
`

疑似セレクターと疑似要素はこれ以上の改良を必要とせずcomponentに適用される。

const Thing = styled.button`
  color: blue;

  ::before {
    content: '🚀';
  }

  :hover {
    color: red;
  }
`

render(
  <Thing>Hello world!</Thing>
)

styled component 7.png

より複雑なセレクターには&を使うとcomponentそれ自身を参照できる。
考えられるいくつかの例をあげよう。

const Thing = styled.div.attrs({ 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"
  }
`

render(
  <React.Fragment>
    <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>
  </React.Fragment>
)

スクリーンショット 2018-11-09 1.11.18.png

セレクターを&なしで使用した場合にはcomponentのchildrenを参照することになる。

const Thing = styled.div`
  color: blue;

  .something {
    border: 1px solid; // an element labeled ".something" inside <Thing>
    display: block;
  }
`

render(
  <Thing>
    <label htmlFor="foo-button" className="something">Mystery button</label>
    <button id="foo-button">What do I do?</button>
  </Thing>
)

スクリーンショット 2018-11-09 1.11.53.png

最後に、&はcomponentへのruleの明確性を増すためにも使用できる。
これはstyled-componentsとバニラCSSが入り交じりstyleが競合する可能性のある環境で有用である。

const Thing = styled.div`
  && {
    color: blue;
  }
`

const GlobalStyle = createGlobalStyle`
  div${Thing} {
    color: red;
  }
`

render(
  <React.Fragment>
    <GlobalStyle />
    <Thing>
      I'm blue, da ba dee da ba daa
    </Thing>
  </React.Fragment>
)

スクリーンショット 2018-11-09 1.11.53.png

追加のpropsの付与 (v2)

ただpropsをrenderされるcomponentやelementにわたすだけの不要なラッパーを取り除くために、.attrs constructorを使うことができる。
これにより追加のprops(もしくは"attributes")をcomponentに適用することができる。

このようにスタティックなpropsと要素に適用したり、React RouterのLink componentへのactiveClassNameのような外部からのpropsを渡すことができる。
更により動的なpropsをcomponentに適用することもできる。
.attrs objectは関数も受け取ることができ、その関数はcomponentが受け取るpropsを引数として取ることができる。
その戻り値はその下で用いられるprops(例ではmarginとpaddingで用いられるprops)にマージされる。

Input Comonentにいくつかのスタティックattributeと動的なattributeを追加してみよう。

const Input = styled.input.attrs({
  // we can define static props
  type: "password",

  // or we can define dynamic ones
  margin: props => props.size || "1em",
  padding: props => props.size || "1em"
})`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;

  /* here we use the dynamically computed props */
  margin: ${props => props.margin};
  padding: ${props => props.padding};
`;

render(
  <div>
    <Input placeholder="A small text input" size="1em" />
    <br />
    <Input placeholder="A bigger text input" size="2em" />
  </div>
);

スクリーンショット 2018-11-09 1.39.12.png

ご覧の通り.attrの中で作られた新たなpropsにアクセスすることができ、そしてtype attributeは要素に受け渡される。

アニメーション

@keyframesを用いたCSSアニメーションは一つのcomponentのscopeに限られていないが、それでも名前の衝突を防ぐためglobalスコープにしたくないと考えられる。
これがappを通してユニークなインスタンスを作るkeyframesヘルパーを導入した理由である。

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

render(
  <Rotate>&lt; 💅 &gt;</Rotate>
);

注意:keyfmesはreact-nativeではサポートされていないので、代わりにReactNative.Animated APIを使おう。

keyframesは使用されたとき初めて遅延評価され、そうしてコードの分離を実現している。
そしてこういった場合shared style fragments向けのcss helperを使用しなければならない。

const rotate = keyframes``

// ❌ This will throw an error!
const styles = `
  animation: ${rotate} 2s linear infinite;
`;

// ✅ This will work as intended
const styles = css`
  animation: ${rotate} 2s linear infinite;
`

注意:v3以下のバージョンではkeyframesを用いたコード分離は行っておらず、上のエラーは起こらない。
v3より上のバージョンにアップグレードする場合には、必ずcss helperを使うようにしなければならない。

React Native

styled-componentsはReact Nativeでも同じ仕様法、import方法で利用できる。
以下の例をSnack by Expoを用いて実行してみてほしい。

import React from 'react'
import styled from 'styled-components/native'

const StyledView = styled.View`
  background-color: papayawhip;
`

const StyledText = styled.Text`
  color: palevioletred;
`

class MyReactNativeComponent extends React.Component {
  render() {
    return (
      <StyledView>
        <StyledText>Hello World!</StyledText>
      </StyledView>
    )
  }
}

また普通arrayになるようなより複雑なstyles(transformなど)や、省略記法もcss-to-react-nativeのおかげでサポートできている。

注意:flex propertyはReact NativeではReact Nativeのレガシーなflex propertyではなく、CSSの省略記法として振る舞う。
flex: 1と設定することはflexShrink: 1とすることに等しい。

React Nativeのプロパティをどう書けばいいか、それをCSSに翻訳する方法を推測したなら、それは多分正しい。

const RotatedBox = styled.View`
  transform: rotate(90deg);
  text-shadow-offset: 10px 5px;
  font-variant: small-caps;
  margin: 5px 7px 2px;
`

webのstyled-componentsとの違いは、keyframescreateGlobalStyleヘルパーが使えないことだ。
なぜならReact Nativeはその両方をサポートしてないからだ。
またもしmediaクエリかネストさせたCSSを使用するときにも注意が必要だ。

注意:v2バージョンではパーセンテージ記法をサポートしている。
これを可能にするためすべての省略記法に向けunitを強化しなければならなかった。
もしv2に移行しているならば、codemodが使用できる。

metro bundlerを使ってより簡単に

styled-components/nativeの代わりに単純にstyled-componentsをimportしたいなら、"react-native"を含んだresolverMainFields configurationを使用しても良い。
これはデフォルトでmetroによってサポートされていたが(今はhaulでも動く)、どうやらいくつかの点で取り除かれてしまっているようだ。

まとめ

超速で訳したので間違いあれば言ってほしい。
疲れた。