MuiThemeProvider

Material-UIにはMuiThemeProviderという関数が存在しており, これがReact-ContextとwithStylesを通して全てのComponentのスタイルに影響している.

ここでは, Material-UIのStyleに関してまとめる.

Material-UI

React Components that Implement Google’s Material Design.

http://www.material-ui.com

2017-05-09 6.04.53.png

Package

  • mateiral-ui@1.0.0-alpha.12
  • react@15.5.3
  • react-dom@15.5.3

Docs

$ git clone -b next git@github.com:callemall/material-ui.git
$ cd material-ui
$ yarn
$ cd docs yarn
$ yarn start

MuiThemeProvider

material-uiはMuiThemeProvider関数を用いて, 全てのComponentのスタイルを管理する.

具体的には, themestyleManagerをpropsとしたMuiThemeProvider関数を上層で使用する.

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'

function App () {
  return (
    <MuiThemeProvider theme={theme} styleManager={styleManager}>
      <Layout/>
    </MuiThemeProvider>
  )
}

render(<App/>, document.querySelector('#app'))

themestyleManagerが存在しない場合はデフォルトのスタイルが適用される.

context

MuiThemeProviderReact-contextを用いていて, componentのスタイルはこのcontextから動的に生成される.

class MuiThemeProvider extends Component {
  static childContextTypes = {
    styleManager: PropTypes.object.isRequired,
    theme: PropTypes.object.isRequired,
  }
  
  getChildContext () {
    const { theme, styleManager } = this
    return {
      theme,
      styleManager
    }
  }
}

この2つのコンテキストは必須であり, propsが存在しない場合はcontextが生成されることになる.

class MuiThemeProvider extends Component {
  static createDefaultContext(props = {}) {
    const theme = props.theme || createMuiTheme();
    const styleManager = props.styleManager || createStyleManager({
      theme,
      jss: create(jssPreset()),
    });

    if (!styleManager.sheetOrder) {
      styleManager.setSheetOrder(MUI_SHEET_ORDER);
    }

    return { theme, styleManager };
  }
}

theme

themecreateMuiTheme関数を用いて生成される. このcreateMuiThemeの引数に設定される{palette}createPalette関数を用いて生成できる.

import { createMuiTheme } from 'material-ui/styles'
import { blue, pink } from 'material-ui/styles/colors'
import createPalette from 'material-ui/styles/palette'

const dark = true

const palette = createPalette({
  primary: blue,
  accent: pink,
  type: dark ? 'dark' : 'light',
});

const theme = createMuiTheme({ palette })

このblueなどはObjectになっていて, Material Design GuidelinesのColor paletteと一致している.

const blue = {
  50: '#e3f2fd',
  100: '#bbdefb',
  200: '#90caf9',
  300: '#64b5f6',
  400: '#42a5f5',
  500: '#2196f3',
  600: '#1e88e5',
  700: '#1976d2',
  800: '#1565c0',
  900: '#0d47a1',
  A100: '#82b1ff',
  A200: '#448aff',
  A400: '#2979ff',
  A700: '#2962ff',
  contrastDefaultColor: 'light'
}

material-ui@0.18.0では多くのカラーコードを指定する必要があり微調整が面倒だったけど, @1.0.0ではtypeなどの値で動的にカラーコードが設定される.

createMuiTheme

createMuiThemeにはpalette以外にtransitionsなどが含まれている.

function createMuiTheme(options = {}) {
  const {
    palette = createPalette(),
    breakpoints = createBreakpoints(),
    mixins = createMixins(breakpoints, spacing),
    typography = createTypography(palette),
    ...more
  } = options

  return {
    direction: 'ltr',
    palette,
    typography,
    shadows,
    transitions,
    mixins,
    spacing,
    breakpoints,
    zIndex,
    ...more
  }
}

例えばtransitionstheme.transitions.create('transform', {})などのように, トランジション前とトランジション後のclassNameを生成してくれる.

トランジションさせたいときは, これをthis.stateなどで切り替えて使用する.

styleManager

styleManagerthemeの更新ができる.

const themeContext = MuiThemeProvider.createDefaultContext({ theme })
const styleManager = themeContext.styleManager

また, themeを動的に修正したい場合はstyleManager.updateTheme関数を用いる.

styleManager.updateTheme(theme)

createStyleSheet

createStyleSheet関数はStyleを生成しHtml内に展開してくれる.

変数styleSheetには関連付けされたClassNameのオブジェクトが返される.

import { createStyleSheet, withStyles } from 'material-ui/styles'

const styleSheet = createStyleSheet('Example', (theme) => {
  return {
    paper: {
      backgroundColor: theme.palette.background.paper,
      maxWidth: 400
    },
    expand: {
      transform: 'rotate(0deg)',
      transition: theme.transitions.create('transform', {
      duration: theme.transitions.duration.shortest
    })
  },
  }
})

class Example extends Component{
  render () {}
}

export default withStyles(styleSheet)(Example)

material-ui@0.18.0ではinline-styleだったので, Jsxの時点では吐き出されるDOMとstyle属性が把握しづらかった.

2017-05-09 9.17.00.png

@1.0.0からはjss-theme-reactorを用いて, classNameを生成してComponentsのスタイルを管理することになったのでとても読みやすい.

また, 一般的な乱数からclassNameを生成するタイプではなく, createStyleSheet(Example, theme => {})のように名前を指定できる.

2017-05-09 9.16.10.png

withStyles

withStyles関数はcreateStyleSheet関数が生成するオブジェクトをReact Componentにpropsとして渡してくれる.

class Example {
  render () {
    return <div className={this.props.classes.expand}></div>
  }
}

export default withStyles(styleSheet)(Example)

Css-in-Jsでは名前空間の衝突が無いだけでなくStyleをオブジェクトとして管理できるので, 共有化がとても容易になる.

const cardStyle = {}
const paperStyle = {}
const styleSheet = createStyleSheet('Example', (theme) => ({
  ...cardStyle,
  ...paperStyle,
  expand: {}
}))

classnames

classNamesを使うと複数classNameの管理が楽になる material-uiのComponentではこれが使われている.

import classNames from 'classnames'

変数themeからはcreatePaletteで生成したオブジェクトが扱える.

例えば, true/falseでトランジションさせたいとき以下のようにStyleを生成する.

const styleSheet = createStyleSheet('Example', (theme) => ({
  expand: {
    transform: 'rotate(0deg)',
    transition: theme.transitions.create('transform', {
      duration: theme.transitions.duration.shortest,
    }),
  },
  expandOpen: {
    transform: 'rotate(180deg)',
  }
}))

この場合はexpandは常にStyleとして存在し, trueの時だけexpandOpenが付加されトランジションする仕組み.

classnamesを用いて以下のように表現することができる.

class Example extends Component {
  render () {
    return <IconButton
      className={classnames(classes.expand, {
        [classes.expandOpen]: this.state.expanded,
      })}
      onClick={this.handleExpandClick}>
  }
}