React + Firebaseでミニアプリを作っていきます。
前回の記事で作るアプリの計画をしたので今回は実際にコードを書いていきます。
前回の記事はこちらから↓
-

React + Firebaseのミニアプリを作る【計画編】
サンプルコードばかりでは申し訳ないので、今回から複数回に分けてReactでミニアプリを作っていきます。 サーバー環境はなんでもいいのですが、簡単に使えるので今回はFirebaseを使います。 &nbs ...
続きを見る
Contents
はじめに
以下の手順で進めていきます。
- 必要なパッケージのインストール
- コンポーネント設計
- ルーティング設定
- Material UIのテーマ設定
- 各コンポーネントの実装(見た目だけ)
ソースコードはこちらから。(進行に応じて更新しますので記事とズレがあります)
(バージョン指定でリンクを貼れたのでズレはないと思います。)
ファイル構成などはGithubを見たほうがわかりやすいと思います。
補足
Githubからソースを取得した場合はyarn installでセットアップして下さい。
必要なパッケージをインストール
最初にcreate-react-appでアプリを作成し、その後必要なパッケージをインストールしていきます。
Material UI・React Routerをインストールします。
yarn add @material-ui/core @material-ui/icons yarn add react-router-dom
他にも必要になるパッケージはあるのですが、必要になったときにインストールします。
コンポーネント設計
Reactでアプリを作成する際にはコンポーネントをどのように分割するか大まかにでも決めておいたほうがいいです。
どのように分割するかというのはいくつか流派がありますが(例:Atomic Design)、今回はそこまで厳密な設計はしません。
Header & Content
まず全体はヘッダーとコンテンツ部分に分かれます。コンテンツはリスト表示するListViewと詳細表示をするDetailViewをルーティングによって分けます。

ListView
ListViewはCardコンポーネントによって構成されます。
Cardコンポーネントは画像とテキストから構成され、以下のように作ります。

Cardコンポーネント
DetailView
DetailViewはツールバーと「質問と答え」にあたるQuestionAnswerコンポーネントによって構成されます。

Toolbarコンポーネント
QuestionAnswerコンポーネントはQuestionとAnswerによって構成されます。

QuestionAnswerコンポーネント
このQuestionAnswerコンポーネントを複数並べる形になります。
以上が大まかなコンポーネント設計です。
ボタンやフォームなど細かなコンポーネントはたくさん必要ですが、これより先は実際に書きながら調整していけば問題ないでしょう。
ルーティング設定
次にListViewとDetailViewのルーティングを設定しておきます。以下のようなURLでアクセスできるように設定します。
| ListView | / |
| DetailView | /detail/id |
まずApp.jsで全体をBrowserRouterで囲います。HeaderとContentはコンポーネントの形だけ作っておきます。
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import Header from "./components/layout/Header";
import Content from "./components/layout/Content";
function App() {
return (
<Router>
<Header />
<Content />
</Router>
);
}
export default App;React Routerではimport { BrowserRouter as Router } from "react-router-dom";とするのがお決まりらしいのでそのようにします。
次にContent.js内で以下のように設定します。
<Switch>
<Route path="/detail/:id">
<DetailView />
</Route>
<Route path="/">
<ListView />
</Route>
</Switch>Switch内でRouteを設定すると、設定したpathにURLが一致したときにコンポーネントが描画されます。
参考↓
これで<Link to="/detail/2" >の形でリンクを設定すればページ遷移ができます。
Material UIのテーマ設定
本来最初にデザインはしなくてもいいのですが、真っ白いbackground-colorだと目がツライのでMaterial UIのテーマを設定しておきます。
Material UIのテーマに関してはこちらの記事にも書きました。
-

