男子プログラマーになりたいやつ

未経験から一端のエンジニア目指し日々勉強中です。

MERN client行ってみよう

MERN行ってみる? f:id:TomozQ:20220212185303j:plain

MERN client準備編

目次




前回の続きです。

今回はクライアントの処理を書いていきます。


今回作成していくもの

f:id:TomozQ:20220206134727g:plain

コンポーネントの雛形作成

ターミナルでclientに移動してパッケージをインストールします

% cd client
% npm install @material-ui/core

次にclientフォルダー直下にcomponentsフォルダーを作成して

さらにその下にフォルダーを作っていきます。

Formフォルダーを作成し、その中にForm.jsファイルを作成

Postsフォルダーを作成し、その中に Posts.jsファイルを作成

PostsフォルダーにPostフォルダーを作成し、その中にPost.jsファイルを作成します。

f:id:TomozQ:20220212122630p:plain

次にAppコンポーネントを書いていきます。

client/src/App.js


import React from 'react';
// @material-ui/coreからコンポーネントをimport
import { Container, AppBar, Typography, Grow, Grid } from '@material-ui/core'
// 画像をimport
import memories from './images/memories.jpg'
const App = () => {
  return (
    <Container maxWidth='lg'>
      {/* title部分 */}
      <AppBar position='static' color='inherit'>
        <Typography variant='h2' align='center' height='60'>memories</Typography>
        <img src={memories} alt='memories' height='60'/>{/* importした画像 */}
      </AppBar>
      {/* main部分 */}
      <Grow in>
        <Container>
          <Grid container justifyContent='space-between' alignItems='stretch' spacing={3}>
            {/* 投稿一覧が表示される部分 */}
            <Grid item xs={12} sm={7}>
              {/* 投稿一覧表示コンポーネント */}
              <Posts />
            </Grid>
            {/* 投稿フォームが表示される部分 */}
            <Grid item xs={12} sm={4}>
              {/* 投稿フォーム表示コンポーネント */}
              <Form />
            </Grid>
          </Grid>
        </Container>
      </Grow>
    </Container>
  );
};

export default App;

Formコンポーネントを追記

client/src/components/Form/Form.js

import React from 'react';

const Form = () => {
  return (
    <h1>
      FORM
    </h1>
  );
};

export default Form;

Postsコンポーネントを追記

client/src/components/Posts/Posts.js

import React from 'react';
import Post from './Post/Post'

const Posts = () => {
  return (
    <>
      <h1>POSTS</h1>
      <Post/>
      <Post/>
    </>
  );
};

export default Posts;

Postコンポーネントを追記

client/src/components/Posts/Post/Post.js

import React from 'react';

const Post = () => {
  return (
    <h1>POST</h1>
  );
};

export default Post;

client/src/App.jsのimport部分に下記のように追記します。

import React from 'react';
import Posts from './components/Posts/Posts'; //追記
import Form from './components/Form/Form';  //追記
// @material-ui/coreからコンポーネントをimport
import { Container, AppBar, Typography, Grow, Grid } from '@material-ui/core'
// 画像をimport
import memories from './images/memories.jpg'

現状このように画面が表示されています。

スタイルは当ててないので画像は変な感じになってると思います。

f:id:TomozQ:20220212122648p:plain

スタイル適応の準備

スタイルを当てていきます。

まずは各フォルダーにstyles.jsというファイルを作っていきます。

client/src/styles.js

client/src/components/Form/styles.js

client/src/components/Posts/styles.js

client/src/components/Posts/Post/styles.js

f:id:TomozQ:20220212124105p:plain

次に各ファイルの中身を書いていきます。

client/src/styles.js

import { makeStyles } from "@material-ui/core/styles";

export default makeStyles((theme) => ({
  appBar: {
    borderRadius: 15,
    margin: '30px 0',
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
  },
  heading: {
    color: 'rgba(0,183,255, 1)',
  },
  image: {
    marginLeft: '15px',
  },
  [theme.breakpoints.down('sm')]: {
    mainContainer: {
      flexDirection: 'column-reverse'
    }
  },
}))

