文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

material-ui v4+TypeScriptでwithStylesからmakeStylesに移行してシンプルにする

makeStylesを使う事でシンプルになりますよ〜

f:id:treeapps:20190616112954p:plain

ついにリリースされたmaterial-ui v4ですが、色々な機能が加わったり、構造がより最適化されたり、是非ともバージョンアップしたいですね。

今回はスタイルの「makeStyles関数」に焦点を当ててみます。

v3からv4への移行

v3のスタイル設定

 import React from "react" 
 import { createStyles, Theme, withStyles, WithStyles } from "@material-ui/core/styles"

const styles = (theme: Theme) =>
  createStyles({
    root: {
      padding: theme.spacing.unit * 2,
    },
  })

interface IProps extends WithStyles<typeof styles> {
  children: React.ReactNode
}

const HelloComponent = (props: IProps) => { 
  const { classes, children } = props 
  return (<div className={classes.root}>{children}</div>)
}

export const Hello = withStyles(styles)(HelloComponent)

FC(functional component)内からstyles定数のrootに触れるようにするため、IPropsのインターフェースにstylesを継承させる事で、classes経由でrootに触れるようにしています。

IPropsにWithStylesを継承するとclasses.rootと書けるようになるのは、WithStylesクラスのプロパティにclassesがあるためです。
github.com

そしてwithStyles(styles) の部分がHOC(higher order component)になっていて、hooksが登場した今となっては、関数でラップしているのが冗長です。

v4のスタイル設定

import React from "react" 
import { Box } from "@material-ui/core"
import { createStyles, Theme, makeStyles } from "@material-ui/core/styles"

const useStyles = makeStyles((theme: Theme) =>
 createStyles({
   root: {
     padding: theme.spacing(2),
   },
 }))

interface IProps {
 children: React.ReactNode
}

export const Hello = function(props: IProps) {
 const { children } = props 
 const classes = useStyles(props)
 return (<Box className={classes.root}>{children}</Box>)
}

IPropsの呪文のようなextendsが消え、withStylesのHOCが消えた事で単なるfunctionにする事ができ、かなりシンプルになりました。

注意点

従来のReact.Componentをextendsしたコンポーネントでは使えない

例えば以下のようなライフサイクルメソッドを持つコンポーネントです。

class Hello extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props)
    this.state = {
      open: true,
    }
  }
  render(
    return (<div>hello</div>)
  )
}

これに対してmakeStylesを適用しようとすると、以下のようになります。
f:id:treeapps:20190616040147p:plain

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

要は「function componentで使えよな!」と怒られています。

という事で従来のReact.Componentを継承した形では使えず、FC化する必要があります。

この従来のstateをもつcomponentをFC化しようとすると、React Hooksを使う事になります。今回はスタイルの話なのでそこは割愛します。

更に言うと、makeStylesを使い、React HooksでFC化し、先日リリースされたhooks対応されたreact-redux v7でconnect関数(HOC)を排除すると、connect関数を排除する事もでき、より単純なfunction化に近づきます。
react-redux.js.org

typescript + next.js + material-uiのサンプル

以前より以下のgitリポジトリを最新化していっています。
github.com

2019/06時点では以下に対応しているサンプルとなります。

  • TypeScript v3.5
  • Next.js v8.1
  • React v16.8
  • material-ui v4
  • redux v4
  • react-redux v7

まだTSLintを使っていたりreact-redux v7のhooksも使っていないですが、一部可能な部分はmakeStylesを使う形に更新しています。

雑感

material-uiは3〜7日おきくらいにバージョンアップを繰り返しており、物凄い勢いで進化している凄いプロダクトです。

進化が速いので、twitterでmaterial-uiの更新情報をこまめにキャッチアップし、更新に追従していった方が無難かもしれませんね。