24
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

日本一わかりやすいReact入門【実践編】#1~5 学習備忘録

Last updated at Posted at 2020-05-27

概要

この記事は、Youtubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact入門【実践編】』の自分用学習備忘録です。

本動画シリーズが、react初心者である私にとって非常に分かりやすいものでしたので、少しでも世の中に広めたいという想いを込めて公開します!

学習初期での筆者習熟レベル

#1...Reactでチャットボットを作ろう

開発アプリ概要

  • チャットボットアプリを開発
  • 予めセンテンス用のデータセットは用意しておき、それらを決められたルールで返信をする

使用技術

  • React
    • Class
    • Functional
    • create-react-app
    • Hooks
  • Firebase
    • Hosting
    • Firebase
    • Clound Functions
  • Material-UI
  • Slack(Incoming Webhook)

Material-UI, Slack Webhookについては完全初見です。

#2...サクッと環境構築しよう

create-react-appで環境を構築。今回のアプリ名は『chatbot-demo』。

terminal
$ npx create-react-app chatbot-demo

アプリのディレクトリに移動して、npmサーバーを起動。

terminal
$ npm start

localhost:3000へブラウザでアクセスし、いつもの画面が出てくれば、無事react環境で出来上がっています!

image.png

続いて、Material-UIをnpmへインストール。

terminal
$ npm install --save @material-ui/core @material-ui/icons @material-ui/system

今回は、Material-UIのRoboto FontFont Iconsを使用したいので、headタグ内に追記。

public/index.html
<head>
...
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</head>

これで環境構築は完了!

#3...Firebaseプロジェクトの作成と初めてのデプロイ

Firebaseとは?

  • Googleが提供するmBaaS
  • DB管理からアプリのデプロイまで、バックエンド側の諸々をお任せできる、兎に角なんかすごいサービス
  • Firebaseを利用することで、エンジニア自身はフロントエンド側のコーディングに集中できる

Firebase -ブラウザ上の設定-

アプリを作成 -> コンソールに移動。
image.png

Settings -> リロケーションリソースをasia-northeast1(要するに日本のこと)に設定。
image.png

Webアプリとして使用するよう設定。
image.png

Database -> Cloud Firestoreでデータベースを作成(データベースは本番環境用の方に設定したが、セキュリティールールは別途設定するので、テスト環境用にしても恐らく関係ない)
image.png

Firebase -ターミナル上での設定-

ローカル全体にfirebase-toolsをインストール。

terminal
$ npm install -g firebase-tools

今回作成したアプリディレクトリ内に、firebaseをインストール。これによりpackage.json内に"firebase"が追加される。

terminal
$ npm install --save firebase            

firebaseにログインする。

terminal
$ firebase login

create-react-appで作成したローカル上のアプリと、ブラウザから作成したfirebase上のアプリを接続する。

terminal
$ firebase init
  • Firebase CLI featuresからは、Firestore、Functions、Hostingを選択。
  • Cloud Functionsの言語はTypescriptを選択。
  • ? What do you want to use as your public directory?はデフォルトではpublicになっているが、buildに変更する。なぜなら、create-react-appにおいては、publicは雛形用のディレクトリであり、本番用ディレクトリはbuildと決められているから。
  • ? Configure as a single-page app (rewrite all urls to /index.html)?はYesを選択。今回はSPAとして開発するため。
  • 上記以外はデフォルトでOK

Firebaseセキュリティルールの設定

現時点では、誰でもDBへの書き込みができる危険な状態になっているので、認証済みユーザーのみが書き込みできるよう設定。

firestore.rules.json
...
allow read;
allow write: if request.auth.uid != null;
...

Firebaseへデプロイ

アプリの本番環境用ディレクトリを作成。

terminal
$ npm run build

コンパイルエラーを避けるため、下記をコメントアウト

functions/src/index.ts
// import * as functions from 'firebase-functions';
...

Firebaseへデプロイする。

terminal
$ firebase deploy

デプロイが完了したら、Hosting URL:のところに表示されているURLを開き、確認してみる。
image.png
create-react-appの初期画面で出ていればOK!! Firebaseは本当にデプロイが簡単ですねー

#4...stateの設計とクラスコンポーネントの作成

stateの設計

  • ルートで管理するstate(React側)とデータモデル(Firebase側)が1対1で対応するよう、設計する。
  • データモデルの設計はViewから始めるのが効率的。所謂「ワイヤーフレーム」から作り始める。

