145
100

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【初心者】#1 Reactの基礎とMaterial-UI使って綺麗に作ってみる

Last updated at Posted at 2021-01-16

Reactでかっこいいページを作りたい!

  • 国内ではVue.jsの人気も高いですが、Reactの方が海外では流行っている
  • React Server Componentsなるものが出てきた
  • 下火気味?だけどReact Nativeをもしやる事になっても、少しは対応しやすくなるかも

ということで、
バックエンドが専門の私ですが、JavaScriptのスキルアップかねてReactをはじめました。

:pushpin: React & Material UI & Django RestFramework記事一覧

随時、ルーティング、ReactでAPI取得、バックエンドでAPIサーバー作ってReactで取得する方法をシリーズで書いていきます😄

内容
part1 React Material UIを使ってみる ← ココ
part2 【React】#2 React axiosでAPI データ取得
part3 【React】 #3 ルーティング・ページ遷移 react-router-dom
part4 【Python】 Django REST Framework ReactとAPIサーバー繋ぐ

デザインはMaterial UIに任せよう

デザイナーでもフロントエンジニアでもない私は、CSS Frameworkに頼ります!
情報量の多さから、Material UIを使うのが良いと判断して勉強開始。

ReactもMaterial UIも英語読めないと対処しにくいところあるので、
誰かの助けになればいいなと思って作りながらメモしながら書いてます。

2020-15.png

使用環境

  • react 17.0.1
  • material-ui 4.11.2

トピック

対象:

  • react初心者
  • CSS苦手な人へ
  • Reactやってみたいけど、デザインが…
  • とりあえず見た目が及第点欲しい人の基礎の基礎

やること

  • インストール、react-create-appから始める 
  • ヘッダー作成
  • グリッド(レスポンシブ)
  • Reactでのアイコン付け方
  • カード、ボタン、画像を使って見た目を整える

環境作る

前提:nodejs入れてない人はインストールしてください。nodejsと調べて、LTSという方をダウンロードしてインストール進めればOKなはずです。

Reactのファイル作りたいところに移動してから、以下でアプリ作ります。
名前はmaterial-reactで作ってみます。

$ npx create-react-app material-react
$ cd material-react

サーバーを起動。

$ npm start

reactのマークがくるくる回ってたらOK

スクリーンショット 2021-01-15 1.06.38.png

Material-UI使えるようにする

公式サイトの通りにインストールしていく。

$ npm install @material-ui/core

必須ではないけど、以下をやっていきます。
index.htmlにフォントを追記。titleタグ付近にでも。

index.html
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

'Roboto'つかうと書いてあるので、フォントの優先度をあげます。

index.css
body {
  margin: 0;
  font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

アイコンを使うので、インストール。

$ npm install @material-ui/icons

デフォルトのページいらないので変更

軽く説明すると、

  • index.htmlの<div id="root"></div>この要素をJSでいじりまくってタグとか生成して作っていく感じ
  • App.jsがJSファイルの親玉?先祖?みたいな感じ
  • componentsフォルダ作って、その中に部品となるもの作り、App.jsにコンポーネントを埋め込んで作っていくが一般的。例えば、サイドバー用のJS、ヘッダー用のJSとか。

image.png

では、デフォルトページを修正。
初期状態では、コンポーネントが存在しないで、App.jsに全てが書いてあります。
全部をApp.jsに作ることもできますが、
Reactは機能ごとにコンポーネントに分けて作るのが普通です。

では、Reactマークのくるくるさせるための記述と、
デザインが、App.jsとApp.cssに書かれていますので、
Material-UI使うために、白紙のページにします。

白紙ページにする

  1. App.css中身全部消す
  2. App.jsの項目削除して真っ白なページにする
App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      真っ白
    </div>
  );
}

export default App;

Reactマークが消えて、真っ白とだけ表示されてればOK

グリッド、レスポンシブ

Bootstrapと同じですね。
画面を横方向を12分割にしてその要素は、12個ののうち、何個分の横幅を使うの?
ってやつです。

flexbox使うより簡単にレスポンシブ対応できるので、専門でない人には便利です!

スクリーンショット 2021-01-15 17.06.04.png

https://material-ui.com/components/grid/#grid
※いまいちわからない方は↑ページ開いて、ブラウザのウィンドウを小さくしたり、大きくしたりしてみてください。

