【React】React基礎編(MUI編)

MUIはマテリアルUIの略で、Microsoftが提供しています。
Reactへの導入は比較的容易にでき、導入サンプルも本家サイトに豊富に記載されています。

今回の記事は、MUIの基本的な使い方は本家サイトに譲り、中々分かりにくい下記項目について解説したいと思います。

  • 導入方法
  • Theme
  • useRefの使用方法

はじめに

画面の見た目を綺麗に構築するライブラリはいくつもありますが、MUIは中でも抜群に性能が良いと考えています。

今回は、無料利用が可能なMUI coreの中でも、分かりにくい箇所themeを主軸に、導入方法とuseRefの設定方法もあわせて記載したいと思います。

導入

MUIの導入には、下記コマンドを実行します。
ついでに、美麗なアイコンが多い「react-icons」もセットで導入したいと思います。

コマンドプロンプトを起動後、プロジェクトフォルダへ移動します。
その後、下記コマンドを実行します。
 ※この後もコマンドプロンプトを使用するので、閉じないで下さい。

npm install @mui/material @emotion/react @emotion/styled @mui/icons-material react-icons

インストールしたコンポーネントの参照先ドキュメントは、下記になります。

項目URL
MUIhttps://mui.com/
MUIアイコンhttps://mui.com/material-ui/material-icons/
reactアイコンhttps://react-icons.github.io/react-icons/

MUIは、公式ドキュメントにサンプルが大量に掲載されているので、慣れるまでは頻繁に確認する事になるでしょう。
また、reactアイコンも何かと検索する事が多いので、併せてブックマークしておくと作業が捗りますよ。

Theme

Themeとは、デフォルトで設定されている各種スタイルを、自由にカスタマイズできるものです。
先ずは、createTheme()にてオリジナルのテーマを作っていきましょう。

import { createTheme } from '@mui/material';
const theme = createTheme({
});
export default theme;

作成したThemeは、TemeProvider()に設定します。
 ※基本はindex.tsxに組み込む形で良いです。今回はたまたまApp.tsxに組み込みました。

以降、関数「App()」に配置した子要素(以下孫要素含む)達は、設定したThemeに従って画面描画を行います。
 ※CssBaselineは、MUIが用意したリセットCSSになります。

import './App.css';
import Sample from 'Sample';
import { CssBaseline, ThemeProvider } from '@mui/material';
import theme from 'theme';

function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline/>
      <div className="App">
        <Sample />
      </div>
    </ThemeProvider>
  ); 
}

export default App;

先ずはサンプルとして、ボタン・チェックボックス・スイッチを使用します。
Themeに何も記載がないので、この段階ではデフォルトスタイルで表示しています。

import {Button,Checkbox,Switch } from '@mui/material';
function Sample() {
    return (
        <div>
            <div>
                <Button variant="contained">Text</Button>
                <Checkbox defaultChecked />
                <Switch defaultChecked />
            </div>
        </div>
    )
}
export default Sample;

画面の確認になります。
配置しただけなのに、綺麗な見た目ですよね。

次より、Themeの設定を行っていきたいと思います。

palette

「palette」は、コンポーネントの色を指定できます。

先ずはサンプルになります。

import { createTheme } from '@mui/material';
import { orange } from '@mui/material/colors';


// カスタムカラーの設定
declare module '@mui/material/styles' {
    interface Palette {
        sample: Palette['primary'];
    }
    interface PaletteOptions {
        sample?: PaletteOptions['primary'];
    }
}
declare module '@mui/material' {
    interface ButtonPropsColorOverrides {
        sample: true;
    }
    interface CheckboxPropsColorOverrides {
        sample: true;
    }
    interface SwitchPropsColorOverrides {
        sample: true;
    }
}

const theme = createTheme({
    palette: {
        mode: 'light', // dark or light 表示モード
        primary: {
            main: '#1976d2',
            light: '#42a5f5',
            dark: '#1565c0',
            contrastText: '#fff'
        },
        secondary: orange,
        sample: {
            main: '#378030',
            light: '#53BF49',
            dark: '#2C6627',
            contrastText:'#fff'
        }
    }
});
export default theme;

サブセクション(左のテーブル)と、各サブセクションのプロパティ(右のテーブル)になります。
下記以外にもオリジナルのサブセクションを作成する事が可能です。
 ※上記サンプルの「sample」がオリジナルのサブセクションになります。

サブセクション説明
primaryデフォルト(メイン)カラー
secondaryサブカラー
errorエラー表示時のカラー
warning警告表示時のカラー
info情報表示時のカラー
success成功時のカラー
プロパティ説明
mainメインカラー
lightライトモードでのカラー
darkダークモードでのカラー
contractTextテキストカラー