動画内ではstate設計の答えが最初から示されていました。ただ、なぜその答えに辿り着いたかを自分なりに納得したかったので、今回は自力でも考えてみました。

1頁目 2頁目
IMG_4714.JPG IMG_4715.JPG

結果、state設計をまとめると以下の通り。

answers: [{key: value}, {key, value}...]

answer: {
    content: string, //回答内容
    nextId: string, //次のcurentId
}

chats: [{key: value}, {key, value}...]

chat: {
    text: string, //チャット本文
    type: string //質問か回答か
}

currentId: string

dataset: {
    "currentId_1": {
        answers: [
            {content:"content_x", nextId:"currentId_x"}
             .
             .
             .
            {content:"content_y", nextId:"currentId_y"}
        ],
        question: "question content_x",
    }
    "currentId_2": {
...
}

open: boolean(true or false)

最後のopenはお問い合わせフォームのモーダル開閉に使用。

stateの定義

stateの設計をApp.jsxに定義する。create-react-appでは、App.jsはFunctional Componentで定義されているが、stateを扱いたいため、Class Componeneに書き換える(拡張子もjsからjsxに変更する)

src/App.jsx
import React from 'react';
import './App.css';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      answers: [],
      chats: [],
      currentId: "init",
      dataset: {},
      open: false
    }
  }
  render(){
    return (
      <div>
      </div>
    );
  }
}

datasetはgithubからコピーして、src/dataset.jsとして保存。