material-react/src/App.js
import './App.css';
import { Grid } from '@material-ui/core';

function App() {
  return (
    <Grid container direction="column">
      <Grid item>
        item1item1item1item1item1item1item1item1item1item1item1item1
      </Grid>
      <Grid item container>
        <Grid sm={2} />
        <Grid xs={12} sm={8}>
          item2item2item2item2item2item2item2item2item2item2item2item2
          item2item2item2item2item2item2item2item2item2item2item2item2
          item2item2item2item2item2item2item2item2item2item2item2item2
          item2item2item2item2item2item2item2item2item2item2item2item2
          item2item2item2item2item2item2item2item2item2item2item2item2
        </Grid>
        <Grid sm={2} />
      </Grid>
    </Grid>
  );
}

export default App;

<Grid container>という親の中に<Grid item>という子を入れていきます。
<Grid item container>分割された子の要素の中をさらに分割するため、container入れてます。

<Grid sm={2} />これが二つありますが、これが左右の余白です。
xsが超小さい画面の時には余白なし。小さい画面以上の時は、 2/12の空白を入れてます。
2/12が二つなので、本体は残りの8/12。

なので<Grid xs={8}>

すると、こんな感じ。
スクリーンショット 2021-01-15 2.02.49.png

ブラウザの画面を手動で大きくしたり、小さくしたりして変化するか試してください。

ヘッダーを作る

srcのなかにコンポーネントフォルダを作ります。
その中にHeader.js作成

$ mkdir src/components
material-react/src/components/Header.js
import React from 'react'

function Header() {
    return (
        <div>
            ヘッダー
        </div>
    )
}

export default Header

ヘッダーコンポーネントをApp.jsに追加

作ったヘッダーコンポーネントを親玉のApp.jsの中につっこみます。

material-react/src/App.js
import Header from './components/Header';

・・・

    <Grid container direction="column">
      <Grid item>
        <Header />
      </Grid>
...

単純に**「ヘッダー」**という文字が出るだけですが、
ヘッダーコンポーネント(ヘッダーの部品)が読み込まれてます。

スクリーンショット 2021-01-15 3.05.16.png

これだとしょぼいので、公式サイトから使用例をパクって改造します。

https://material-ui.com/components/app-bar/
https://material-ui.com/ja/api/app-bar/

基本的なものだけで構成してみました。

material-react/src/components/Header.js
import { AppBar, Toolbar, Typography } from '@material-ui/core'
import React from 'react'

function Header() {
    return (
        <AppBar position="static">
            <Toolbar>
                <Typography>ヘッダー</Typography>
            </Toolbar>
        </AppBar>
    )
}

export default Header

ヘッダーなのでposition="static"にしてます。
これ指定しないと、ヘッダーに要素が重なってしまいます。
スクリーンショット 2021-01-15 3.14.36.png

アイコンをつける

https://material-ui.com/components/material-icons/

検索して良いアイコンを探す。
好きなアイコンをクリックすると下の画像みたいな表示が出る。

今回はこのシルエットみたいなアイコンを試しに貼り付けてみます。
スクリーンショット 2021-01-15 3.17.25.png

importのところをコピーしてHeader.jsに貼り付け。iconを使えるようにします。

Header.js
import AccountCircleOutlinedIcon from '@material-ui/icons/AccountCircleOutlined';

        <AppBar position="static">
            <Toolbar>
                <Typography>ヘッダー</Typography>
                <AccountCircleOutlinedIcon />
            </Toolbar>
        </AppBar>

スクリーンショット 2021-01-15 3.19.26.png
アイコンが出ました。

ただ、普通、こういうのって、左端にありますよね。
ということで、CSSをいじって、調整します。

Material-UIでCSSを追加

makeStyleを使ってみましょう。
Material-UIで作るときにCSSを書くとき使うもののようです。
ファイル一つにたくさんのCSS書くより、コンポーネントに分かれてるし、そこにCSS書けばよくない?
ってことで使われるらしいです。(?)
当然、JSで書くので計算とか、条件とか、Scssっぽく?便利にデザインを適用もできますね。

Header.js
import React from "react";
import { AppBar, Toolbar, makeStyles, Typography } from "@material-ui/core";
import AcUnitRoundedIcon from "@material-ui/icons/AcUnitRounded";

