きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

Material UI + TypeScript で Mixin を定義する

TypeScript + Materia UI でアプリを書いていて、テーマ関係のメソッドの中に createMixins というものを見つけたので使い方を調べてみました。

デフォルトの mixin といえば gutter くらいしかなくて、自分でも登録できたら色々使えそうだな...と思い、型定義を追いかけながらコードを試行錯誤したところ、多少不恰好ですが動くものができたのでメモしておきます。

プロジェクトコードは以下のリポジトリにあります。

プロジェクト作成

プロジェクトは create-react-appreact-scripts-ts で生成したものをベースにしています。この辺は特に詳細は説明しません。

create-react-app practice-mui-with-jsonschema-form --scripts-version=react-scripts-ts --use-npm

Theme の定義

Theme を定義しているファイルの全体のコードは以下のようになります。

import { createMuiTheme } from '@material-ui/core/styles'
import createMixins from '@material-ui/core/styles/createMixins'
import { CSSProperties } from '@material-ui/core/styles/withStyles'

// `Mixins` 定義を拡張する
declare module '@material-ui/core/styles/createMixins' {
  interface Mixins {
    myBackgroundMixin: CSSProperties, // <- custom mixin!
  }
}

// `createMixins` で利用するインスタンスを取得する
const { breakpoints, spacing } = createMuiTheme()

const mixins = createMixins(breakpoints, spacing, {
  myBackgroundMixin: {
    backgroundColor: '#ff0000'
  }
})

export default createMuiTheme({
  mixins,
  palette: {
    divider: '#ff0000'
  },
})

以下、個々のポイントを説明していきます。

Mixin の定義を拡張する

createMixins で参照されている interface Mixins の定義を見ていると何やら意味深なコメントが書いてあります。

export interface Mixins {
  gutters: (styles?: CSSProperties) => CSSProperties;
  toolbar: CSSProperties;
  // ... use interface declaration merging to add custom mixins
}

TypeScript の柔軟であり分かりづらい部分ですが、interface などの定義は後追い拡張(マージ)が行えます。 ここでは以下のように myBackgroundMixin を追加定義してみました。

declare module '@material-ui/core/styles/createMixins' {
  interface Mixins {
    // (=˘ ꒳ ˘=) 展開先を赤く染め上げる血みどろの Mixin...
    myBackgroundMixin: CSSProperties,
  }
}

Mixin の定義を行う

これにより createMixins の引数である MixinsOptions にも拡張が反映され、createMixins 内で myBackgroundMixin が定義できるようになります

const mixins = createMixins(breakpoints, spacing, {
  // (=˘ ꒳ ˘=) 血みどろの実装...容赦のないビビッドな赤
  myBackgroundMixin: {
    backgroundColor: '#ff0000'
  }
})

上の定義の拡張を行わないと Object literal may only specify known properties, and 'myBackgroundMixin' does not exist in type 'MixinsOptions'. というエラーが出ます。

ちょっと曲者な createMixins の引数

createMixins の引数には BreakpointSpacingインスタンスが必要なのですがいちいち用意したくはないですよね。今回は小手先のアイディアとして一度 createMuiTheme() を実行してデフォルトのインスタンスを取得しています。

const { breakpoints, spacing } = createMuiTheme()

もっといい方法がある気がしますね。

実際に利用してみる

上記の定義を含めた Theme インスタンスMuiThemeProvider 経由で提供してやれば、配下のコンポーネントで今回定義した myBackgroundMixin を利用することができます。

import * as React from 'react'

import Button from '@material-ui/core/Button'
import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles'

interface Props extends WithStyles<typeof styles> {
  name: string
}

function MyButton({ classes, name }: Props) {
  return (
    <Button className={classes.root}>
      {name}
    </Button>
  )
}

const styles = ({ mixins, palette }: Theme) => createStyles({
  root: {
    ...mixins.myBackgroundMixin,
    color: palette.common.white,
  }
})

export default withStyles(styles)(MyButton)

無事、手元では赤いボタンが表示されました。

ここまで、細切れの説明になりましたので、冒頭で紹介したリポジトリで全体を見ていただくのが分かりやすいかもしれません。