client/src/Form/styles.js

import { makeStyles } from '@material-ui/core/styles';

export default makeStyles((theme) => ({
  root: {
    '& .MuiTextField-root': {
      margin: theme.spacing(1),
    },
  },
  paper: {
    padding: theme.spacing(2),
  },
  form: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'center',
  },
  fileInput: {
    width: '97%',
    margin: '10px 0',
  },
  buttonSubmit: {
    marginBottom: 10,
  },
}));

client/src/Posts/styles.js

import { makeStyles } from '@material-ui/core/styles';

export default makeStyles((theme) => ({
  mainContainer: {
    display: 'flex',
    alignItems: 'center',
  },
  smMargin: {
    margin: theme.spacing(1),
  },
  actionDiv: {
    textAlign: 'center',
  },
}));

client/src/Posts/Post/styles.js

import { makeStyles } from '@material-ui/core/styles';

export default makeStyles({
  media: {
    height: 0,
    paddingTop: '56.25%',
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    backgroundBlendMode: 'darken',
  },
  border: {
    border: 'solid',
  },
  fullHeightCard: {
    height: '100%',
  },
  card: {
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'space-between',
    borderRadius: '15px',
    height: '100%',
    position: 'relative',
  },
  overlay: {
    position: 'absolute',
    top: '20px',
    left: '20px',
    color: 'white',
  },
  overlay2: {
    position: 'absolute',
    top: '20px',
    right: '20px',
    color: 'white',
  },
  grid: {
    display: 'flex',
  },
  details: {
    display: 'flex',
    justifyContent: 'space-between',
    margin: '20px',
  },
  title: {
    padding: '0 16px',
  },
  cardActions: {
    padding: '0 16px 8px 16px',
    display: 'flex',
    justifyContent: 'space-between',
  },
});

それぞれのコンポーネントで呼び出して定義をしておきます。

client/src/App.js

import React, { useEffect } from 'react';
import Posts from './components/Posts/Posts'; //追記
import Form from './components/Form/Form';  //追記
// @material-ui/coreからコンポーネントをimport
import { Container, AppBar, Typography, Grow, Grid } from '@material-ui/core'
// 画像をimport
import memories from './images/memories.jpg'

import useStyles from './styles'  //追記
const App = () => {
  const classes = useStyles()  //追記

  return (
    <Container maxWidth='lg'>
      {/* title部分 */}
      <AppBar className={classes.appBar} position='static' color='inherit'>
        <Typography className={classes.heading} variant='h2' align='center' height='60'>memories</Typography>
        <img className={classes.image} src={memories} alt='memories' height='60'/>{/* importした画像 */}
      </AppBar>
      {/* main部分 */}
      <Grow in>
        <Container>
          <Grid container justifyContent='space-between' alignItems='stretch' spacing={3}>
            {/* 投稿一覧が表示される部分 */}
            <Grid item xs={12} sm={7}>
              {/* 投稿一覧表示コンポーネント */}
              <Posts />
            </Grid>
            {/* 投稿フォームが表示される部分 */}
            <Grid item xs={12} sm={4}>
              {/* 投稿フォーム表示コンポーネント */}
              <Form />
            </Grid>
          </Grid>
        </Container>
      </Grow>
    </Container>
  );
};

export default App;

client/src/components/Form/Form.js

import React from 'react';

import useStyles from './styles'  //追記
const Form = () => {
  const classes = useStyles()  //追記
  return (
    <h1>
      FORM
    </h1>
  );
};

export default Form;

client/src/components/Posts/Posts.js

import React from 'react';
import Post from './Post/Post'
import useStyles from './styles'  //追記
const Posts = () => {
  const classes = useStyles()  //追記

  return (
    <>
      <h1>POSTS</h1>
      <Post/>
      <Post/>
    </>
  );
};

export default Posts;

client/src/components/Posts/Post/Post.js

import React from 'react';
import useStyles from './styles'
const Post = () => {
  const classes = useStyles()
  return (
    <h1>POST</h1>
  );
};

export default Post;

reducer作成