配色が分かりやすいように、コンポーネントを増やし「secondary」と「sample」の設定を行いました。

import {Button,Checkbox,Switch } from '@mui/material';
function Sample() {
    return (
        <div>
            <div>
                <Button variant="contained">Text</Button>
                <Checkbox defaultChecked />
                <Switch defaultChecked />
            </div>
            <div style={{marginTop: 10}}>
                <Button variant="contained" color='secondary'>Text</Button>
                <Checkbox defaultChecked color='secondary'/>
                <Switch defaultChecked color='secondary'/>
            </div>
            <div style={{marginTop: 10}}>
                <Button variant="contained" color='sample'>Text</Button>
                <Checkbox defaultChecked color='sample' />
                <Switch defaultChecked color='sample' />
            </div>
        </div>
    )
}
export default Sample;

各コンポーネントに設定した配色(primary,secondary,sample)を確認します。

各コンポーネントが、指定した配色(青色、オレンジ色、緑色)で表示されています。

typography

「typography」は、コンポーネントのフォントと文字サイズを設定します。

ついでに新規フォントを追加します。
下記サイトより2つのフォント「Crimson Pro」と「Note Sans」を探して、インストールしましょう。

Fontsource
Download and self-host 1500+ open-source fonts in neatly bundled NPM packages. Access a comprehensive library of web typ...

下記が、2つのフォントのインストールコマンドになります。
導入時に使用したコマンドプロンプトが残っていれば、そこから実行して下さい。
 ※閉じている場合は、起動した後プロジェクトフォルダに移動してください。

npm install @fontsource/crimson-pro @fontsource/noto-sans-jp

インストールしたフォントをインポートします。

import { createTheme } from '@mui/material';
import "@fontsource/crimson-pro";
import '@fontsource/noto-sans-jp';

const theme = createTheme({
    typography: {
        fontFamily: [
            '"Crimson Pro"',
            '"Noto Sans JP"',
            'sans-serif',
            '"Myrica M"',
            '"Moralerspace Neon HW"'
        ].join(','),
        fontSize: 15,
    },
});
export default theme;

「typpography」で設定する項目は下記になります。

サブセクション説明
fontFamily文字の種類
fontSize文字の大きさ(デフォルト14px)
htmlFontSizeHTMLのデフォルトフォントサイズを指定する
fontWeightRegularsxで’fontWeight’を’regular’を指定した時の太さを指定する
(デフォルト値)
fontWeightMediumsxで’fontWeight’を’medium’を指定した時の太さ指定する
fontWeightBoldsxで’fontWeight’を’bold’を指定した時の太さ指定する
fontWeightLightsxで’fontWeight’を’light’を指定した時の太さ指定する

variant

「variant」は、UIの見た目をパラメータ「variant」で設定する事で、細かく変更する事が可能です。
下記サンプルの「sample」はオリジナルで作成した「variant」になります。

import { createTheme } from '@mui/material';
import "@fontsource/crimson-pro";
import '@fontsource/noto-sans-jp';

// オリジナルvariantの設定用
declare module "@mui/material/styles" {
    interface TypographyVariants {
        sample: React.CSSProperties;
    }

    // allow configuration using `createTheme`
    interface TypographyVariantsOptions {
        sample?: React.CSSProperties;
    }
}
// Update the Typography's variant prop options
declare module "@mui/material/Typography" {
    interface TypographyPropsVariantOverrides {
        sample: true;
    }
}

const theme = createTheme({
    typography: {
        fontFamily: [
            '"Crimson Pro"',
            '"Noto Sans JP"',
            'sans-serif',
        ].join(','),
        fontSize: 15,
        subtitle1:{
            fontSize: '1.5rem',
            fontFamily: 'Arial',
            fontStyle: 'italic',
            fontWeight: 500
        },
        body1:{
            fontFamily: 'Segoe UI Symbol',
        },
        sample: {
            fontWeight: 500,
            color: 'red',
            fontSize: 30,
            fontFamily: 'BlinkMacSystemFont',
        },
        h3: undefined // バリアントを無効化
    },
});
export default theme;

Typographyのvariantに「body1」「subtitle1」「sample」を設定します。
 ※「body1」はTypographyのデフォルトバリアントです。

import {Button,Checkbox,Switch,Typography} from '@mui/material';
function Sample() {
    return (
        <div>
            <div>
                <Button variant="contained">Text</Button>
                <Checkbox defaultChecked />
                <Switch defaultChecked />
                <Typography>Sample Text</Typography>
                <Typography variant='subtitle1'>Sample Text</Typography>
                <Typography variant='sample'>Sample Text</Typography>
            </div>
        </div>
    )
}
export default Sample;