const useStyles = makeStyles(() => ({
  typographyStyles: {
    flex: 1
  }
}));

const Header = () => {
  const classes = useStyles();
  return (
    <AppBar position="static">
      <Toolbar>
        <Typography className={classes.typographyStyles}>
          Anthony sistilli
        </Typography>
        <AcUnitRoundedIcon />
      </Toolbar>
    </AppBar>
  );
};

export default Header;

classNameはHTMLのclassのことです。reactの中ではclassNameと書きます。

で、クラスをmakeStyleで生成するということで↓追加。

.js
const useStyles = makeStyles(() => ({
  typographyStyles: {
    flex: 1
  }
}));

useStyles作って

const classes = useStyles();でclasses作って、
classNameにオブジェクトのキー名typographyStyles
を設定しているので

classes.typographyStylesで、ここの要素にflex: 1を適用するということです。

スクリーンショット 2021-01-15 3.35.59.png

コンテンツ部分のコンポーネントを作る

$ touch src/components/Content.js

Content.js作成。App.js
ヘッダーの時と同じようにインポートと、<Content />とを入れる。

App.js
import Content from './components/Content';

...

    <Grid container direction="column">
      <Grid item>
        <Header />
      </Grid>
      <Grid item container>
        <Grid sm={2} />
        <Grid xs={12} sm={8}>
          <Content />
        </Grid>
        <Grid sm={2} />
      </Grid>
    </Grid>

コンテンツ部分は
「カード」
を使います。

カードの部分もコンポーネントにしたいのでBodyCard.jsを作ります。

$ touch src/components/BodyCard.js

Contentコンポーネントの中にBodyCardコンポーネントが入れ子になってます。
App.jsからみるとBodyCardコンポーネントは孫に当たります。

では、Outlined Cardというのを公式ページからパクってきて、

https://material-ui.com/ja/components/cards/

src/components/BodyCard.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';

const useStyles = makeStyles({
    bullet: {
      display: 'inline-block',
      margin: '0 2px',
      transform: 'scale(0.8)',
    },
    title: {
      fontSize: 14,
    },
    pos: {
      marginBottom: 12,
    },
});


