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

# Reactでかっこいいページを作りたい！
- 国内ではVue.jsの人気も高いですが、Reactの方が海外では流行っている
- `React Server Components`なるものが出てきた
- 下火気味？だけどReact Nativeをもしやる事になっても、少しは対応しやすくなるかも

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


# :pushpin: React & Material UI & Django RestFramework記事一覧
随時、ルーティング、ReactでAPI取得、バックエンドでAPIサーバー作ってReactで取得する方法をシリーズで書いていきます😄

|  | 内容|
|:----:|:----:|
|**part1**|**<font color="red">React Material UIを使ってみる</font>　← ココ**|
|**part2**|[【React】#2 React axiosでAPI データ取得](https://qiita.com/Bashi50/items/b4e0626b2ede73568eab)|
|**part3**|[【React】 #3 ルーティング・ページ遷移 react-router-dom](https://qiita.com/Bashi50/items/ce8aaeaa52f73ba971b9)|
|**part4**|[【Python】 Django REST Framework ReactとAPIサーバー繋ぐ](https://qiita.com/Bashi50/items/203505440049d759b694)|

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

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

![2020-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/c6cf1cbf-a5ed-a676-4bed-b5a3a248a58d.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/ea57a1a0-8b0e-4ae6-1c69-60d598bd9024.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f2a5b000-cdf5-af31-3543-5a1f119c5ea2.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個ののうち、何個分の横幅を使うの？
ってやつです。

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

![スクリーンショット 2021-01-15 17.06.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/06cacaa1-0113-2586-71ff-4629676a6d09.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/add957ed-30ea-2a00-d6dc-574b1a908f9d.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://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/646e346f-81b8-1e17-377a-9286d4015062.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://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/4c978853-42ca-34ca-30a6-c84836d31325.png)


# アイコンをつける

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

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

今回はこのシルエットみたいなアイコンを試しに貼り付けてみます。
![スクリーンショット 2021-01-15 3.17.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/4d8e9f7d-7d9b-8835-fa9f-256184491041.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f8766e48-492a-d7e7-4ca1-9b7729eae7c1.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/2a326fa1-b450-9208-805c-be7f9f0f175d.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/3a5327a6-5894-5a09-84fe-5d87e252e826.png)

３個表示すると

```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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/101a9a11-1485-af19-5554-a1de129bc185.png)

## スペースを入れて見た目調整
くっついていて見た目良くないので、調整します。

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

![スクリーンショット 2021-01-15 4.21.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/fd605781-8ac6-9283-c3ae-322dc070740b.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サイズで１行に３個並べるようにしました。


![スクリーンショット 2021-01-15 4.27.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/ec6065d3-6664-208a-5d65-78d0d000daf5.png)

↓xsサイズにすると

![スクリーンショット 2021-01-15 4.31.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/2f793373-b511-f6d9-a87e-99a8be616ed1.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://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/1c791e51-d309-6004-a148-273c4a918df8.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="タイトル１" 
                subheader="サブヘッダー１" 
                avatarUrl="https://joeschmoe.io/api/v1/random" 
                imageUrl="https://picsum.photos/150"
                text="カードの説明１" />
            </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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/1f64f4fa-e6f5-d485-4651-886afafafee4.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**|**<font color="red">React Material UIを使ってみる</font>　← ココ**|
|**part2**|[【React】#2 React axiosでAPI データ取得](https://qiita.com/Bashi50/items/b4e0626b2ede73568eab)|
|**part3**|[【React】 #3 ルーティング・ページ遷移 react-router-dom](https://qiita.com/Bashi50/items/ce8aaeaa52f73ba971b9)|
|**part4**|[【Python】 Django REST Framework ReactとAPIサーバー繋ぐ](https://qiita.com/Bashi50/items/203505440049d759b694)|