React + styled-components で CSS の transition を使う

React + styled-components を触っている中で、スライドするボックスをどう作るかを考える機会がありました。

そこで、CSS の transition プロパティを、React + styled-components でどう使うかを調べてみたので、その記録を残しておきます。

## CSS の transition プロパティについて

<a href="https://developer.mozilla.org/ja/docs/Web/CSS/transition" target="_blank" rel="nofollow noopener">transition - CSS: カスケーディングスタイルシート | MDN</a>

transition プロパティでは、そのプロパティを指定した要素における、2つの状態間の変化を定義することができます。
2つの状態の定義の仕方は、`:hover` や `:active` といった擬似クラスや、自分で設定したクラスに定義する形で表現します。

今回は、<a href="https://developer.mozilla.org/ja/docs/Web/CSS/transform" target="_blank" rel="nofollow noopener">transform</a> プロパティの `translateX()` を使い、要素の位置を左右にずらした状態を2つ定義し、その遷移で要素の左右移動をさせてみる例を考えます。

## サンプル

ボタンを押すことで左右にボックスがスライドするという簡単なサンプルを作ってみました。

![slide.gif](https://qiita-image-store.s3.amazonaws.com/0/222030/60b556cf-78ea-d693-fa7b-ca27e671ba74.gif)

### ①  クラスの付け替え

まず単純にボタンを押した状態を `state` で定義しておき、その状態でクラスのつけ外しをする方法でやってみました。

<p data-height="220" data-theme-id="0" data-slug-hash="NLJZEL" data-default-tab="js,result" data-user="gotchane" data-pen-title="HoverDivSampleByAddingClass" class="codepen">See the Pen <a href="https://codepen.io/gotchane/pen/NLJZEL/">HoverDivSampleByAddingClass</a> by Masayuki Goto (<a href="https://codepen.io/gotchane">@gotchane</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

`.is-slided` の CSS に `transform` プロパティを定義しておき、クラスをつけ外しすることで状態遷移させています。

### ②  styled-components の css 定義内で切り替える

上記でも実現できたのですが、styled-components を使っているのにわざわざクラスをつけるのは違和感がありました。
どうせなら自前のクラスをつけないで実現したいと思い、調べた方法がこちらです。

<p data-height="265" data-theme-id="0" data-slug-hash="pOBzNe" data-default-tab="js,result" data-user="gotchane" data-pen-title="HoverDivSampleByProps" class="codepen">See the Pen <a href="https://codepen.io/gotchane/pen/pOBzNe/">HoverDivSampleByProps</a> by Masayuki Goto (<a href="https://codepen.io/gotchane">@gotchane</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

styled-components の CSS 定義に `props` でボタンの状態を渡し、`translateX` 内の数値を変化させることで状態遷移させています。

何故上記の方法で `transition` が利用できるのでしょうか？

ブラウザの開発者コンソールで調べてみたところ、styled-components で生成された要素に付加されているハッシュ値のクラス名が、props が切り替わるごとに変わることで、2つの状態をそれぞれ定義できていたようです。

#### ボタン押す前

styled-components によって動的にクラスが割り当てられている

```html
<div class="sc-bwzfXH dbpADZ">...</div>
```

```css
.dbpADZ {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 200px;
    height: 100%;
    transform: translateX(0px);
    background: pink;
    padding: 15px;
    transition: all 0.3s ease-out 0s;
}
```

#### ボタン押した後

* `.dbpADZ` → `.bIxbAR` に変わっている
* `translateX(0px)` → `translateX(200px)` に変わっている

```html
<div class="sc-bwzfXH bIxbAR">...</div>
```

```css
.bIxbAR {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 200px;
    height: 100%;
    transform: translateX(200px);
    background: pink;
    padding: 15px;
    transition: all 0.3s ease-out 0s;
}
```

### 補足：React の render 内で styled-components を定義すると transition がうまく動かないので注意

最後に補足として、styled-components のコンポーネントを React コンポーネントの `render()` 内で定義するとどうなるでしょうか？
そのサンプルがこちらです。

<p data-height="265" data-theme-id="0" data-slug-hash="VGNZQO" data-default-tab="js,result" data-user="gotchane" data-pen-title="HoverDivSampleByPropsInRender" class="codepen">See the Pen <a href="https://codepen.io/gotchane/pen/VGNZQO/">HoverDivSampleByPropsInRender</a> by Masayuki Goto (<a href="https://codepen.io/gotchane">@gotchane</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

React のコンポーネントは、props が切り替わると再レンダリングされるので、その度に styled-components のコンポーネントも再レンダリングされています。
サンプルを載せましたが、状態遷移中の描画が一瞬抜け落ちる挙動になってしまうようです。([styled-components の issue ](https://github.com/styled-components/styled-components/issues/1334)でも話題に上がっていました)

transition を 使う場合、 styled-components のコンポーネント定義は、React コンポーネントの `render()` 内で行わない方が良さそうですね。

## 参考

* https://github.com/ueeieiie/styled-transition-transform
* https://spectrum.chat/thread/bf5c730b-6b35-457f-a485-acc50bae047f
* https://github.com/styled-components/styled-components/issues/1334