次にapiをまとめるフォルダーを作ります

client/src直下にapiというフォルダーを作成してindex.jsファイルを作成します。

同じくclient/src直下にaction, reducersというフォルダーを用意し、それぞれにposts.jsというファイルを用意します。reducersにはindex.jsファイルも用意します。

それではそれぞれの中身を書いていきます。

の前にreact-reduxをインストールします

% cd client
% npm install react-redux

中身を書いていきます。

client/src/reducers/index.js

import { combineReducers } from "redux";
// client/src/reducers/postsをimport
import posts from './posts'

// combineReducersにpostsを登録する
export default combineReducers({ posts, })

client/src/reducers/posts.js

// 即時関数をそのままexport default
export default (posts = [], action) => {  // state(今回の場合はposts)とactionを引数に取る
  switch (action.type) {
    case 'FETCH_ALL':
      return action.payload
    case 'CREATE':
      return posts  
    default:
      return posts;
  }
}

client/src/actions/posts.js

// client/apiの全てをimport
import * as api from '../api'

// Action Creators 実処理
export const getPosts = () => async (dispatch) => {   // 非同期
  try {
      // client/api/index.jsのfetchPosts関数を実行
      const { data } = await api.fetchPosts()
      // actionにtypeとpayloadを渡す payloadには上で取得したdataを格納する
      dispatch({type: 'FETCH_ALL', payload: data})
  } catch (error) {
    console.log(error.message)
  }

}

client/src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux' //追記
import { createStore, applyMiddleware, compose } from 'redux' //追記
import thunk from 'redux-thunk' //追記

import reducers from './reducers' //追記

import App from './App'

// client/reducers/からreducerをimportし、storeを作成する
const store = createStore(reducers, compose(applyMiddleware(thunk)))

ReactDOM.render(
    // ストアを使用する範囲をProviderで囲みstoreに上で作成したstoreを定義
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
  )

client/src/App.jsも追記します

import React, { useEffect } from 'react';
import Posts from './components/Posts/Posts'; //追記
import Form from './components/Form/Form';  //追記
// @material-ui/coreからコンポーネントをimport
import { Container, AppBar, Typography, Grow, Grid } from '@material-ui/core'
// dispatch使用に使うhooks
import { useDispatch } from 'react-redux'
// client/src/actionsからgetPosts関数をimport
import { getPosts } from './actions/posts'
// 画像をimport
import memories from './images/memories.jpg'

import useStyles from './styles'
const App = () => {
  const classes = useStyles()
  // dispatchを定義
  const dispatch = useDispatch()

  useEffect(() => {
    // getPosts関数を実行
    dispatch(getPosts())
  },[dispatch])

  return (
    <Container maxWidth='lg'>
      {/* title部分 */}
      <AppBar className={classes.appBar} position='static' color='inherit'>
        <Typography className={classes.heading} variant='h2' align='center' height='60'>memories</Typography>
        <img className={classes.image} src={memories} alt='memories' height='60'/>{/* importした画像 */}
      </AppBar>
      {/* main部分 */}
      <Grow in>
        <Container>
          <Grid container justifyContent='space-between' alignItems='stretch' spacing={3}>
            {/* 投稿一覧が表示される部分 */}
            <Grid item xs={12} sm={7}>
              {/* 投稿一覧表示コンポーネント */}
              <Posts />
            </Grid>
            {/* 投稿フォームが表示される部分 */}
            <Grid item xs={12} sm={4}>
              {/* 投稿フォーム表示コンポーネント */}
              <Form />
            </Grid>
          </Grid>
        </Container>
      </Grow>
    </Container>
  );
};

export default App;

ややこしくなってきたので一旦図でまとめてみました。

間違ってたら教えてください・・・

f:id:TomozQ:20220212182745p:plain

長くなってきたので今回はここまでにします。

現在のclientのフォルダ構成はこんな感じになってると思います。

f:id:TomozQ:20220212183420p:plain

次回も引き続きクライアント側の表示/投稿処理を実装していきます。



今回はここまで

続きは次回!

それではまた!