Kosaku Kurino

Kosaku Kurino

【React】Hooksの出現で変わったいまどきのコンポーネント設計

これまでのコンポーネント設計

これまでReactである程度開発できる人がアニメーションなどローカルで閉じて制御させたいコンポーネント実現しようとするとき、問題になってくるのがStateLifecycleです。

React入門者は各コンポーネントをクラスで作成していると思うのでローカルでStateを使い、componentDidMountなどのLifecycle methodを使えば良いので問題はありません。

しかし、慣れてくるとコンポーネントをクラスでは作成せず、SFC(Stateless Function Component)化していく流れがありました(過去形)。そしてそのような人たちは好んでStateReduxMobxでグローバルに一元管理しているはずです。

SFC化したコンポーネントはローカルにStateを保持できないし、Lifecycle methodも使えません。そのため、HoC(Higher-Order Components)を活用し、コンポーネントをラップすることで擬似的にローカルStateを保持させたり、Lifecycle method使えるようにすることで解決してきました。

これまでコンポーネントはSFC化し、必要があればHoCを用いてラップするのがベストプラクティス的な流れがありましたが、React version16.8で正式にリリースしたHooksという機能の出現で流れが変わりました

HoCの代表的ライブラリーrecomposeの開発の停止の発表、recomposeの開発者がReactチームに参画したみたいなのでHoCの流行りは終わると思っています。

これからのコンポーネント設計

これまでのコンポーネント

まずクラスで作ったコンポーネント、SFC化したコンポーネント、HoCでラップしたコンポーネントの違いについてみてみます。

Component local state lifecycle method performance
Class ×
SFC × ×
HoC(wrap SFC)

つまりパフォーマンスを考えたとき、SFCを使い、必要に応じてHoC(wrap SFC)がベストプラクティスでした

HooksがもたらすFC(Function Component)とは

Hooksはクラスを使わずとも、ローカルでstateを管理できたり、様々な機能を使えるようにできるフックAPIです。

useStateuseEffectuseCallbackuseReducerなどたくさんあります。ここら辺の使い方はたくさん記事が世に出回っているので説明しません。

Hooksを利用することで、これまでHoC(wrap SFC)で実現していたローカルのstatelifecycle methodの利用を上手く吸収したFC(Function Component)を作ることができます

HoC(wrap SFC)とあまり変わらないが、一々ラップする必要がなくなっているところと、可読性がかなり上がります。

パフォーマンスの面では、まだHoC(wrap SFC)よりは遅いらしいです。

FCの例をあげておきます。 カラーの部分はわざわざuseStateuseEffectを使わなくてもいいのですが、使い方の紹介として使っています。

import React, { useState } from 'react'
import styled, { keyframes } from 'styled-components'

const white = '#F5F5F5'
const innerDefaultColor = '#3C3C3C'
const outerDefaultColor = '#FF5A5F'

const SampleButton = ({ onClick, inner, outer, text, children }) => {
  const [ hover, setHover ] = useState(false)

  const [ textColor, setTextColor ] = useState(white)
  useEffect(() => {
    if(text) {
      setTextColor(text)
    }
    else {
      setTextColor(white)
    }
  }, [text])

  const [ innerColor, setInnerColor ] = useState(innerDefaultColor)
  useEffect(() => {
    if(inner) {
      setInnerColor(inner)
    }
    else {
      setOuterColor(innerDefaultColor)
    }
  }, [inner])

  const [ outerColor, setOuterColor ] = useState(outerDefaultColor)
  useEffect(() => {
    if(outer) {
      setOuterColor(outer)
    }
    else {
      setOuterColor(outerDefaultColor)
    }
  }, [outer])

  const scaleUp = keyframes`
    from {
      transform: scale(1)
    }
    to {
      transform: scale(${hover? 1.05 : 1})
    }
  `

  const Container = styled.div`
    position: relative
    display: flex
    justify-content: center
    align-items: center
    width: 210px
    height: 210px
  `

  const Outer = styled.div`
    position: absolute
    top: 0
    left: 0
    width: 210px
    height: 210px
    background: ${outerColor}
    border-radius: 105px
    animation-name: ${scaleUp}
    animation-duration: 0.5s
    animation-timing-function: ease-in
    animation-iteration-count: infinite
    animation-direction: alternate
    z-index: -1
  `

  const Inner = styled.div`
    display: flex
    justify-content: center
    align-items: center
    text-align: center
    width: 200px
    height: 200px
    border-radius: 100px
    background: ${innerColor}
    color: ${textColor}
    cursor: pointer

  `
  
  return (
    <Container>
      <Outer />
      <Inner
        onClick={onClick}
        onMouseOver={() => setHover(true)}
        onMouseOut={() => setHover(false)}
      >
        {children}
      </Inner>
    </Container>
  )
}

export default SampleButton

結論

これからのReactのコンポーネント設計は、SFCとHoC(wrap SFC)主体の設計からSFCとFC主体へとシフトしていきます。Hooksの出現は割と大きな変化だと思うので、一度Reactの知識をアップデートしておいた方がいいと思います。