Material-UIでテーマを切り替えられるようにする
Material UIで複数のテーマをユーザが切り替えられるようにします。 完成図はこちら。リロードしてもテーマは維持されたままになります。 Contentsはじめに準備実装Materi ...
続きを見る
Material UIでは簡単にテーマのカスタマイズができます。
App.jsを以下のように書き、paletteの色を設定します。
// import部分は省略
const darkTheme = createMuiTheme({
palette: {
type: "dark",
primary: {
main: "#3FC1C9"
},
secondary: {
main: "#fc5185"
},
background: {
paper: "#364F6B",
default: "#364F6B"
}
}
});
function App() {
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Router>
<Header />
<Content />
</Router>
</ThemeProvider>
);
}
export default App;ThemeProviderで全体を包みます。
ちなみに色に関しては以下のようなサイトで組み合わせを選ぶのがオススメです。
各コンポーネント実装(見た目だけ)
ここから各コンポーネントを実装していくのですが、いきなりサーバーとの通信やCRUDなどは作らずざっくりとした外観を作ります。
ListView
ListViewはカードを並べるだけなので、Material UIのCardの例から必要ないものを削って表示するだけです。
画像に関しては1枚の仮の画像を使います。画像アップロードは結構面倒なので今後実装します。
MediaCard.jsを以下のように書きます。
// import部分は省略
const useStyles = makeStyles(theme => ({
root: {
width: 400,
background: theme.palette.primary.main
},
media: {
height: 200
}
}));
const MediaCard = props => {
const { title } = props;
const classes = useStyles();
return (
<Card className={classes.root}>
<CardActionArea>
<CardMedia
className={classes.media}
image={Image}
title="Contemplative Reptile"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2" align="center">
{title}
</Typography>
</CardContent>
</CardActionArea>
</Card>
);
};
export default MediaCard;
次に作成したMediaCardを使ってListViewを作成します。
サーバーとの通信は後で実装するのでデータ部分はハードコードでOKです。
// import部分は省略
const useStyles = makeStyles(theme => ({
link: {
textDecoration: "none"
}
}));
const ListView = props => {
const classes = useStyles();
const items = {
"0": "JSes6",
"1": "React",
"2": "Firebase"
};
return (
<React.Fragment>
<Grid item xs={12}>
<Box textAlign="center">
<Button variant="contained" color="primary">
Create
</Button>
</Box>
</Grid>
{Object.keys(items).map(ky => (
<Grid item key={ky}>
<Link to={`/detail/${ky}`} className={classes.link}>
<MediaCard title={items[ky]} />
</Link>
</Grid>
))}
</React.Fragment>
);
};
export default ListView;MediaCardコンポーネント全体をLinkで包んでいますので、クリックするとDetailViewに遷移するようになっています。
Header.jsをこのあたりを参考に作ると以下のようなListViewが出来ます。

ListView
DetailView
DetailViewはフォームなどもあるので少し面倒ですが、この段階で作り込む必要はないです。ざっくり書いていきます。
Toolbarの作成
まずはToolbarを作ります。
// import部分は省略
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
justifyContent: "space-between"
},
backButton: {
color: "white"
},
link: {
textDecoration: "none"
}
}));
const DetailToolbar = props => {
const classes = useStyles();
const title = "React";
return (
<Toolbar variant="dense" className={classes.root} disableGutters={true}>
<Link to="/" className={classes.link}>
<Button
variant="contained"
color="primary"
className={classes.backButton}
startIcon={<ArrowBackIosIcon />}
>
Back
</Button>
</Link>
<h1>{title}</h1>
<div>
<IconButton edge="start" color="inherit" aria-label="menu">
<EditOutlinedIcon />
</IconButton>
<IconButton edge="start" color="inherit" aria-label="menu">
<DeleteOutlineIcon />
</IconButton>
<IconButton edge="start" color="inherit" aria-label="menu">
<ImageOutlinedIcon />
</IconButton>
</div>
</Toolbar>
);
};
export default DetailToolbar;
Material UIのToolbarにdisplay: "flex"とjustifyContent: "space-between"を設定してButtonを設置するだけでそれなりの見た目になります。
QuestionAnswerの作成
Question部分は単にテキストを表示するだけなのでデザインだけの問題です。
以下のようなBoxを用意してテキストを埋め込むだけです。
// import部分は省略
const useStyles = makeStyles(theme => ({
questionBox: {
borderColor: theme.palette.primary.main
}
}));
const QuestionBox = props => {
const classes = useStyles();
return (
<Box border={3} p={2} className={classes.questionBox}>
{props.children}
</Box>
);
};
export default QuestionBox;
Answer部分に関しては以下のようにMaterial UIのTextFieldでOK。
<TextField
label="Multiline"
multiline
rows="4"
defaultValue={answer}
variant="outlined"
fullWidth
/>最後にQuestionAnswerコンポーネントとしてQuestionコンポーネントとAnswerコンポーネントをまとめます。(QuestionとAnswerを並べるだけなので、ソースコードは省略)
DetailView全体は以下のようになります。
// import部分は省略
const DetailView = props => {
const questions = {
q1: "「それ」を一言で表すと?",
q2: "「それ」はどんな問題をどう解決した?",
q3: "「それ」の代わりとなるものは?",
q4: "「それ」に関連するキーワードは?",
q5: "「それ」の目次を作るとしたら?"
};
const answers = {
q1: "answer1",
q2: "answer2",
q3: "answer3",
q4: "answer4",
q5: "answer5"
};
return (
<Grid container spacing={5}>
<Grid item xs={12}>
<DetailToolbar />
</Grid>
{Object.keys(questions).map(ky => (
<Grid item key={ky} xs={12}>
<QuestionAnswer question={questions[ky]} answer={answers[ky]} />
</Grid>
))}
<Grid item xs={12}>
<Box textAlign="center">
<Button variant="contained" color="primary">
Save
</Button>
</Box>
</Grid>
</Grid>
);
};
export default DetailView;以上で主な外観は出来上がります。ページ遷移もできているはずです。
こんな感じ。

DetailView
ここからフォームなどの処理を書いていき、CRUDを作っていきます。
次回に続く。
===
次はCRUD操作の実装をしていきますが、その前にFirebaseでの画像アップロードの記事を投稿予定です。



