iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 6
1
Modern Web

Next.js + 各種套件組合系列 第 6

Next.js & Material-UI

介紹

Material Design 是 Google 提供的一套設計標準, Material-UI 是參考其標準設計為 React 開發者使用的套件目前在 V1.0 BETA 版 ,最主要差異是導入了 Css In Js (在v1.0版之前是使用 Sass 模式),目前 Css In Js 有很多套而 Material-UI 是使用 Jss 來當作主要的 核心

V1.0 Next.js 上的設定

先安裝
npm install Material-UI @next --save
在安裝 如果要使用ICON作者也很貼心的加入到套件之中
npm install material-ui-icons

如果要單獨使用套件直接掛入就可以了,不過這樣是沒有 Theme 通常要再補一個 Provider提供佈景


import React from 'react';
import { render } from 'react-dom';
import Button from 'material-ui/Button';

function App() {
  return (
    <Button raised color="primary">
      Hello World
    </Button>
  );
}

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

Jss 因為要在 Document 本文中先設定JssProvider ,在 Next.js 要覆寫 Document 本文就要在 pages 目錄底下設定 _document.js 這隻檔案

import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import JssProvider from 'react-jss/lib/JssProvider';
import getContext from '../styles/getContext';

class MyDocument extends Document {
  render() {
    return (
      <html lang="en" dir="ltr">
        <Head>
          <title>My page</title>
          <meta charSet="utf-8" />
           <meta
            name="viewport"
            content={
              'user-scalable=0, initial-scale=1, ' +
              'minimum-scale=1, width=device-width, height=device-height'
            }
          />

          <link rel="manifest" href="/static/manifest.json" />
           <meta name="theme-color" content={this.props.stylesContext.theme.palette.primary[500]} />
          <link rel="stylesheet"    
          href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
          />
        </Head>
        <body>    <Main /> <NextScript />   </body>
      </html>
    );
  }
}

MyDocument.getInitialProps = ctx => {
  const context = getContext();
  const page = ctx.renderPage(Component => props => (
    <JssProvider registry={context.sheetsRegistry} jss={context.jss}>
      <Component {...props} />
    </JssProvider>
  ));
  return {
    ...page,
    stylesContext: context,
    styles: (
      <style
        id="jss-server-side"
        dangerouslySetInnerHTML={{ __html: context.sheetsRegistry.toString() }}
      />
    ),
  };
};
export default MyDocument;

這邊 主要是在 MyDocment.getInitialProps 完成Jss Provider 設定,順便傳入 Material 的佈景主題到 Meta 之中

<meta name="theme-color" content={this.props.stylesContext.theme.palette.primary[500]} />

context 也是設定主要佈景的地方,會跟 client共用

    stylesContext: context,

context 內容主要是 Jss 跟 Material 的設定 Material 會要先設定 Theme
然後透過 Jss 的 createContext 去創建給 stylesContext 使用,這邊順便注入到 SSR 的 Gobal 與 Client SPA 共用


import { create, SheetsRegistry } from 'jss';
import preset from 'jss-preset-default';
import { createMuiTheme } from 'material-ui/styles';
import purple from 'material-ui/colors/purple';
import green from 'material-ui/colors/green';
import createGenerateClassName from 'material-ui/styles/createGenerateClassName';

const theme = createMuiTheme({
  palette: {
    primary: purple,
    secondary: green,
  },
});

// Configure JSS
const jss = create(preset());
jss.options.createGenerateClassName = createGenerateClassName;

function createContext() {
  return {
    jss,
    theme,
    sheetsRegistry: new SheetsRegistry(),
  };
}

export default function getContext() {
  if (!process.browser) {
    return createContext();
  }

  // Reuse context on the client-side
  if (!global.__INIT_MATERIAL_UI__) {
    global.__INIT_MATERIAL_UI__ = createContext();
  }

  return global.__INIT_MATERIAL_UI__;
}

接下來設定 SPA , Material-UI提供了一個 withStyles HOC 讓 Css 掛到指定的元件,在 componentWillMount
這 getContext (import getContext from '../styles/getContext')
就 SPA 跟 SSR 都同一隻設定值(不用分開寫),在 componentDidMount 的時候因為SSR會有些衝突所以就把多餘的節點先移除,在給 SPA 的 MuiThemeProvider 需要的 Theme 以及 sheetsManager (都來自設定 getContext),
這樣就會有 Theme 主題了 ,接下來就可以在 Next.js 輕鬆的使用 Material-UI,其他元件的部分因為都是隨裝即用可以參考 https://material-ui-next.com/

主要理解這邊提到的三隻程式的關聯就可以輕鬆快樂上手了


/* eslint-disable flowtype/require-valid-file-annotation */

import React, { Component } from 'react';
import { withStyles, MuiThemeProvider } from 'material-ui/styles';
import wrapDisplayName from 'recompose/wrapDisplayName';
import getContext from '../styles/getContext';

// Apply some reset
const styles = theme => ({
  '@global': {
    html: {
      background: theme.palette.background.default,
      WebkitFontSmoothing: 'antialiased', // Antialiasing.
      MozOsxFontSmoothing: 'grayscale', // Antialiasing.
    },
    body: {
      margin: 0,
    },
  },
});

let AppWrapper = props => props.children;

AppWrapper = withStyles(styles)(AppWrapper);

function withRoot(BaseComponent) {
  class WithRoot extends Component {
    static getInitialProps(ctx) {
      if (BaseComponent.getInitialProps) {
        return BaseComponent.getInitialProps(ctx);
      }

      return {};
    }

    componentWillMount() {
      this.styleContext = getContext();
    }

    componentDidMount() {
       const jssStyles = document.querySelector('#jss-server-side');
      if (jssStyles && jssStyles.parentNode) {
        jssStyles.parentNode.removeChild(jssStyles);
      }
    }

    render() {
      return (
        <MuiThemeProvider
          theme={this.styleContext.theme}
          sheetsManager={this.styleContext.sheetsManager}
        >
          <AppWrapper>
            <BaseComponent {...this.props} />
          </AppWrapper>
        </MuiThemeProvider>
      );
    }
  }

  if (process.env.NODE_ENV !== 'production') {
    WithRoot.displayName = wrapDisplayName(BaseComponent, 'withRoot');
  }

  return WithRoot;
}

export default withRoot;

另外 JSS 的寫作風格是 利用駝峰 CSS 語法像一些有"-"的都要拿掉例如 background-image在JSS 就要寫成 backgroundImage ,通常都會把這些 CSS跟 Components放在同一層在 import 近來後透過 mateial-UI 提供的 withStyle HOC綁定到物件 ,物件的class 都要寫成 className

總結
Material-UI 使用了 Jss 駝峰寫法 Css In Js 的好處之一就是可以省去 perBuild (Css的語法都支援),也省去開發階段reBuild 工作,除了Next.js 設定稍微複雜一些,稍微胖一些.不過個人滿喜歡 Material-UI 風格的,這個相對於 styled-components 都是屬於 Css In Js,也是常常被比較, styled-components 比較輕 day7 來介紹一下

官方提供 Next.js 範例

https://github.com/mui-org/material-ui/tree/v1-beta/examples/nextjs


上一篇
Next.JS & Firebase Auth
下一篇
Next.js & Styled-component
系列文
Next.js + 各種套件組合30

尚未有邦友留言

立即登入留言