Typographyの文字「Sample Text」が、設定したvariantにより変化している事が分かります。

設定可能なvariantは、下記の13種になります。

variant 13種
  • h1
  • h2
  • h3
  • h4
  • h5
  • h6
  • subtitle1
  • subtitle2
  • body1
  • body2
  • button
  • caption
  • overline

spacing

「spacing」は、UI間のすき間を設定します。
コンポーネントのパラメータ「sx」を指定した時の設定値に影響を与えます。

import { createTheme } from '@mui/material';

const theme = createTheme({
    spacing: 2
});
export default theme;

コンポーネントへの指定方法
themeの「spacing値」と、「sxで設定した値」をかけた数値のすき間を設定します。

<Box sx={{m:2}}>bbb</Box> // margin: 2 * 2 = 4px
<Box sx={{p:3}}>bbb</Box> // padding: 2 * 3 = 6px;

sxの設定は、「余白の種類(margin、padding)」と「余白の位置(top, right等 任意)」の組み合わせになります。

余白の種類
  • m = margin
  • p = padding
余白の位置
  • t = top
  • r = right
  • b = bottom
  • l = left
  • x = left & right
  • y = top & bottom

組み合わせの種類としては下記14種となります。

余白の組み合わせ14種
  • m, p
  • mt, mr, mb, ml, mx, my
  • pt, pr, pb, pl, px, py


breakpoints

「breakpoints」は、レスポンシブにスタイルを設定します。

breakpointsのサンプルになります。
xs,xl等を廃止し、mobile, desktop等を新規で追加し、各設定項目に対し、画面幅を指定しています。

import { createTheme } from '@mui/material';

declare module '@mui/material/styles' {
    interface BreakpointOverrides {
        xs: false; // removes the `xs` breakpoint
        sm: false;
        md: false;
        lg: false;
        xl: false;
        mobile: true; // adds the `mobile` breakpoint
        tablet: true;
        laptop: true;
        desktop: true;
    }
};
const theme = createTheme({
    breakpoints:{
        values:{
            mobile: 0, // adds the `mobile` breakpoint
            tablet: 640,
            laptop: 1024,
            desktop: 1200,
        }
    }
});
export default theme;

「breakpoints」の指定方法です。
画面幅に対し、背景色と文字の表示/非表示を切り替えています。

import { Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import { red, green, blue, orange } from '@mui/material/colors';

const Wrap = styled('div')(({ theme }) => ({
    padding: theme.spacing(1),
    [theme.breakpoints.up('mobile')]: {
        backgroundColor: orange[500], // ブラウザのwidth= 0px ~ 
    },
    [theme.breakpoints.up('tablet')]: {
        backgroundColor: red[500], // ブラウザのwidth= 640px ~ 
        color: '#FFF'
    },
    [theme.breakpoints.up('laptop')]: {
        backgroundColor: blue[500], // ブラウザのwidth= 1023px ~ 
    },
    [theme.breakpoints.up('desktop')]: {
        backgroundColor: green[500], // ブラウザのwidth= 1200px ~
    },
}));

function Sample() {
    return (
        <Wrap>
            <Typography>abc</Typography>
            <Typography sx={{display: {mobile:'none', laptop:'block'}}}>出現</Typography>
        </Wrap>
    )
}
export default Sample;

■mobile 画面幅「~ 639px」

■tablet 画面幅「640px ~ 1023px」

■laptop 画面幅「1024px ~ 1199px」

■desktop 画面幅「1200px ~」

画面幅に合わせて、変化している様子が分かります。
この機能を利用して、端末や機種ごとに見え方を変える事が可能になります。

zIndex

「zIndex」は、各コンポーネントのz軸(重なり具合)を調整する項目になります。

下記は、各コンポーネントの初期値になります。

コンポーネント名zIndex初期値
mobile stepper1,000
fab1,050
speed dial1,050
app bar1,100
drawer1,200
modal1,300
snackbar1,400
tooltip1,500

滅多に変える事は無いと思いますが、いざと言う時に知識として知っておくと良いです。

transitions

「transitions」は、cssをカスタマイズでき「durations」「easings」を変更できます。

APIを利用して、UIの要素に変更を行います。

theme.transitions.create(props, options) => transition

「transitions」の使用のサンプルになります。
APIを使用し、カスタマイズしています。

import { Button } from '@mui/material';
import { styled } from '@mui/material/styles';

const CustomBtn = styled(Button)`
    ${ ({ theme }) => `
        cursor: crosshair;
        opacity: 0.2;
        margin-top: 30px;
        background-color: ${theme.palette.secondary.dark};
        transition: ${theme.transitions.create(
            [
                'background-color', 
                'margin-top',
                'opacity',
                'transform'
            ],
            {
                delay: '100ms',
                duration: theme.transitions.duration.standard,
                easing:theme.transitions.easing.easeIn,      
            }
        )};
        &:hover {
            transform: scale(1.4);
            background-color: ${theme.palette.success.main};
            margin-top: 35px;
            opacity: 1;
        }
    `}
`;

function Sample() {
    return (
        <CustomBtn variant='contained'>ボタン</CustomBtn>
    )
}
export default Sample;

「duration」「easing」は、themeで初期値が設定されていますが、themeの設定によって変更可能です。

duration

プロパティ名初期値
shortest150
leavingScreen195
shorter200
enteringScreen225
short250
standard300
complex375
duration

easing

プロパティ名初期値
easeInOutcubic-bezier(0.4, 0, 0.2, 1)
easeOutcubic-bezier(0.0, 0, 0.2, 1)
easeIncubic-bezier(0.4, 0, 1, 1)
sharpcubic-bezier(0.4, 0, 0.6, 1)
easing

shape

「shape」は、UIの隅を指定します。

「shape」のサンプルになります。
borderRadiusの値を大きくすると、UIの角が丸くなっていきます。

import { createTheme } from '@mui/material';

const theme = createTheme({
    shape: {
        borderRadius: 15
    }
});
export default theme;

components

「components」は、コンポーネントのスタイルや、デフォルトのプロパティを変更できます。
また、独自のバリアントも設定可能です。

システム全体で共通の変更を行いたい場合、ここで設定を行うイメージになると思います。

「components」のサンプルになります。

import { createTheme } from '@mui/material';

declare module '@mui/material/Button' {
    interface ButtonPropsVariantOverrides {
        sample: true;
    }
}

const theme = createTheme({
    components:{
        MuiButtonBase:{
            defaultProps: {
                centerRipple: false
            },
            styleOverrides: {
                // Name of the slot
                root: {
                    cursor: 'zoom-in',
                }
            },
        },
        MuiButton:{
            variants: [
                {
                    props: { variant: 'sample' },
                    style: {
                        border: '1px solid blue'
                    }
                }
            ]
        }
    }
});
export default theme;
  • defaultPropsで、コンポーネントの初期値を設定します。
  • stypeOverridesで、cssの初期値を変更します。
  • variantsで、新規バリアントを設定できます。

useRefの使い方

MUIは、目的のUIに様々なラッピングを行って見た目を変更しています。
そのため、useRefを通常通りに使用してしまうと、目的のHTML要素が取得できません。

取得出来るのは、<input>をラップしいた「外側の<div>要素」を取得します。

下記サンプルは、「TextField」に通常通りにuseRefを設定しました。
useRefの中身をconsole.logで出力し、確認してみましょう。

import { TextField } from '@mui/material';
import { useRef, useState } from 'react'

function Sample() {
    const iptRef = useRef<HTMLInputElement>(null);
    const [text, setText] = useState<string>('テキスト');

    if ( iptRef.current !== null ){
        console.log(iptRef.current);
    }

    return (
        <TextField
            ref={iptRef}
            variant='outlined'
            value={text}
            onChange={(e) => setText(e.target.value)} />
    )
}
export default Sample;

サンプル画面とDOMを確認してみます。

TextFieldは、<input>をラッピングしている<div>全体を指しています。
useRefで取得する対象のHTML要素は、内部にある<input>要素です。

では、useRefに何が格納されるのか確認してみましょう。

案の定、外側の<div>要素がuseRefに格納されていました。
このまま無理やり使用しても問題ありませんが、やはり効率が悪いです。

useRefの正しい設定方法

<input>要素にuseRefを設定したい場合は、「inputRef」を使用します。

先ほどのサンプルから、「ref => inputRef」に変更しています。

import { TextField } from '@mui/material';
import { useRef, useState } from 'react'

function Sample() {
    const iptRef = useRef<HTMLInputElement>(null);
    const [text, setText] = useState<string>('テキスト');

    if ( iptRef.current !== null ){
        console.log(iptRef.current);
    }

    return (
        <TextField
            inputRef={iptRef}
            variant='outlined'
            value={text}
            onChange={(e) => setText(e.target.value)} />
    )
}
export default Sample;

console.logを確認してみます。

今度は正常に<input>要素をuseRefに格納する事が出来ました。

おわりに

MUIの導入とthemeしか触れていませんが、一旦ここで記事を終了したいと思います。
実際、様々なコンポーネントを導入する際、本家のURLからサンプルを参照した方が正確で正しいコーディングが行えます。

ここでは、本家で拾いにくい項目に対して紹介いたしました。

後は使い慣れれば、割と簡単に導入出来ちゃいます。