src/dataset.js
const defaultDataset = {
  "init": {
      answers: [
          {content: "仕事を依頼したい", nextId: "job_offer"},
          {content: "エンジニアのキャリアについて相談したい", nextId: "consultant"},
          {content: "学習コミュニティについて知りたい", nextId: "community"},
          {content: "お付き合いしたい", nextId: "dating"},
      ],
      question: "こんにちは!🐯トラハックへのご用件はなんでしょうか?",
  },
  "job_offer": {
      answers: [
          {content: "Webサイトを制作してほしい", nextId: "website"},
          {content: "Webアプリを開発してほしい", nextId: "webapp"},
          {content: "自動化ツールを作ってほしい", nextId: "automation_tool"},
          {content: "その他", nextId: "other_jobs"}
      ],
      question: "どのようなお仕事でしょうか?",
  },
  "website": {
      answers: [
          {content: "問い合わせる", nextId: "contact"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "Webサイト細作についてですね。コチラからお問い合わせできます。",
  },
  "webapp": {
      answers: [
          {content: "問い合わせる", nextId: "contact"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "Webアプリ開発についてですね。コチラからお問い合わせできます。",
  },
  "automation_tool": {
      answers: [
          {content: "問い合わせる", nextId: "contact"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "自動化ツール開発についてですね。コチラからお問い合わせできます。",
  },
  "other_jobs": {
      answers: [
          {content: "問い合わせる", nextId: "contact"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "その他についてですね。コチラからお問い合わせできます。",
  },
  "consultant": {
      answers: [
          {content: "YouTubeで動画を見る", nextId: "https://www.youtube.com/channel/UC-bOAxx-YOsviSmqh8COR0w"},
          {content: "学習コミュニティについて知りたい", nextId: "community"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "トラハックは普段からYouTubeでキャリアについて発信しています。また、僕が運営するエンジニア向け学習コミュニティ内でも相談に乗っていますよ。",
  },
  "community": {
      answers: [
          {content: "どんな活動をしているの?", nextId: "community_activity"},
          {content: "コミュニティに参加したい", nextId: "https://torahack.web.app/community/"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "2020年3月から学習コミュニティを始めました!🎉Webエンジニアへの転職を目指す人向けに、プログラミングを教えたりキャリアの相談に乗っています。",
  },
  "community_activity": {
      answers: [
          {content: "さらに詳細を知りたい", nextId: "https://youtu.be/tIzE7hUDbBM"},
          {content: "コミュニティに参加したい", nextId: "https://torahack.web.app/community/"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "フロントエンド向けの教材の提供、キャリアや勉強法に関するメルマガの配信、週1のオンライン作業会などを開催しています!\n詳細はYouTube動画で紹介しています。",
  },
  "dating": {
      answers: [
          {content: "DMする", nextId: "https://twitter.com/torahack_"},
          {content: "最初の質問に戻る", nextId: "init"}
      ],
      question: "まずは一緒にランチでもいかがですか?DMしてください😘",
  },
}

export default defaultDataset

defaultDatasetという定数を定義し、export defaultでファイル外部から利用できるようしている。これをApp.jsにimportする。

src/App.js
import React from 'react';
import './App.css';
import defaultDataset from "./dataset"

...

css周りを設定

  • githubよりassets/styles/style.cssをコピー
  • App.cssを削除。App.jsxのcss importを上記ファイルに修正
  • index.cssをassets/stylesへ移動。index.jsのimport文を修正

css設定を終えたら、早速App.jsx内でjsxタグを書いてみる

src/App.jsx
import React from 'react';
import './assets/styles/style.css';
import defaultDataset from "./dataset"

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      answers: [],
      chats: [],
      currentId: "init",
      dataset: defaultDataset,
      open: false
    }
  }
  render() {
    return (
      <section className="c-section">
        <div className="c-box">
        </div>
      </section>
    );
  }
}

localhost:3000へ行くと、空の四角い箱が画面に表示されているはずです。

image.png

#5...繰り返し再利用できる関数コンポーネントを作ろう

画面下部に表示させる回答部分をコンポーネントで作る。コンポーネントの構造としては、

  • AnswersList
    • Answer
    • Answer
    • Answer
    • ...

のようなイメージ。選択肢ひとつひとつがAnswerコンポーネントであり、それらを束ねる親コンポーネントとしてAnswersListコンポーネントを定義する。

作成・修正するファイルは、

  • src/App.jsx
  • components/AnswersList.jsx
  • components/Answer.jsx
  • components/index.js

components/index.jsはいわゆるエントリーポイントの役割を担う。全てのコンポーネントを読み込む場所であり、コンポーネントをimportしたい場合は(コンポーネントの種類に依らず)参照先をここにしておけばいい

完成形はこちら。

src/App.jsx
import React from 'react';
import './assets/styles/style.css';
import defaultDataset from "./dataset"
import {AnswersList} from "./components/index"

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      answers: [],
      chats: [],
      currentId: "init",
      dataset: defaultDataset,
      open: false
    }
  }

  initAnswer = () => {
    const initDataset = this.state.dataset[this.state.currentId];
    const initAnswers = initDataset.answers;

    this.setState({
      answers: initAnswers
    })
  }

  componentDidMount() {
    this.initAnswer()
  }

  render() {
    return (
      <section className="c-section">
        <div className="c-box">
          <AnswersList answers={this.state.answers} />
        </div>
      </section>
    );
  }
}

  • AnswersListコンポーネントをimportしている。今回はコンポーネントファイルを直接読みに行かず、エントリーポイントであるcomponents/index.jsを経由している
  • AnswersListにdatasetの値が渡されるまでの時系列を整理すると、
    • App.jsxのMountingが開始。まず最初にconstructor()が走り、各stateに初期値が与えられる
    • constructor()の次はrender()が走り、JSX構造が作られる。この時点では、this.state.answersの中身はconstructor()で定義されたanswers: []となっている
    • render()の次はcomponentDidMount()が走る。initAnswer()関数が実行されることで、answersの中にdatasetの値が代入される
  • ライフサイクルのうち、「最初に1回だけ実行したい」という処理は、componentDidMount()に書く
src/components/AnswersList.jsx
import React from 'react'
import {Answer} from './index'

const AnswersList = (props) => {
  return (
    <div className="c-grid__answer">
      {props.answers.map((value, index) => {
        return <Answer content={value.content} key={index.toString()} />
      })}
    </div>
  )
}

export default AnswersList
  • App.jsxより受けとってprops.answersをmap関数を用いて繰り返し処理
components/Answers.jsx
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const useStyles = makeStyles((theme) => ({
  root: {
  },
}));

const Answer = (props) => {
  return (
    <Button variant="contained" color="primary">
      {props.content}
    </Button>
  )
}

export default Answer
components/index.js
export {default as AnswersList} from './AnswersList'
export {default as Answer} from './Answer'
  • 全てのコンポーネントを読み込ませる。

ここまでで、dataset内のテキストが含まれたボタンが表示されるようになる。

image.png

記事が長くなったのでいったんここまで!

続きは『日本一わかりやすいReact入門【実践編】#6~9 学習備忘録』です。

参考URL

24
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?