Webプログラミング

React + Firebaseのミニアプリを作る【実装編1】

React + Firebaseでミニアプリを作っていきます。

前回の記事で作るアプリの計画をしたので今回は実際にコードを書いていきます。

 

前回の記事はこちらから↓

React + Firebaseのミニアプリを作る【計画編】

サンプルコードばかりでは申し訳ないので、今回から複数回に分けてReactでミニアプリを作っていきます。 サーバー環境はなんでもいいのですが、簡単に使えるので今回はFirebaseを使います。 &nbs ...

続きを見る

 

はじめに

以下の手順で進めていきます。

  1. 必要なパッケージのインストール
  2. コンポーネント設計
  3. ルーティング設定
  4. Material UIのテーマ設定
  5. 各コンポーネントの実装(見た目だけ)

 

ソースコードはこちらから。(進行に応じて更新しますので記事とズレがあります)

(バージョン指定でリンクを貼れたのでズレはないと思います。)

ファイル構成などは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

DetailView

ここからフォームなどの処理を書いていき、CRUDを作っていきます。

次回に続く。

 

 

===

次はCRUD操作の実装をしていきますが、その前にFirebaseでの画像アップロードの記事を投稿予定です。

この記事は参考になりましたか?

下記のボタンよりアンケートにご協力お願いします。

-Webプログラミング
-, ,

© 2020 エンジニアの本棚