今回は、Reactでページの表示部分を作っていきます。
モックアップを作る
まず初めに、ページのモックアップを作ります。
モックアップとは、工業製品の設計・デザイン段階で試作される、外見を実物そっくりに似せて作られた実物大の模型のこと。ソフトウェアやWebサイト、印刷物などのデザインを確認するための試作品のこともこのように呼ばれることがある。
――― IT用語辞典 e-Words
作ろうと思いましたが、この手のモックアップツールは大半が有料みたいなのでペイントで済ませます。
出来ました。シンプルな2カラム右サイドバーのトップページです。
少々荒は目立ちますが、こんな感じで作るページのイメージを画像にしておきます。
ヘッダーは左側にブログのヘッダー画像かブログアイコンとブログタイトルが表示され、右側にドロップダウンメニューやリンクが表示されます。
それぞれの記事はアイキャッチ画像の上にタイトルと概要が表示されます。
サイドバーはまだ何を置くか決めていませんが、ウィジェット形式の何かが作成出来ると良いと思います。
Atomic Design とは
ウェブページを構成する要素を粒度を基準に分類する事で、要素の再利用性の向上や冗長性の削減を目指すという思想らしいです。
要素ベースでページを作るReact等の最近のフロントエンドフレームワークでウェブアプリケーションを作成する場合に役に立ちます。
分類は5つ有り、それぞれに以下のような名前と基準が付いています。
- Atoms (原子) : それ以上分割出来ない要素
- Molecules (分子) : 2つ以上の原子から構成される要素
- Organisms (有機体) : いくつかの分子の集合
- Templates (テンプレート) : データを流し込むための枠組み
- Pages (ページ) : 実際のデータを含むテンプレート
実際にどのような要素が分類されるのかをメモしておきます。
分類 | 要素 |
---|---|
Atoms | Button, Icon, Text, Title |
Molecules | Card, Box, Form, Popup |
Organisms | Header, Calender, Modal, CardList |
Templates | TopPage, SinglePost, CategoryList, RegistrationPage |
Pages | 自己紹介ページとか |
入力フォームは入力のためテキストボックスや送信のためボタン等のAtomの集合なのでMolecule、ヘッダーは様々なアイコン付きボタンや見出しテキストドロップダウンメニューから成り立っているのでOrganismsという感じのようです。
少々難しいですが、ウェブ開発を効率化するための思想に捕らわれて効率が落ちるのは本末転倒なので、あまり深くは考えずこの思想に緩く制約されようと思います。
ちなみに、Atomic Designに階層構造を取り入れたLayered Atomic Designという物もあるようです。
Reactでフロントエンドを作る
では、実際に作っていきます。
Material-UIのインストール
作り始める前に、React用のUIフレームワークの1つであるMaterial-UIをインストールします。
$ cd frontend
$ npm install @material-ui/core @material-ui/icons
コマンドを使って暫く待つとインストール完了です。
Atomic Designのためのディレクトリ構造を作る
前回は、ページを構成する要素を全てfrontend/src/App.js
に記述しました。
Atomic Designでは要素に対し分類がされているので、まずはそれぞれのディレクトリを作成します。
まずは要素を保存するための親ディレクトリをfrontend/src/components/
に作成します。
そしてその中に、atoms
、molecules
、organisms
、templates
、pages
のディレクトリをそれぞれ作ります。
具体的には、以下のようなディレクトリ構造になります。
blogress/
|-- frontend
|-- src/
|-- components/
|-- atoms/ (原子)
|-- molecules/ (分子)
|-- organisms/ (有機体)
|-- templates/ (テンプレート)
|-- pages/ (ページ)
|-- ...
|-- ...
これらのディレクトリに適した物を書く事で、Reactのコンポーネント志向なウェブアプリケーション作成が自然と出来ます。
定数ファイルを作る
定数用のファイルを作る設計が良いのかは置いておいて、定数を置いておくためにfrontend/src/components/constants.jsx
を作成してブログ名等を記述しておきます。
export const BLOG_TITLE = 'My Blogress Life';
export const BLOG_DESCRIPTION = 'This is my blog made in simple blog system named Blogress.'
将来的にはこれをAPIから読み込む形にするので、このように一ヶ所から参照する形にするようにしておくと恐らく便利です。
ヘッダーを作る
何処から作っても良いのですが、まずはヘッダーから作る事にします。
ReactはReactコンポーネントと呼ばれる要素単位を併せてページを作成するので、ヘッダーのReactコンポーネントを作成します。
frontend/src/components/organisms/Header.jsx
に以下のように記述してください。
import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { AppBar, Toolbar, Typography } from '@material-ui/core';
import SportsVolleyballIcon from '@material-ui/icons/SportsVolleyball';
import { BLOG_TITLE } from '../constants';
const useStyles = makeStyles(theme => ({
offset: theme.mixins.toolbar,
}))
const Header = () => {
const classes = useStyles();
return(
<Fragment>
<AppBar position='fixed'>
<Toolbar>
<SportsVolleyballIcon />
<Typography variant="h6" color="inherit" noWrap>
{BLOG_TITLE}
</Typography>
</Toolbar>
</AppBar>
<div className={classes.offset}/>
</Fragment>
);
}
export default Header;
今の所は特に何の機能も無い、ブログタイトルと謎のアイコンだけが存在している簡易なヘッダーです。
Atomic Designでファイル分割を行う
ヘッダーが完成したので早速トップページに設置して確認したい所ですが、ヘッダーのReactコンポーネントを利用する前に、frontend/src/App.js
の中身をAtomic Designに従って分割します。
オレオレAtomic Designかも知れませんが、frontend/src/App.js
を以下のように分割します。
frontend/src/components/templates/TopPageTemplate.jsx
では、トップページの骨組みを作成します。
import React, { useState, Fragment } from 'react';
import Header from '../organisms/Header';
const TopPageTemplate = props => (
<Fragment>
<Header />
{props.posts.map(post => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</Fragment>
);
export default TopPageTemplate;
このテンプレートに対して、frontend/src/components/pages/TopPage.jsx
がブログの記事データをprops
で流し込むような構造にします。
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { makeStyles } from '@material-ui/core/styles';
import TopPageTemplate from '../templates/TopPageTemplate';
const useStyles = makeStyles(theme => ({
page: {
margin : 60,
}
}));
const TopPage = () => {
const classes = useStyles();
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get('http://localhost:8000/api/posts/')
.then(res=>{setPosts(res.data);})
.catch(err=>{console.log(err);});
}, []);
return(
<TopPageTemplate className={classes.page} posts={posts} />
);
}
export default TopPage;
従って、frontend/src/App.js
では以下のようにfrontend/src/components/pages/TopPage.jsx
の表示のみを行います。
import React from 'react';
import TopPage from './components/pages/TopPage';
const App = () => {
return(
<TopPage />
);
}
export default App;
多少オレオレAtomic Designな気がしますが、これが正しいと言い聞かせながら進みます。
では、http://localhost:3000/
にアクセスして確認しましょう。
きちんとヘッダーが付いているのが確認出来ました。
記事タイルグリッドを作る
そのまま記事の一覧表示も作ります。
私のイメージを図にしたモックアップの通り、今回は記事タイルがグリッド表示されている物を作ります。
まずは、Atomic Designに従って、frontend/src/components/PostTile.jsx
に単体の記事タイルを作ります。
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Card, CardActionArea, CardContent, CardMedia, Typography } from '@material-ui/core';
const useStyles = makeStyles({
card: {
position: 'relative',
maxWidth: 345,
},
media: {
height: 345
},
content: {
position: 'absolute',
bottom: 0,
width: '100%',
color: 'white',
backgroundColor: 'rgba(0,0,0,0.6)',
},
excerpt: {
marginRight: 30,
}
});
const PostTile = props => {
const classes = useStyles();
return (
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.media}
image={props.thumbnail}
title={props.title}
/>
<CardContent className={classes.content}>
<Typography gutterBottom variant="h5" component="h2">
{props.title}
</Typography>
<Typography variant="body2" component="p" className={classes.excerpt} noWrap>
{props.body}
</Typography>
</CardContent>
</CardActionArea>
</Card>
);
}
export default PostTile;
そして、記事タイルグリッドはこの記事タイルを複数用いた物なので、frontend/src/components/organisms/PostTileGrid.jsx
に作成します。
import React from 'react';
import { Grid } from '@material-ui/core'
import PostTile from '../molecules/PostTile';
const PostTileGrid = props => (
<Grid container spacing={4}>
{props.posts.map(post => (
<Grid item xs={4}>
<PostTile title={post.title} body={post.body} thumbnail={post.thumbnail}/>
</Grid>
))}
</Grid>
);
export default PostTileGrid;
PostTileGrid
が完成したので、frontend/src/components/templates/TopPageTemplate.jsx
で利用するように書き換えます。
import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Header from '../organisms/Header';
import PostTileGrid from '../organisms/PostTileGrid';
const useStyles = makeStyles(theme => ({
postTileGrid: {
margin: theme.spacing(4),
}
}));
const TopPageTemplate = props => {
const classes = useStyles();
return (
<Fragment>
<Header />
<div className={classes.postTileGrid}>
<PostTileGrid posts={props.posts}/>
</div>
</Fragment>
);
}
export default TopPageTemplate;
また、このままでは左右の余白がギリギリまで詰められてしまうので、frontend/src/index.css
を編集して、body
にmin-width: 960px;
を追加します。
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-width: 960px;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
これで、記事がタイル状でグリッド表示されるようになったはずです。
サムネイル表示にも対応しているため、Djangoの管理画面からposts
のthumbnail
を設定しながら、今は3つしかないサンプル記事をもう少し増やしてから確認します。
これまで見て来た質素な記事リストから、リッチな記事タイルグリッドになったのが確認出来たと思います。
また、frontend/src/index.css
でページの最低幅を指定しているので、横幅が小さくなりすぎても表示が崩れないようになっています。
フッターを作る
最後にフッターを作りましょう。
frontend/src/components/organisms/Footer.jsx
を作成してください。
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Typography } from '@material-ui/core';
import { BLOG_TITLE } from '../constants';
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}{BLOG_TITLE}{' '}{new Date().getFullYear()}{'.'}
</Typography>
);
}
const useStyles = makeStyles(theme => ({
footer: {
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(6),
},
}))
const Footer = () => {
const classes = useStyles();
return(
<footer className={classes.footer}>
<Copyright />
</footer>
);
}
export default Footer;
フッターのReactコンポーネントが完成したので、frontend/src/components/templates/TopPageTemplate.jsx
で利用しましょう。
import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Header from '../organisms/Header';
import Footer from '../organisms/Footer';
import PostTileGrid from '../organisms/PostTileGrid';
const useStyles = makeStyles(theme => ({
postTileGrid: {
margin: theme.spacing(4),
}
}));
const TopPageTemplate = props => {
const classes = useStyles();
return (
<Fragment>
<Header />
<div className={classes.postTileGrid}>
<PostTileGrid posts={props.posts}/>
</div>
<Footer />
</Fragment>
);
}
export default TopPageTemplate;
これで、記事タイルグリッドの後に今の所コピーライトの表示だけしてくれるフッターが表示されるはずです。
記事が作成日時で昇順になっていたりと気になる点は有りますが、完成です。
まとめ
今回で一応なブログのトップページのような形になりました。
成果が目で確認しやすいフロントエンドはやっていて楽しいですね。