function BodyCard() {
    const classes = useStyles();
    const bull = <span className={classes.bullet}></span>;
    return (
        <Card variant="outlined">
            <CardContent>
            <Typography className={classes.title} color="textSecondary" gutterBottom>
                Word of the Day
            </Typography>
            <Typography variant="h5" component="h2">
                be{bull}nev{bull}o{bull}lent
            </Typography>
            <Typography className={classes.pos} color="textSecondary">
                adjective
            </Typography>
            <Typography variant="body2" component="p">
                well meaning and kindly.
                <br />
                {'"a benevolent smile"'}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">Learn More</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard

カードを一つだけ表示させてみます。

Content.js
import React from 'react'
import BodyCard from './BodyCard'

function Content() {
    return (
        <BodyCard />
    )
}

export default Content

一つだけは表示できてますね。
スクリーンショット 2021-01-15 4.08.39.png

3個表示すると

Content.js
import { Grid } from '@material-ui/core'
import React from 'react'
import BodyCard from './BodyCard'

function Content() {
    return (
        <Grid container>
            <Grid item xs={4}> 
                <BodyCard />
            </Grid>
            <Grid item xs={4}> 
                <BodyCard />
            </Grid>
            <Grid item xs={4}> 
                <BodyCard />
            </Grid>
        </Grid>
    )
}

export default Content

スクリーンショット 2021-01-15 4.12.40.png

スペースを入れて見た目調整

くっついていて見た目良くないので、調整します。

Material-UI公式ページのgridの項目で
spacingの項目で確認してからやるといいかもしれません。

スクリーンショット 2021-01-15 4.21.16.png

spacing={2}足す。もっと開けたいときは数字を大きくするといいです。

Content.js
return (
        <Grid container spacing={2}>
            <Grid item xs={4}> 
                <BodyCard />
            </Grid>
            <Grid item xs={4}> 
                <BodyCard />
            </Grid>
            <Grid item xs={4}> 
                <BodyCard />
            </Grid>
        </Grid>
    )

あとは、好みで調整してみてください。
画面のサイズによってどのような横幅にするかをxs, sm md, lg, xlで決められます。
Bootstrapと同じ感じ。

今回は、スマホなど画面が非常に小さくなると、カードが一つずつ。smサイズで1行に3個並べるようにしました。

スクリーンショット 2021-01-15 4.27.29.png

↓xsサイズにすると

スクリーンショット 2021-01-15 4.31.30.png

カードの見た目改造 カードのヘッダー、クリックできるアイコン

カードの中のヘッダーとクリックできるアイコンを用意します。
今回は⭐️アイコンつけます。

Card要素の一番初めにCardHeaderを追加して、
⭐️アイコンを調べてインポート(StarBorderOutlinedIcon)。
該当箇所に貼り付けます。

BodyCard.js
import CardHeader from '@material-ui/core/CardHeader';
import Avatar from '@material-ui/core/Avatar';
import IconButton from '@material-ui/core/IconButton';
import StarBorderOutlinedIcon from '@material-ui/icons/StarBorderOutlined';

        <Card variant="outlined">
            <CardHeader
                avatar={
                <Avatar aria-label="recipe" className={classes.avatar}>
                    R
                </Avatar>
                }
                action={
                <IconButton aria-label="settings">
                    <StarBorderOutlinedIcon />
                </IconButton>
                }
                title="Shrimp and Chorizo Paella"
                subheader="September 14, 2016"
            />

IconButtonがあるとホバーした時に違い出て、押せる感じになってます。
スクリーンショット 2021-01-15 15.16.25.png

アバターと画像は適当な画像をランダムで取得できるサービスあったのでテストで使います。

https://joeschmoe.io/
https://picsum.photos

BodyCard.js
import { CardMedia } from '@material-ui/core';

.
.
.

function BodyCard(props) {
    const { avatarUrl, title, subheader, text, imageUrl } = props;
    const classes = useStyles();
    const bull = <span className={classes.bullet}></span>;
    return (
        <Card variant="outlined">
            <CardHeader
                avatar={<Avatar src={avatarUrl} />}
                action={
                <IconButton aria-label="settings">
                    <StarBorderOutlinedIcon />
                </IconButton>
                }
                title={title}
                subheader={subheader}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent>
            <Typography variant="body2" component="p">
                {text}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}
Content.js
.
.
.

function Content() {
    return (
        <Grid container spacing={2}>
            <Grid item xs={12} sm={4}> 
                <BodyCard 
                title="タイトル1" 
                subheader="サブヘッダー1" 
                avatarUrl="https://joeschmoe.io/api/v1/random" 
                imageUrl="https://picsum.photos/150"
                text="カードの説明1" />
            </Grid>
            <Grid item xs={12} sm={4}> 
                <BodyCard />
            </Grid>
            <Grid item xs={12} sm={4}> 
                <BodyCard />
            </Grid>
            <Grid item xs={12} sm={4}> 
                <BodyCard />
            </Grid>
        </Grid>
    )
}

今は、propsを一個めのカードにしか与えてないので、このようになってます。

スクリーンショット 2021-01-15 15.48.56.png

では、残りのカードにも表示されるようにして、

Content.js
import { Grid } from '@material-ui/core'
import React from 'react'
import BodyCard from './BodyCard'

const cardContents = [
    {
        title: "タイトル1",
        subheader: "サブヘッダー1",
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    },
    {
        title: "タイトル2",
        subheader: "サブヘッダー2",
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    },
    {
        title: "タイトル3",
        subheader: "サブヘッダー3",
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    },
    {
        title: "タイトル4",
        subheader: "サブヘッダー4",
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    },
]

function Content() {
    const getCardContent = getObj => {
        return (
            <Grid item xs={12} sm={4}>
                <BodyCard {...getObj} />
            </Grid>
        );
    };
    return (
        <Grid container spacing={2}>
            {cardContents.map(contentObj => getCardContent(contentObj))}
        </Grid>
    )
}

export default Content

同じ画像になりますができました。
次回はデータをAPIで取得して表示する方法について触れます。

:pushpin: React & Material UI & Django RestFramework記事一覧

内容
part1 React Material UIを使ってみる ← ココ
part2 【React】#2 React axiosでAPI データ取得
part3 【React】 #3 ルーティング・ページ遷移 react-router-dom
part4 【Python】 Django REST Framework ReactとAPIサーバー繋ぐ
145
100
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
145
100

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?