20201010のReactに関する記事は16件です。

LaravelでExample.jsを表示させてみる

Reactを使う準備はできた
じゃあExample.jsの描画はどうやるんだ?ということで私的まとめ

環境

  • laravel 7.28.4

準備

npm run dev

これを実行する理由は以下

ブラウザで確認するためにnpm run watchコマンドを実行しておきます。npm run watchコマンドを実行しておくとファイルを更新するその更新を検知し自動でコンパイルを実行してくれます。
参考:初めてのLaravel6.xとReact入門

ファイルが変更されるたびにビルドをしてくれるってことですな
実行しておきましょう

表示

表示させるコンポーネントはこちら
公式のまま

Example.js
import React from 'react';
import ReactDOM from 'react-dom';

function Example() {
    return (
        <div className="container">
            <div className="row justify-content-center">
                <div className="col-md-8">
                    <div className="card">
                        <div className="card-header">Example Component</div>

                        <div className="card-body">I'm an example component!</div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default Example;

if (document.getElementById('example')) {
    ReactDOM.render(<Example />, document.getElementById('example'));
}

これを表示するViewを作成

/resources/views/sample.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
  <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>Title</title>
</head>
<body>
<h1>sample</h1>
<div id="example">
    <example-component></example-component>
</div>

<script src="{{ asset('/js/app.js') }}"></script>
</body>
</html>

参考:LaravelでVue.jsを使って開発するファーストステップ

Example.jsではdivのid名appに描画するから,
表示用のViewにそのように設置する

後はルーティング

web.php
Route::get('/sample', function () {
    return view('sample');
});

これでlocahost:8000/sampleにアクセスすれば表示がされる

疑問

  • 無意識にlocahost:8000/sampleにアクセスしたから表示されたもののポートの管理はどこでやっているのか
  • php artisan serveを実行していないのにlocalhost:8000につないだらLaravelのwelcomeページが表示されることからちゃんとルーティングがされているのはなぜか

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactコンポーネントの雛形生成を自動化するスクリプトファイルを作る

エンジニアにとって無駄な作業はストレス。
繰り返しの作業を自動化したい。

という事でコンポーネントファイルを自動生成するスクリプトを書いた。

スクリプトファイルの使い方

まずは使い方から。
ルートディレクトリで

bash ./etc/scripts/make-component-template.sh components Layout

というように、ディレクトリ名とコンポーネント名を指定するだけ。


ルートディレクトリ直下にcomponentsディレクトリがある事を想定しています。
carbon (1).png

自動生成するファイル

./etc/scripts/make-component-template.sh components Layoutを実行すると以下のファイルを生成します。

components
└── Layout
    ├── Layout.jsx
    ├── index.jsx
    └── style.css

ファイルの中身

index.jsx
export { default } from './Layout'
Layout.jsx
import React, { memo } from 'react'
import PropTypes from 'prop-types';
import style from './style.css'

Component.propTypes = {

};

const Component = memo(() => {
  return (
    <div>

    </div>
  );
});


function Container(props) {

  return <Component />
}

Container.propTypes = {

};

export default Container

style.cssは空です。

コンポーネントを生成するためのシェルスクリプト

シェルスクリプト

etc/scripts/make-component-template.sh
#!/bin/bash
if [ $# -ne 2 ]; then
  echo "指定された引数は$#個です。" 1>&2
  echo "実行するには2個の引数が必要です。" 1>&2
  echo "例: components(ディレクトリ名) Layout(コンポーネント名)" 1>&2
  exit 1
fi

DIR=$1
COMPONENT=$2
TARGET="$DIR/$COMPONENT"

if [ -e "$TARGET" ]; then
  echo "ディレクトリ'$TARGET'は既に存在します。" 1>&2
  exit
fi

mkdir "$TARGET"
touch "$TARGET/index.jsx"
echo "export { default } from './$COMPONENT'" > "$TARGET/index.jsx"

cp etc/scripts/component-template.txt "$TARGET/$COMPONENT.jsx"

touch "$TARGET/style.css"

Reactコンポーネントのテンプレート

etc/scripts/component-template.txt
import React, { memo } from 'react'
import PropTypes from 'prop-types';
import style from './style.css'


Component.propTypes = {

};

const Component = memo(() => {
  return (
    <div>

    </div>
  );
});



function Container(props) {

  return <Component />
}

Container.propTypes = {

};

export default Container

補足

  • 生成されるReactコンポーネントの構成を変更したい場合は、component-template.txtファイルを書き換えてください。
  • eslintでエラーが出たりprettierで整形されないように、component-templateの拡張子をtxtにしています。

Enjoy Hacking!?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの開発環境をDockerで作る

動機

React/Reduxの開発環境をDockerで作成した際の備忘録。

環境

macOS Catalina 10.15.5
Docker version 19.03.13
docker-compose version 1.27.4

Dockerfile

From node:latest
WORKDIR /usr/src/app
RUN npm install -g create-react-app

docker-compose

docker-compose.yml
version: '3'
services:
 node:
  build:
   context: .
   dockerfile: Dockerfile
  tty: true
  environment:
   - NODE_ENV=production
  volumes:
   - ./:/usr/src/app
  command: sh -c "cd project_name && yarn start"
  ports:
   - "3000:3000"
項目 意味
version docker-composeのversion
services 構築サービス
build Dockerfileのディレクトリとファイルを指定
tty コンテナを起動し続けるかを決定
environment 環境変数を設定
volumes ローカルのディレクトリが接続(マウント)する作業ディレクトリを指定
command コンテナないで実行するコマンド
ports 外部に対して公開するポート

NODE_ENV

動作環境を設定する
本番環境:production
開発環境:development

command

イメージのbuild

docker-compose build

アプリケーションの作成

docker-compose run --rm node sh -c "create-react-app project_name"

docker-compose runで先ほど作成したイメージを起動し、sample-projectでアプリケーションを作っていきます。runはコンテナを一度だけ起動する。

Redux、Expressライブラリのインストールします。

npm install redux --save
npm install react-redux --save
npm install express --save

インストール確認

npm list --depth=0

コンテナの起動

docker-compose up -d

バックグラウンドで起動。

コンテナの停止

docker-compose down

オプションに-vをつけると、Compose ファイルの volumes セクションの名前付きボリュームを削除する。

Command

動いているコンテナの確認

docker-compose ps

コンテナの削除

docker rm <コンテナID>

イメージの削除

docker rmi <イメージID>

Docker hubへpush

docker imageにタグづけする

docker tag <baseのimage名> <DockerhubID>/<image名>:タグ名

Dockerhubへのログイン

docker login

docker hubにpush

docker push <DockerhubID>/<image名>:タグ名

これでDockerhubのレポジトリに/が追加される。

自動ビルド設定

GitHubと連携する手順を示す。事前にGitHubにレポジトリを用意する必要がある。

Dockerhubから対象のrepositoryを選択 → 「Builds」 → 「Link to GitHub」
スクリーンショット 2020-10-11 9.39.33.png

連携するRepositoryを選択
スクリーンショット 2020-10-11 9.41.43.png

Repositoryを選択すると下の選択画面が開く。AUTESTをOffにすると自動ビルドされないので、それ以外を選択。REPOSITORY LINKSはお好きに。BUILD RULESは自動ビルドの詳細設定。デフォルトでよければこれで。
作成当初はSave and Buildがいいっぽい。
スクリーンショット 2020-10-11 9.47.03.png

これでGitHubにpushされると、DockerHubは自動ビルドになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの開発環境をDockerで作る。イメージの作成→コンテナ起動→DockerHubにpush

動機

React/Reduxの開発環境をDockerで作成した際の備忘録。

環境

macOS Catalina 10.15.5
Docker version 19.03.13
docker-compose version 1.27.4

Dockerfile

From node:latest
WORKDIR /usr/src/app
RUN npm install -g create-react-app

docker-compose

docker-compose.yml
version: '3'
services:
 node:
  build:
   context: .
   dockerfile: Dockerfile
  tty: true
  environment:
   - NODE_ENV=production
  volumes:
   - ./:/usr/src/app
  command: sh -c "cd project_name && yarn start"
  ports:
   - "3000:3000"
項目 意味
version docker-composeのversion
services 構築サービス
build Dockerfileのディレクトリとファイルを指定
tty コンテナを起動し続けるかを決定
environment 環境変数を設定
volumes ローカルのディレクトリが接続(マウント)する作業ディレクトリを指定
command コンテナないで実行するコマンド
ports 外部に対して公開するポート

NODE_ENV

動作環境を設定する
本番環境:production
開発環境:development

command

イメージのbuild

docker-compose build

アプリケーションの作成

docker-compose run --rm node sh -c "create-react-app project_name"

docker-compose runで先ほど作成したイメージを起動し、sample-projectでアプリケーションを作っていきます。runはコンテナを一度だけ起動する。

Redux、Expressライブラリのインストールします。

npm install redux --save
npm install react-redux --save
npm install express --save

インストール確認

npm list --depth=0

コンテナの起動

docker-compose up -d

バックグラウンドで起動。

コンテナの停止

docker-compose down

オプションに-vをつけると、Compose ファイルの volumes セクションの名前付きボリュームを削除する。

Command

動いているコンテナの確認

docker-compose ps

コンテナの削除

docker rm <コンテナID>

イメージの削除

docker rmi <イメージID>

Docker hubへpush

docker imageにタグづけする

docker tag <baseのimage名> <DockerhubID>/<image名>:タグ名

Dockerhubへのログイン

docker login

docker hubにpush

docker push <DockerhubID>/<image名>:タグ名

これでDockerhubのレポジトリに/が追加される。

自動ビルド設定

GitHubと連携する手順を示す。事前にGitHubにレポジトリを用意する必要がある。

Dockerhubから対象のrepositoryを選択 → 「Builds」 → 「Link to GitHub」
スクリーンショット 2020-10-11 9.39.33.png

連携するRepositoryを選択
スクリーンショット 2020-10-11 9.41.43.png

Repositoryを選択すると下の選択画面が開く。AUTESTをOffにすると自動ビルドされないので、それ以外を選択。REPOSITORY LINKSはお好きに。BUILD RULESは自動ビルドの詳細設定。デフォルトでよければこれで。
作成当初はSave and Buildがいいっぽい。
スクリーンショット 2020-10-11 9.47.03.png

これでGitHubにpushされると、DockerHubは自動ビルドになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactHookFormの導入と簡単な使い方

はじめに

今回はReactHookFormの導入と簡単な解説をしていこうと思います。
Reactの環境構築はできているものとします。一応、JavasciptとTypescriptの両方のコードを載せているので自分が使っている方を見てください。

参考:ReactHookFormドキュメント

ReactHookFormとは

ReactHookFormでは、Form内のデータをStateで管理する必要が無くなり、onChangeなどによるレンダリング回数を劇的に減らすことができます。(useCallbackとかでも減らせるっぽい)
さらにバリーデーションも簡単に行うことができます。
早速やっていきましょう!

導入

VSCodeのターミナルなどで以下のコマンドを入力しましょう。

VSCodeのターミナル
# npm
npm install react-hook-form

# yarn
yarn add react-hook-form

これでもう使えるようになります。Typescriptの型定義ファイルも一緒に入ってるのでそのまま使えるみたいです!

簡単な使い方

以下のようなonChangeでvalueを更新して、onSubmitでvalueを表示する簡単なプログラムを書き換えてみましょう!

App.jsx
import React, { useState } from 'react';

export const App = () => {
    const [value, set_value] = useState('');
    const [text, set_text] = useState('');

    const handle_change = (e) => {
        set_value(e.target.value);
    };

    const handle_submit = (e) => {
        e.preventDefault();
        set_text(value);
        set_value('');
    };

    return (
        <>
            <form onSubmit={handle_submit}>
                <input type="text" value={value} onChange={handle_change} />
                <button type="submit">追加</button>
            </form>
            <h1>{text}</h1>
        </>
    );
};

↓以下のように書き換えました!バリデーションも追加しています。

Javascript

App.jsx
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';

export const App = () => {
    const [text, set_text] = useState('');
    const { register, errors, handleSubmit, reset } = useForm();

    const handle_submit = (data) => {
        set_text(data.value);
        reset();
    };

    return (
        <>
            <form onSubmit={handleSubmit(handle_submit)}>
                <input
                    type="text"
                    name="value"
                    ref={register({ required: 'テキストを入力してください' })}
                />
                <button type="submit">追加</button>
            </form>
            <h1>{text}</h1>
            {errors.value && <p>{errors.value.message}</p>}
        </>
    );
};

Typescript

App.tsx
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';

type FormData = {
    value: string;
};

export const App = () => {
    const [text, set_text] = useState('');
    const { register, errors, handleSubmit, reset } = useForm<FormData>();

    const handle_submit = (data: FormData) => {
        set_text(data.value);
        reset();
    };

    return (
        <>
            <form onSubmit={handleSubmit(handle_submit)}>
                <input
                    type="text"
                    name="value"
                    ref={register({ required: 'テキストを入力してください' })}
                />
                <button type="submit">追加</button>
            </form>
            <h1>{text}</h1>
            {errors.value && <p>{errors.value.message}</p>}
        </>
    );
};

上から解説していきます!
まず、react-hook-formからuseFormを名前付きインポートします。

import { useForm } from 'react-hook-form';

useFormから今回使うregister、errors、handleSubmit、resetを分割代入します。

名前 役割
register form内のinputなどの参照、バリデーションなど
errors エラーの表示
handleSubmit formの入力内容を取得
reset form内の入力内容のリセット
const { register, errors, handleSubmit, reset } = useForm();

dataという名前で入力されたデータをとってきて、set_textに入れています。

console.logすればわかるのですが、dataは以下のようなオブジェクトになっています。

data: {
  value: // 入力内容
}

valueとなっているのはinputのname属性を参照しているからです。

その後、resetで入力内容をリセットしています。

const handle_submit = (data) => {
        set_text(data.value);
        reset();
};

先ほど書いたように、name属性にvalueを定義しています。ref属性にregisterを書く必要があり、その後に({})みたいな感じで連想配列が作れて、そこでバリデーションを定義することができます。

errorsのところは入力したvalueがregisterのバリデーションに引っ掛かった時にエラーメッセージを表示する処理を書いています。

return (
        <>
            <form onSubmit={handleSubmit(handle_submit)}>
                <input
                    type="text"
                    name="value"
                    ref={register({ required: 'テキストを入力してください' })}
                />
                <button type="submit">追加</button>
            </form>
            <h1>{text}</h1>
            {errors.value && <p>{errors.value.message}</p>}
        </>
    );

valueをStateで管理する必要が無くなり、バリデーションも簡単に実装できました!

最後に

ここまで読んでいただきありがとうございました!ReactHookFormについて少しでもわかっていただけたら幸いです!
質問やご要望などありましたら、コメントしていただけるとモチベーションにつながります!
今回のでは、入力してない時にボタンをdisableにしたりみたいなのができないので、次はReactHookFormで入力の有無の状態を取得する方法をまとめたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がReact & Firebaseを使って収支管理アプリを作成(解説編)

今回作ったもの

ログイン機能付きの収支管理アプリです。
いわゆる家計簿アプリ的な物です。
毎月の収入と支出をリスト化し月の残高を表示します。

【操作動画】 
*GIFなので画質悪くてすいません。
GIF.gif

感想編については、別途記事を書いてるので、ご興味ございましたら是非ご覧下さいませ!
初心者がReact学習歴1週間でWebアプリ作成に挑戦してみた

コードについて

長くなってしまうので全部は載せておりません。
必要だと思うところだけ解説してます。
スタイルを基本CSSで装飾してますが、classNameは邪魔だと思うのでこの記事では消してます。
全コードはGithubに載せてます。
こちら

使用技術

  • React(version 16.13.1)
    • Router
    • クラスの代わりにHooks (useState, useEffect, useContext)
  • Firebase
    • Authentication
    • Cloud Firestore
    • Hosting

Reactの準備

create react appで作成

npx create-react-app my-app
cd my-app
npm start

Firebaseの準備

プロジェクトを作成

スクリーンショット 2020-10-05 午後3.47.11.png

作成方法は手順に従えば大丈夫です。
簡単なので割愛しますが、全体の流れはこちらのYoutubeを参考にしました。
日本一わかりやすいReact入門【実践編】#3...Firebaseプロジェクトの作成と初めてのデプロイ

注意点として、node.jsとfirebaseのバージョンにより少し挙動が動画内容と異なります。

ポイントは、

  • ロケーション設定は初めにやる。東京なら「asia-northeast1」
  • プランは現在の安定node versionだとSparkではなくBlazeプラン(従量制)になります
  • Blazeプランなので、念の為Google Cloud Platformを作成して、アクセス& 料金状況/アラーム通知を受信できるよう設定します(任意)

Authenticationの設定

Sign-in methodよりステータスを有効にする
今回は「メール/パスワード」を使用
signinmethod.png

Cloud Firestoreでデータベース作成

  • テストモードで実行
  • とりあえずコレクション/ドキュメントを追加してみる(イメージのため)

最終的なコレクション/ドキュメント構成はこちらです。

スクリーンショット 2020-10-05 午後3.50.32.png

Firebaseの設定

Firebaseの情報をwebアプリに登録します。
プロジェクト内容は一応セキュリティを考慮し.envに登録します。
.envgitignoreすれば公開される心配がないということですね。

REACT_APP_FIREBASE_KEY="APIキー"
REACT_APP_FIREBASE_DOMAIN="プロジェクト.firebaseapp.com"
REACT_APP_FIREBASE_DATABASE="https://プロジェクト.firebaseio.com"
REACT_APP_FIREBASE_PROJECT_ID="プロジェクト"
REACT_APP_FIREBASE_STORAGE_BUCKET="プロジェクト.appspot.com"
REACT_APP_FIREBASE_SENDER_ID="ID番号"

srcディレクトリ直下にfirebaseディレクトリとFirebase.jsファイルを作成します。
ここでFirebaseの初期化処理が行われます。
authdbも作り、毎回全部書かなくて済むようにします。

src/firebase/Firebase.js
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";

firebase.initializeApp({
  apiKey: process.env.REACT_APP_FIREBASE_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
});

const auth = firebase.auth();
const db = firebase.firestore();

export { auth, db }

ログイン機能の実装

authディレクトリを作成し、こちらに認証機能系は集約させます。

ディレクトリ構成

src
 ├── auth
    ├── AuthProvider.js
    └── Login.js
    └── PrivateRoute.js
    └── SignUp.js
 ├── components
 ├── firebase
 ├── App.js

まず、App.jsにログイン状態で表示ページを変える為、Routerを作ります。
exactはpathが「含む」とならないように指定。

src/App.js
const App = () => {
  return (
    <AuthProvider>
      <Router>
        <Switch>
          <PrivateRoute exact path="/" component={Home} />
          <Route exact path="/login" component={Login} />
          <Route exact path="/signup" component={SignUp} />
        </Switch>
      </Router>
    </AuthProvider>
  );
};

export default App;

AuthPrivider.js

認証の情報(ユーザーがログイン、サインアップする)は、こちらで作ります。
そしてユーザー情報が必要なコンポーネントでuseContextを使います。
通常データはトップダウン形式でpropsを渡さないといけないですが、
contextを使うことで、コンポーネントツリーに簡単にデータを共有することができます。

useContextについてこちらの記事が非常にわかりやすかったです。
useContextの使い方
こんなに簡単なの?React useContextって

src/auth/AuthProvider.js
import React, { useEffect, useState } from "react";
import { auth } from "../firebase/Firebase";

const AuthContext = React.createContext() 

const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);

  //サインアップ後認証情報を更新
  const signup = async (email, password, history) => {
    try { 
      await auth.createUserWithEmailAndPassword(email, password);
      auth.onAuthStateChanged(user => setCurrentUser(user));
      history.push("/");
    } catch (error) {
      alert(error);
    }
  };

  //ログインさせる
  const login = async (email, password, history) => {
    try {
      await auth.signInWithEmailAndPassword(email,password);
      auth.onAuthStateChanged(user => setCurrentUser(user));
      history.push("/");
    } catch (error) {
      alert(error);
    }
  }

  //初回アクセス時に認証済みかチェック
  useEffect(() => {
    auth.onAuthStateChanged(setCurrentUser);
  }, []);

  return (
    <AuthContext.Provider value={{ signup, login, currentUser}}>
      {children}
    </AuthContext.Provider>
  )
}

export {AuthContext, AuthProvider}

初期値のユーザーのステートはnullですね。

signup関数
引数にemailpasswordhistoryを渡して非同期処理を行います。
auth.createUserWithEmailAndPassword(email, password)は、
firebaseのメソッドでemailpasswordを元にアカウントが作成されます。
その後userの情報を取得し、CurrentUserにセットします。

history.push("/")は、reactRouterの画面遷移させる機能です。
今回はログイン後、Home画面に行きます。

login関数
同じ様に、ユーザーがログインしたら情報を取得しCurrentUserを更新するようにします。
auth.signInWithEmailAndPassword(email,password)
これもまたfirebaseのメソッドです。

あとは、最初にログインしてるか確認する為、useEffectで一回だけauth.onAuthStateChangedを実行します。
※一回だけ実行したいので、第二引数には空の配列[]を渡します。

PrivateRoute.js

アプリのメイン画面Home.jsは、PrivateRouteに指定します。
ここで、ユーザーがログインしてれば→メイン画面を表示。
未ログインの場合→ログイン画面を表示。の作業を行なってます。

src/auth/PraveteRoute.js
import React, { useContext } from "react";
import { Route } from "react-router-dom";
import { AuthContext } from "./AuthProvider";
import Login from "./Login";

const PrivateRoute = ({ component, ...rest}) => {
  const { currentUser } = useContext(AuthContext);
  //AuthContextからcurrentUserを受け取る

  const Component = currentUser ? component : Login;
  //currentUserがtrueの場合component=Home、falseならLoginコンポーネントにroute

  return <Route {...rest} component={Component} />;
};

export default PrivateRoute;

...restは、残りのpropsをまとめて引数に渡してます(今回他のpropsはないですが)
この...ですが、以前RestParametersの記事を書きましたのでよろしければご参照ください。
ES6の新しい構文です!
スプレッド構文とRestパラメータを理解する

SignUp.jsとLogin.js

SignUp.jsコンポーネントでは、ユーザー登録画面の表示、登録内容を取得します。
handleSubmitが実行される時、入力されたemailpasswordの内容をAuthProviderで作ったsignup関数の引数に渡してデータが登録されます。
アップデート後のhistory(情報)を渡すために、最後exportの時withRouter(SignUp)を使っています。

src/auth/SignUp.js
const SignUp = ({ history }) => {
  const { signup } = useContext(AuthContext);
  //AuthContextからsignup関数を受け取る

  const handleSubmit = event => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    signup(email.value, password.value, history);
  };

  return (
      <div>
        <h1>Sign Up</h1>
        <form onSubmit={handleSubmit}>
          <div>
            <label>E-mail Address</label>
            <input name="email" type="email" placeholder="email@gmail.com" />
          </div>
          <div>
            <label>Password</label>
            <input name="password" type="password" placeholder="Password"/>
          </div>
            <SignUpButton type="submit">SIGN UP</SignUpButton>
        </form>
        <Link to="/login">SignInへ戻る</Link>
      </div>
    </div>
  );
};

export default withRouter(SignUp);

Login.jsも似たような感じで作ります。
signup部分をloginに変えるだけですね。

src/auth/Login.js
const Login = ({ history }) => {
  const { login } = useContext(AuthContext);
  //AuthContextからlogin関数を受け取る

  const handleSubmit = event => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    login(email.value, password.value, history);
  };

コンポーネント・ファイル構成

続いてメイン画面です。
アプリのメイン画面を担うコンポーネント達がこちら。
メイン(親)のコンポーネントは、Home.jsになります。

src
 ├── auth
 ├── components
    ├── Home.js
    └── Header.js
    └── Balance.js
    └── IncomeExpense.js
    └── AddItem.js
    └── ItemsLists.js
    └── IncomeItem.js
    └── ExpenseItems.js
    └── TotalIncomeExpense.js //共通関数ファイル
 ├── firebase
 ├── App.js

画面上ではこんな感じ。
main.png

ステートの作成/更新

親コンポーネントから子コンポーネントに渡す為、ステートは全てHome.jsで作成します。

作成したステート達

src/compoments/Home.js
  const [inputText, setInputText] = useState("");
  const [inputAmount, setInputAmount] = useState(0);
  const [incomeItems, setIncomeItems] = useState([]);
  const [expenseItems, setExpenseItems] = useState([]);
  const [type, setType] = useState("inc")
  const [date, setDate] = useState(new Date());

収入incomeの配列incomeItemsと、支出expenseの配列expenseItemsは、後々計算が楽なので分けて作成。

データを追加する

Firestoreからデータを取得・追加は全てHome.jsで行います。

こちらは追加バージョン

src/compoments/Home.js
  const addIncome = (text, amount) => {
    const docId = Math.random().toString(32).substring(2);
    const date = firebase.firestore.Timestamp.now();
    db.collection('incomeItems').doc(docId).set({
      uid: currentUser.uid,
      text,
      amount,
      date,
    })
    .then(response => {
      setIncomeItems([
        ...incomeItems, {text: inputText, amount: inputAmount, docId: docId , date: date}
      ]); 
    })
  }
  • 収入income用の関数addIncomeを用意。引数にはユーザーが入力したtextamountを渡す。
  • docIdをこちらで作る。
  • 収入リストが順番に並べられるようにdateを作成。firebase.firestore.Timestamp.now()(入力時間が登録される)→firebaseのメソッド
  • どこのcollectionのdocumentに追加するかは、firebaseのメソッドを使用db.collection('incomeItems').doc(docId).set({})
  • setしたいデータを配列として追加uid ~ date
  • その後.thenで、reactアプリ側のsetIncomeItemsのステートを更新

ポイントは、
ユーザーが削除ボタンを押した時、データを削除するのにdocIdを使います。
reactアプリと連動させたいので、こちら側で手動で作成してます。
※その時、数値だとエラーになるので文字列に変換.toString(32).substring(2)
手動で作らない場合は、「.set」ではなく「.add」で自動生成可能。

これと同じ内容で出費expense用の関数も用意すれば、値はFirestoreとReact上で無事追加/更新されます。

データを取得する

Firestoreからデータを取ってきて、アプリ上で表示させます。

src/compoments/Home.js
  const getIncomeData = () => {
    const incomeData = db.collection('incomeItems')
    incomeData.where('uid', '==', currentUser.uid).orderBy('date').startAt(startOfMonth(date)).endAt(endOfMonth(date)).onSnapshot(query => {
      const incomeItems = []
      query.forEach(doc => incomeItems.push({...doc.data(), docId: doc.id}))
      setIncomeItems(incomeItems);
    })
  }
  • 取得したいデータのIncome用の関数getIncomeDataを作成
  • コレクションincomeItemsのドキュメントを取得→変数incomeDataに代入
  • uidが現在のユーザーと一致する場合のstartOfMonth~endOfMonthのドキュメントを取得
  • 取得したデータを保存する空の配列incomeItemsを作成
  • その配列にドキュメントのdataidpush(追加)する
  • reactアプリ側のincomeItemsの配列を更新する

ポイントは、
orderByのメソッドでdateを昇順にしてます。
リストがバラバラに表示されてしまうので、制御する為に必要です。
また、orderByで昇順にしようとするとfirebaseから「indexを作れ」というエラーが出ます。
その際、親切にURLが表示されるのでそこにアクセスしてindexを作ればOKです(結構時間かかります)
参考記事:複合index

startAtendAtは、その月の分だけ表示させる為です。
この引数の中身については、後ほど詳細を書きます。

これと同じ内容で出費expense用の関数も用意すれば、無事Firestoreからデータを取得できて、Reactのステートに更新/表示がされます。

尚、データを取得するタイミングは、useEffectを使って操作します。

  1. 最初の1回のみ実行してほしい。引数は空の配列。
  2. dateが更新されるたびに実行してほしい。※dateは次で解説してますがヘッダーの月の部分です。
src/compoments/Home.js
  useEffect (() => {
    getIncomeData();
    getExpenseData();
  }, []);

  useEffect(() => {
    getIncomeData();
    getExpenseData();
  }, [date]);

月ごとにデータを表示させる

月ごとにデータを分けて表示させる為、ステートで作ったdateを使います。
初期値は現在の日時が入ってます。

src/compoments/Home.js
const [date, setDate] = useState(new Date());

ヘッダーに渡して表示

dateは、ユーザーがヘッダーの"前月"か"次月"ボタンを押すと更新されます。
↓の関数で月の部分を変えてます。

src/compoments/Home.js
  //for Header
  const setPrevMonth = () => {
    const year = date.getFullYear();
    const month = date.getMonth()-1;
    const day = date.getDate();
    setDate(new Date(year, month, day));
  }

  const setNextMonth = () => {
    const year = date.getFullYear();
    const month = date.getMonth()+1;
    const day = date.getDate();
    setDate(new Date(year, month, day));
  }

これらはHeader.jsで使うのでpropsで渡してあげます。

src/compoments/Home.js
<Header 
  date={date}
  setPrevMonth={setPrevMonth}
  setNextMonth={setNextMonth}
/>

Header.jsでは、現在の月を表示させる為、yearmonthを作り、
隣に前月と次月ボタンを表示させます。

src/compoments/Header.js
  const today = date;
  const year = today.getFullYear();
  const month = today.getMonth()+1;

  return (
    <div className="head">
      <SignOutButton onClick={() => auth.signOut()}>Sign Out</SignOutButton>
      <div>
        <button onClick={() => setPrevMonth()}>←前月 </button>
        <h1>{year}{month}</h1>
        <button onClick={() => setNextMonth()}> 次月→</button>
      </div>
    </div>
  )

これでボタンをクリックしたら、setPrevMonth()setNextMonth()が実行され、月の表示が変わります。

ユーザーの入力内容を操作する

ユーザーが入力した内容を取得する関数は、AddItem.jsコンポーネントで行ってます。

src/components/AddItem.js
export const AddItem = ({ addIncome, addExpense, inputText, setInputText, inputAmount, setInputAmount, type, setType, selectedMonth, thisMonth}) => {

  const typeHandler = (e) => {
    setType(e.target.value);
  }

  const inputTextHandler = (e) => {
    setInputText(e.target.value);
  };

  const inputAmountHandler = (e) => {
    setInputAmount(parseInt(e.target.value));
  }

  const reset = () => {
    setInputText("");
    setInputAmount("");
  }

  const submitItemHandler = (e) => {
    e.preventDefault();
    if (inputText == '' || inputAmount == '0' || !(inputAmount > 0 && inputAmount <= 10000000)) {
      alert ('正しい内容を入力してください')
    } else if ( type === 'inc') {
      addIncome(inputText, inputAmount) 
      reset();
    } else if ( type === 'exp' ) {
      addExpense(inputText, inputAmount)
      reset();
    }
  }

  const thisMonthForm = () => {
    return (
      <form>
        <select onChange={typeHandler}>
          <option value="inc">+</option>
          <option value="exp">-</option>
        </select>
        <div>
          <label>内容</label>
          <input type="text" value={inputText} onChange={inputTextHandler}/>
        </div>
        <div>
          <label>金額</label>
          <input type="number" value={inputAmount} onChange={inputAmountHandler}/>
          <div></div>
        </div>
        <div>
        <AddButton type="submit" onClick={submitItemHandler}>追加</AddButton>
        </div>
      </form> 
    )
  }

  const otherMonthForm = () => {
    return (
      <form></form>
    )
  }

  return (
    <>
    {thisMonth === selectedMonth ? thisMonthForm() : otherMonthForm()}
    </>
  )

} 
  • 収入income、出費expense、どちらに追加するのかはtypeで分けてます。
  • ユーザーが、option を選択した時にtypeHandler関数でtypeの値を更新します。
  • inputの値とamountの値もonChangeで取得します。
  • amountについては、後に計算するのでparseIntで値を数値化します。

submitItemHandlerで追加ボタンが押された時、操作してる内容はこちら。

  • デフォルトのイベント(動作)をキャンセル
  • 正しい内容が入力されてない場合、エラーを表示
  • incタイプなら、Home.jsで定義したaddIncomeの引数に ユーザーの入力内容inputTextinputAmountを渡す
  • expタイプも同様

→Firestoreのデータが追加され、reactアプリのステートも更新されるという流れ。

ヘッダーが今月なのか、今月でない月かによって表示方法を変える為、条件付きレンダーを行ってます。
(※今月のみ追加フォームを表示させる為)

この条件に使ってるselectedMonththisMonthは、
他のコンポーネント(リスト)でも使うので、Home.jsで作ってpropsで渡してます。

src/components/Home.js
  //operate add form and income/expense list
  const selectedMonth = date.getMonth() + 1;
  const today = new Date();
  const thisMonth = today.getMonth() + 1;

リストの表示

リストの表示はItemsList.jsを作り、そこでitemsに対してmapを行い、IncomeItemExpenseItemをそれぞれ表示します。

src/components/ItemsList.js
export const ItemsList = ({ deleteIncome, deleteExpense, incomeItems, expenseItems, incomeTotal, selectedMonth, thisMonth}) => {

  return (
    <div>
      <div>
        <h3>収入一覧</h3>
          <ul>
            {incomeItems.map((incomeItem) => (
              <IncomeItem 
                deleteIncome={deleteIncome}
                incomeText={incomeItem.text}
                incomeAmount={incomeItem.amount}
                incomeItem={incomeItem}
                key={incomeItem.docId}
                selectedMonth={selectedMonth}
                thisMonth={thisMonth}
              />
            ))}
          </ul>
      </div>
      <div>
        <h3>支出一覧</h3>
        <ul>
            {expenseItems.map((expenseItem) => (
              <ExpenseItem
                deleteExpense={deleteExpense}
                expenseText={expenseItem.text}
                expenseAmount={expenseItem.amount}
                expenseItem={expenseItem}
                key={expenseItem.docId}
                incomeTotal={incomeTotal}
                selectedMonth={selectedMonth}
                thisMonth={thisMonth}
              />
            ))}
          </ul>
      </div>
    </div>
  )
}

mapを実行して作られた一つ一つの項目を表示されるコンポーネントがこちら↓

Incomeバージョン

src/components/IncomeItem.js
export const IncomeItem = ({ deleteIncome, incomeItem, incomeText, incomeAmount, thisMonth, selectedMonth}) => {

  const deleteHandler = () => {
    deleteIncome(incomeItem.docId);
  }

  const showThisMonth = () => {
    return (
      <li>
      <div>{incomeText}</div>
      <div>+{Number(incomeAmount).toLocaleString()}</div>
      <button onClick={deleteHandler}>×</button>
      </li>
    )
  }

  const showPastMonth = () => {
    return (
      <li>
      <div>{incomeText}</div>
      <div>+{Number(incomeAmount).toLocaleString()}</div>
      </li>
    )
  }

  return (
    <>
      {thisMonth === selectedMonth ? showThisMonth() : showPastMonth()}
    </>
  )
}

Number(incomeAmount).toLocaleString()は、カンマ「,」を表示させる為。
deleteHandlerは "×" を押した時にdleteIncomeを実行してます。
このdelteIncomeは、fireStoreのdocIdの関係上、Home.jsで定義されてます。↓
引数にこのincomeItem.docIdを渡せば該当のアイテムは削除されます。

src/compoments/Home.js
  const deleteIncome = (docId) => {
    db.collection('incomeItems').doc(docId).delete()
  }

firebaseのメソットを使い、incomeItemsコレクションにある、該当ドキュメントを削除してます。

あとは、スタイル上、表示の仕方を変えたいので、ここでも条件付きレンダーを行ってます。
→ showThisMonthshowPastMonthで分ける。
これで今月以外、削除ボタンを表示させないようにしてます。

これと同じようにexpenseバージョンも作ればOK

収入/支出の値を計算する

収入と支出の各合計

値の計算はそれぞれのincomeItemsexpenseItemsの配列から、amountを取り出して計算します。

src/components/IncomeExpense.js
export const IncomeExpense = ({ incomeTotal, expenseItems }) => {

  const expenseAmounts = expenseItems.map(expenseItem => expenseItem.amount);

  const expenseTotal = expenseAmounts.reduce((acc, cur) => acc += cur, 0);

  const percentage = () => {
    if (incomeTotal >= 1) {
      return `${Math.round((expenseTotal / incomeTotal) * 100)} %`;
    } else {
      return '---';
    }
  };

  return (
    <div>
      <div>
        <h2>収入</h2>
        <div>
          <p>+ {Number(incomeTotal).toLocaleString()}<span> </span></p>
      </div>
        </div>
      <div>
        <h2>支出</h2>
        <div>
          <p>- {Number(expenseTotal).toLocaleString()}<span> </span></p>
          <div>{percentage()}</div>
        </div>
      </div>
    </div>
  )
}

mapreduceについては、こちらを元に別で記事を書いています。
filterは途中色々変えたので、結局今回のアプリに使っていません。

map・filter・reduceの書き方・使い方

支出expenseの合計計算
- expenseAmountexpenseItemsの中のamountだけ取り出した配列を代入します。
- expenseAmountを使って、累計を計算し、expenseTotalとします。

これを収入incomeItemsでも同じことをします。
ただincomeバージョンについては、他のコンポーネントでも使うので、共通関数ファイルTotalIncome.jsを作成してます。

TotalIncome.jsで関数totalCalcを作り、Home.jsincomeItemsを引数に渡してます。

src/components/Home.js
  // calculate % and show total
  const incomeTotal = totalCalc(incomeItems);
src/components/TotalIncome.js
export const totalCalc = (incomeItems) => {
  const incomeAmounts = incomeItems.map(incomeItem => incomeItem.amount);
  return incomeAmounts.reduce((acc, cur) => acc += cur, 0);
};

こちらを使う他のコンポーネントとは、
割合%を表示するIncomeExpense.jsExpenseItemと、計算に必要なBalance.jsになります。

残高の計算

総合計の残高を計算をするBalance.jsコンポーネントでは、
IncomeTotal - ExpenseTotalをすれば計算できます。

src/components/Balance.js
const balance = incomeTotal - expenseTotal

アプリを公開

あとは公開するだけです!
私の場合はfirebase logininitは先に済ませてました。

> firebase login
> firebase init
- 選択項目は FireStore, Functions, Hosting
- publicディレクトリはbuildにする
> npm run build
> firebase deploy

あとがき

今回のアプリは全てデータをpropsで親から子供に渡しているので、
どうしてもメインのHome.jsが少しボリューミーになってしまいました。(そういうものなのか?)
初心者が書いたコードですので、ご理解いただければと思います。
こうした方が良い等ありましたら、是非ご指摘お願いします!
日々勉強して、もっと良い書き方でコードを書けるよう頑張ります。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザーに画像をトリミングしてもらう(react-image-crop)

動機

アイコン画像などを正方形にトリミングしてから、投稿してほしい。
Canvaで実装しようとしたけれども、なかなかイケてない(以前の記事
調べたらreact-image-cropってのがあるらしい、使ってみよう。

環境

  • react 16.13.1
  • react-image-crop 8.6.6
  • react-bootstrap 1.3.0
  • Laravel 7.x (本稿ではあまり関係ないです)

結果のイメージ

投稿した画像をModalで表示し、トリミングしてからアップロードできるようになっています。

react-image-cropのインストール

GitHubを参考にして、インストールしました。

npm install react-image-crop --save

react-bootstrapのインストール

画像をアップロードしてもらった後に、bootstrapのModalを簡単に利用するために、今回インストールしました。
公式 を参照し、react-bootstrapをインストールしました。bootstrap自体は既にインストール済みだったので今回は省略しました。

npm install react-bootstrap --save

使い方

使い方は、GitHubを参考にしています。
公式ではHooks使っていますが、勉強不足のため今回はクラスを使用しました。

render

ReactCropコンポーネントをModalの中に入れました。ReactCropには様々な値を渡します。今回利用したのは以下の4つです。詳細はGitHub会津ラボのサイトが参考になると思います。

  • src (画像のソース)
  • crop(トリミング時の幅などのプロパティ)
  • keepSelection(領域外をクリックしたときに選択を解除するかどうか)
  • onChange(トリミング幅や位置が変わったときに発火するイベント)
app.js
import React from 'react';
import ReactCrop from 'react-image-crop';
import 'react-image-crop/dist/ReactCrop.css';
import { Modal, Button } from 'react-bootstrap';
import ReactDOM from 'react-dom';

 export default class UserImageInput extends React.Component {

 ...

  render() {
    const image = this.state.image;
    const src = this.state.src;
    const crop = this.state.crop;
    const show = this.state.show;
    return (
      <div className="ml-5">
        <UserImage image={image} />
        <label htmlFor="user-image" className="d-block">
          <input
            type="file"
            accept="image/jpeg,image/png"
            name="user-image"
            id="user-image"
            onChange={this.onChangeImage}
          />
        </label>

        <ModalWindow
          show={show}
          setShow={this.setShow}
          trimming={this.trimming}
        >
          <ReactCrop
            src={src}
            crop={crop}
            keepSelection={true}
            onChange={(newCrop) => this.setCrop(newCrop)}
          />
        </ModalWindow>
      </div>
    );
  }
}

画像のトリミング

基本的にはGitHubを参考にして、drawImageしています。その後はBASE64に変換してデータをstateにsetしてトリミング後の画像を画面に表示しています。

app.js
export default class UserImageInput extends React.Component {

...

  trimming() {
    this.setShow(false);
    const crop = this.state.crop;
    const image = this.resizedImage;

    const canvas = document.createElement('canvas');
    const scaleX = image.naturalWidth / image.width;
    const scaleY = image.naturalHeight / image.height;
    canvas.width = crop.width;
    canvas.height = crop.height;
    const ctx = canvas.getContext('2d');

    ctx.drawImage(
      image,
      crop.x * scaleX,
      crop.y * scaleY,
      crop.width * scaleX,
      crop.height * scaleY,
      0,
      0,
      crop.width,
      crop.height
    );

    const contentType = image.src.split(';')[0].split(':')[1];
    const trimmedSrc = canvas.toDataURL(contentType);

    // TODO: 閉じるときに、アニメーションになるときとならないときがある。bootstrap側の問題?
    this.setShow(false);
    this.setImage(trimmedSrc);
  }

  ...

Modal

ここは、React Bootstrapを使って、Modalを表示しています。props.childrenにはReactCropコンポーネントが渡されます。OKボタンのonClickに、先ほどつくったtrimmingメソッドを渡しています。

app.js
function ModalWindow(props) {
  const show = props.show;
  const setShow = props.setShow;

  const handleClose = () => setShow(false);
  const trimming = props.trimming;

  return (
    <>
      <Modal
        show={show}
        onHide={handleClose}
        backdrop="static"
        keyboard={false}
        size="lg"
        aria-labelledby="contained-modal-title-vcenter"
        centered
      >
        <Modal.Header closeButton>
          <Modal.Title>画像のトリミング</Modal.Title>
        </Modal.Header>
        <Modal.Body className="text-center">{props.children}</Modal.Body>
        <Modal.Footer>
          <Button variant="secondary" onClick={handleClose}>
            キャンセルする
          </Button>
          <Button variant="primary" onClick={trimming}>
            OK
          </Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

constructor

ここでは、stateの設定と、使う関数をbindしています。

app.js
export default class UserImageInput extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      src: null,
      show: false,
      crop: {
        aspect: 1,
        unit: '%',
      },
      image: this.props.image ?? null,
    };

    this.onChangeImage = this.onChangeImage.bind(this);
    this.setCrop = this.setCrop.bind(this);
    this.setShow = this.setShow.bind(this);
    this.readImage = this.readImage.bind(this);
    this.resize = this.resize.bind(this);
    this.trimming = this.trimming.bind(this);
  }

  ...

画像の読み込み

画像の読み込みが終わったらstateが更新されて、Modalのshowにtrueが渡されて、トリミングできるようになります。

app.js
export default class UserImageInput extends React.Component {

...

  onChangeImage(e) {
    const reader = new FileReader();
    const file = e.target.files[0];

    // 値を初期化しないと、
    // もう一回画像をしようとしてキャンセルするとonChangeが発火するけど画像は無い、っていう状態になる。
    e.target.value = null;

    reader.readAsDataURL(file);

    reader.onload = () => {
      this.readImage(reader.result);
    };
  }

  resize(image) {
    // 幅の指定、ここを最適化する必要あり。
    const modalWidth = 500;

    if (image.width > modalWidth) {
      const scale = modalWidth / image.width;
      image.width = modalWidth;
      image.height = image.height * scale;
    }

    this.resizedImage = image;

    return image;
  }

  readImage(src) {
    const image = new Image();
    image.src = src;
    image.onload = () => {
      const aspect = image.height / image.width;
      const crop = this.state.crop;

      if (aspect > 1) {
        crop.width = 100;
      } else {
        crop.height = 100;
      }

      // Canvasを利用して、リサイズしたImageをBase64形式にする。
      const resizedImage = this.resize(image);

      const canvas = document.createElement('canvas');
      canvas.width = this.resizedImage.width;
      canvas.height = this.resizedImage.height;

      const ctx = canvas.getContext('2d');
      ctx.drawImage(
        resizedImage,
        0,
        0,
        resizedImage.width,
        resizedImage.height
      );

      // src -> data:image/jpeg;base64........
      const contentType = src.split(';')[0].split(':')[1];
      const resizedSrc = canvas.toDataURL(contentType);
      // ctx.clearRect(0, 0, resizedImage.width, resizedImage.height);

      this.setState({
        src: resizedSrc,
        crop: crop,
        show: true,
      });
    };
  }

  ...

ユーザー画像の表示

トリミング前後の画像の表示になります。トリミング前は用意しておいた他の画像を表示します。

app.js
function UserImage(props) {
  if (props.image) {
    return (
      <img
        className="img-fluid"
        src={props.image}
        alt="user-image"
      />
    );
  } else {
    return (
      <img
        className="img-fluid"
        src="/img/icon.svg"
        alt="user-image"
      />
    );
  }
}

setState

stateを更新する関数たちです。

app.js
export default class UserImageInput extends React.Component {

...

 setShow(isShown) {
    this.setState({
      show: isShown,
    });

    // 閉じるときは初期化する。
    if (!isShown) {
      const crop = {
        aspect: 1,
        unit: '%',
      };

      this.setCrop(crop);
    }
  }

  setImage(image) {
    this.setState({
      image: image,
    });
  }

  setCrop(newCrop) {
    this.setState({
      crop: newCrop,
    });
  }

  ...

参考サイト

https://github.com/DominicTobias/react-image-crop
https://react-bootstrap.github.io/components/modal/
https://www.aizulab.com/blog/react-image-crop/

所感

ライブラリを使用することで、イケてるトリミング機能が作成できました。
(現在、サーバー側(Laravel)に送信してからS3にアップロードするところでつまずいています。。。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React入門 学習メモ

はじめに

以下の知識の習得を目的として行った学習メモになります。

  • Reactの基礎知識
  • JSXについて
  • create-react-appでの環境構築
  • Reactコンポーネントの作り方
  • モジュールのimport、exportについて
  • React Hooksについて

Reactの基礎知識

Reactとは?

  • Facebookが開発
  • JavaScriptのライブラリ(フレームワークではない)
  • WebのUIを作る
  • React ≠ SPA

コンポーネントとは?

UIは2つに分類される
1. 見た目(View)
2. 機能(Controller)

コンポーネント = 見た目 + 機能

Webページはコンポーネントのツリー構造になっている

なぜコンポーネントを使うのか

  • 再利用性するため
  • 分割統治するため
  • 変更に強くするため

Virtual DOM

そもそもDOMとは?

→ Document Object Modelの略
→ インターフェース
→ HTMLにアクセスする窓口
→ HTML構造、見た目、コンテンツを変更したいときはDOMを通して操作を行う

Virtual DOMとは?

Reactで管理するDOM。
通常のDOMはブラウザのレンダリングによって管理されるが
Reactではブラウザのレンダリングと別で管理を行う
→効率よくDOM操作できる

通常のDOM操作

document.getElementById('hoge').innerText='fuga';

ReactのVirtual DOM操作

render(
  <div id='hoge'>fuga</div>
);

差分描画

Reactでは変更されたVirtual DOMの差分のみを再描画する

JSX

JavaScript内でHTMLっぽく書ける

ReactDOM.render(
  <div className={hoge}>
    <h1>Hello World!</h1>
  </div>
)

JSXの基礎知識と文法

JSXとは?

  • JavaScript内でHTMLを簡単に記述するための言語
  • JavaScriptの拡張言語
  • Facebookが開発
  • React公式ドキュメントはほぼJSXで記述されている
  • Reactでは業界標準

なぜJSXを使う?

通常のJavaScriptでHTMLを記述(DOM操作)

const fuga = "<h1>Hello, World!</h1>"
document.getElementById('hoge').innerHTML = fuga;

量が増えると。。

const fuga = "<h1>Hello, World!</h1>"
const foo = "<h2>React Commentary</h2>"
const bar = "Hi, I'm Billy Gibbons."
document.getElementById('hoge').innerHTML = fuga;
document.getElementById('foo').innerHTML = foo;
document.getElementById('bar').innerHTML = bar;

JSXを使うと。。

return (
  <React.fragment>
    <div id="hoge">
      <h1>Hello, World!</h1>
    </div>
    <div id="foo">
      <h2>React Commentary</h2>
    </div>
    <p id="foo">Hi, I'm Billy Gibbons.</p>
  </React.fragment>
)

可読性が高い!

ただJSXは実際のところJavaScriptではない。
JSXの構文をブラウザは理解できない。

そこでトランスパイラが必要。

トランスパイラ

「翻訳」のような役割。
JSX → JavaScript(ES6) → JavaScript(ES5)

ReactのトランスパイラはBabel

トランスパイラを主たる実装として開発されている言語の例
CoffeeScript、TypeScript...etc

もしJSXがなかったら。。

React.createElementを使う

React.createElement(
  "h1",
  null,
  "Hello, World!"
)

JSXを使用して記述したJSをBabelでトランスパイルするとReact.createElementを使用した形に変換される

JSXの基本文法

1.Reactパッケージのインストールが必要

// .jsxファイル内の先頭に宣言
import React from "react";

2.HTMLとほぼ同じ文法(ただclassclassNameに)

const App = () => {
  return (
    <div id="hoge" className="fuga">
      <h1>Hello, World!</h1>
    </div>
  );
};

3.{}内に変数や関数を埋め込める

const foo = "<h1>Hello, World!</h1>"
const App = () => {
  return (
    <div id="hoge" className="fuga">
      {foo}
    </div>
  );
};

4.変数名などは全てキャメルケースで記述する

const fooBar = "<h1>Hello, World!</h1>"
const App = () => {
  return (
    <div id="hoge" className="fuga">
      {fooBar}
    </div>
  );
};

5.空要素は閉じる

const App = () => {
  return (
    <div id="hoge" className="fuga">
      <input type="text" id="blankElement" />
      <img src="/assets/icon/icon.png" />
    </div>
  );
};

環境構築

create-react-app

必要なもの

  • node 8.10以上
  • npm 5.6以上

上記インストールのためにhomebrew、nodebrewが必要

homebrewのインストール

https://brew.sh/index_ja

nodebrewのインストール

$ brew install nodebrew
$ nodebrew -v // インストールの確認

nodeのインストール

参考:https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

$ nodebrew ls remote // インストール可能なnodeのバージョン確認
$ nodebrew install stable // 安定版のインストール
$ nodebrew ls // 現在インストールされているnodeのバージョン一覧
$ nodebrew use v{インストールしたバージョン} // currentへの追加
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zprofile // zshの場合

npmのインストール

nodeを入れた時点でnpmも入る
バージョンが5.6以上であることを確認する

npm -v

create-react-appによるプロジェクトの作成

$ npx create-react-app react-blog-app

create-react-appとは

React開発環境を超簡単に構築できるツール。
Reactを学習するのに最適な環境
(React公式documentから引用)

  • React開発環境の構築は難しい
  • トランスパイラのbabelやバンドラーのwebpackの設定が必要

create-react-appなら1コマンドで環境を整えてくれる

create-react-appの環境構成

  1. src : コンポーネントを作るJSファイルなど
  2. public : htmlファイルや設定ファイルなど。manifest.jsonはPWAを開発する際に使用する設定ファイル
  3. build : 本番環境用のファイル

基本コマンド

$ npm run build

srcとpublic内のファイルを1つにまとめて(バンドル)、buildディレクトリに出力する

$ npm start

ローカルサーバを起動してReactアプリを動かす

$ npm run eject

babelやwebpackの設定を変更したい時に使用する

その他の環境構築ツール

  • Next.js → サーバーサイドレンダリング(SSR)
  • Gatsby → 静的ウェブサイトに最適(SSG)

コンポーネントの基本

コンポーネントとは?

UIは2つに分類される
1. 見た目(View)
2. 機能(Controller)

コンポーネント = 見た目 + 機能

Webページはコンポーネントのツリー構造になっている

なぜコンポーネントを使うのか

  • 再利用性するため
  • 分割統治するため
  • 変更に強くするため

コンポーネントの種類

  1. Class Component : クラスによって定義されたコンポーネント
  2. Functional Component : 関数型で定義されたコンポーネント

Functional Component

  • ES6のアロー関数で記述
  • stateを持たない(stateless)
  • propsを引数に受け取る
  • JSXをreturnする
  • シンプル
Article.jsx
import React from 'react';

const Article = (props) => {
    return (
        <div>
            <h2>{props.title}</h2>
        </div>
    );
};

export default Article;

Class Component

  • React.Componentを継承
  • ライフサイクルやstateを持つ
  • propsにはthisが必要
  • renderメソッド内でJSXをreturnする
Article.jsx
import React from 'react';

class Article extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
              <h2>{this.props.title}</h2>
            </div>
        );
    }
}

export default Article;

最近の主流はFunctional Component

  • 記述量が少ない
  • コンポーネントにstateを持たせたくない

propsでデータを受け渡す

親コンポーネント

Blog.jsx
import React from 'react';
import Article from "./Article";

const Blog = () => {
    return (
        <React.Fragment>
            <Article title={'Hello, React'}/>
        </React.Fragment>
    );
}

export default Blog;

子コンポーネント

Article.jsx
import React from 'react';

const Article = (props) => {
    return (
        <div>
            <h2>{props.title}</h2>
        </div>
    );
};

export default Article;

受け渡せるデータ型

  • {}内に記述
  • 文字列、数値、真偽値、配列、オブジェクトなどなんでも渡せる
  • 文字列は{}なくてもOK
Blog.jsx
import React from 'react';
import Article from "./Article";

const Blog = () => {
    const authorName = 'Eric Clapton';
    return (
        <React.Fragment>
            <Article
                title={'Hello, React.'}
                order={3}
                isPublished={true}
                author={authorName}
            />
        </React.Fragment>
    );
}

export default Blog;

再利用する

コンポーネントは再利用できることが最大の利点

Blog.jsx
import React from 'react';
import Article from "./Article";

const Blog = () => {
    const authorName = 'Eric Clapton';
    return (
        <React.Fragment>
            <Article title={'出生について'}/>
            <Article title={'Cream時代'}/>
            <Article title={'ソロ活動時代'}/>
        </React.Fragment>
    );
}

export default Blog;

コンポーネントの状態

stateの設定と取得と変更

状態(state)とは

  • コンポーネントの中で管理する変数
  • ローカルステートと呼ばれる
  • propsとして子コンポーネントに渡せる

なぜstateを使うのか

  • render()内では値を変更してはいけない
  • setState()で値を変更する
  • stateの変更 = 再レンダーのきっかけ
    →ページをリロードせずに表示を切り替えられる

stateの設定方法

  • Class Componentが前提
  • constructor()内で宣言
  • オブジェクト型で記述
Blog.jsx
import React from 'react';
import Article from "./Article";

class Blog extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isPublished: false
        }
    }
    // 略
}

export default Blog;

stateの取得

  • 同コンポーネント内ならthis.state.key名で取得できる
  • 子コンポーネントで参照したい場合はpropsとして渡す
Blog.jsx
render() {
  return (
    <React.Fragment>
      <Article
        title={'出生について'}
        isPublished={this.state.isPublished}
      />
    </React.Fragment>
  );
}

stateの変更方法

  • setState()を使う
  • 関数にラップするのが一般的
  • setState()内に記述されたstateのみを変更
Blog.jsx
    // 公開状態を反転させる関数を定義する
    togglePublished = () => {
        this.setState({
           isPublished: !this.state.isPublished
        });
    }

コンポーネントのライフサイクル

ライフサイクルとは

  • コンポーネントの「時間の流れ」
  • 生成、変更、破棄までの循環
  • それぞれの段階で必要な処理を記述

3種類のライフサイクル

Mounting:コンポーネントが生成される期間
Updating:コンポーネントが変更される期間
Unmounting:コンポーネントが破棄される期間

なぜライフサイクルを使うのか

  • 関数外に影響を与える関数を記述するため
    e.g. DOM変更、API通信、ログ出力、setState() ...etc
  • 副作用 = 適切な場所に配置すべき処理
order Mounting Updating Unmounting
1 constructor()
2 render() render()
3 componentDidMount()
4 componentDidUpdate()
5 componentWillUnmount()

主要メソッド

Mount

constructor():初期化(stateなど)
render():VDOMを描画(JSXをreturn)
componentDidMount():render()後に一度だけ呼ばれる。リスナーの設定やAPI通信に使われる。

Updating

render():VDOMを描画(JSXをreturn)
componentDidUpdate():再render()後に呼ばれる。スクロールイベントや条件付きイベント。

Unmounting

componentWillUnmount():コンポーネントが破棄される直前に呼ばれる。リソースを解放するために使う。(リスナーの解除など)

importとexport

モジュール化について

  • 他言語では昔からある概念
  • JavaScriptではES2015(ES6)から採用
  • 基本的に1ファイル=1モジュール
  • 任意の場所で読み込んで使用できる

モジュール化のメリット

  1. 分割統治できる
    大規模プログラムでも管理しやすくなる
  2. 任意の場所で読み込める
    必要なものを必要な分だけ
  3. 再利用できる
    何度も同じコードを書かなくていい

名前付きexport

1モジュールから複数の関数をexport
クラスはexportできない

index.js
export function Foo() {
  return (
    <h1>FOO</h1>
  );
}
export const Bar = () => {
  return (
    <h1>BAR</h1>
  );
}

名前なし(default)export

  • 1ファイル(1モジュール) 1export
  • ES6で推奨されているexport方法
  • アロー関数は宣言後にexport
  • クラスをexportできる
Foo.js
export default function Foo() {
  return (
    <h1>FOO</h1>
  );
}
Bar.js
const Bar = () => {
  return (
    <h1>BAR</h1>
  );
}
export default Bar;
Hoge.js
export default class Hoge extends Fuga {
  render() {
    return (
      <h1>Hoge</h1>
    );
  }
}

モジュール全体のimport

  • 名前なし(default)exportしたモジュールをimportする
  • モジュール全体のimport
Blog.jsx
import React from 'react';
import Article from "./Article";
Article.jsx
const Article = (props) => {
  return (
    <div>Article</div>
  );
}
export default Article;

関数ごとのimport

  • 名前付きexportされたモジュールをimportする
  • {}内にimportしたい関数名
Hoge.js
import { Foo, Bar } from "./FooBar";
FooBar.js
export function Foo() {
  return (
    <h1>FOO</h1>
  );
}
export const Bar = () => {
  return (
    <h1>BAR</h1>
  );
}

別名import

  • 別名(エイリアス)をつけてimportできる
  • モジュール全体なら * as name
  • モジュール一部なら A as B
Blog.jsx
import React from 'react';
import * as AnotherName from './Article';
import { Foo as MyFoo } from './FooBar';
Article.js
const Article = (props) => {
  return (
    <div>Article</div>
  );
}
export default Article;

React Hooks

関数コンポーネントでもstateを扱う

Hookとは

  • クラスの機能(stateやライフサイクル)をFunctional Componentでも使える機能
  • React 16.8から導入された(2020/2に正式リリース)
  • 100%後方互換
    →古い書き方をしているコンポーネントなどに影響を及ぼさない
    →小さく導入できる

なぜHookを使うのか

シンプルさを保つため

  • Class Componentは複雑になりやすい
    • thisという悪魔
    • stateを扱うロジックが複雑
    • 複数のライフサイクルメソッドに副作用のある処理がまたがる

useState()を使う

  • ステートフックと呼ばれる
  • クラスコンポーネントでいうthis.statethis.setState()の代替
  • 複数のstateを扱うときはstate毎に宣言する

1.useState関数をインポート

import React, {useState} from 'react';

2.宣言する

const [isPublished, togglePublished] = useState(false);

3.JSX内で使う

<input // onClick={() => togglePublished(!isPublished)} />

Functional Componentでもライフサイクルを扱う

useEffect()を利用する

useEffect()のメリット

  • ライフサイクルメソッドを代替できる
  • Functional Componentでライフサイクルを扱える
  • コードをまとめることができる
    • ?‍♀️ 機能ベース(何をやっているのか)
    • ?‍♀️ 時の流れベース(ライフサイクルのメソッド毎)

useEffect()の仕組み

  • レンダー毎にuseEffect()内の処理が走る
  • 代替できるメソッド
    • componentDidMount()
    • componentDidUpdate()
    • componentWillUnmount()

useEffect()の使用

パターン① レンダー毎

useEffect(() => {
  console.log('Render!');
  return () => {
    console.log('Unmounting!');
  }
})
  • 基本形
  • useEffect()内にCallback関数を書く
  • Callbackはレンダー毎に呼ばれる
  • returnするCallback関数はアンマウント時に呼ばれる。(クリーンアップ関数)

パターン② マウント時のみ実行

useEffect(() => {
  console.log('Render!');
}, [])
  • 第二引数の配列内の値を前回レンダーと今回レンダーで比較
    • 変更があればCallback関数を実行
  • 第二引数に空の配列を渡すと最初の1回(マウント時)のみ実行される

パターン③ マウント&アンマウント時に実行

useEffect(() => {
  console.log('Render!');
  return () => {
    console.log('Unmounting!');
  }
}, [])
  • ①と②の複合形
  • 通常のCallbackはマウント時のみ
    • 配列が空の場合はUpdating期間は処理が実行されない
  • アンマウント時はretrun内のクリーンアップ関数が実行される

パターン④ 特定のレンダー時に実行

const [limit, release] = useState(true);

useEffect(() => {
  console.log('Render!');
}, [limit])
  • マウント時に実行される
  • limitの値が変わった時に実行される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Hooks学習メモ

React Hooks

関数コンポーネントでもstateを扱う

Hookとは

  • クラスの機能(stateやライフサイクル)をFunctional Componentでも使える機能
  • React 16.8から導入された(2020/2に正式リリース)
  • 100%後方互換
    →古い書き方をしているコンポーネントなどに影響を及ぼさない
    →小さく導入できる

なぜHookを使うのか

シンプルさを保つため

  • Class Componentは複雑になりやすい
    • thisという悪魔
    • stateを扱うロジックが複雑
    • 複数のライフサイクルメソッドに副作用のある処理がまたがる

useState()を使う

  • ステートフックと呼ばれる
  • クラスコンポーネントでいうthis.statethis.setState()の代替
  • 複数のstateを扱うときはstate毎に宣言する

1.useState関数をインポート

import React, {useState} from 'react';

2.宣言する

const [isPublished, togglePublished] = useState(false);

3.JSX内で使う

<input // onClick={() => togglePublished(!isPublished)} />

Functional Componentでもライフサイクルを扱う

useEffect()を利用する

useEffect()のメリット

  • ライフサイクルメソッドを代替できる
  • Functional Componentでライフサイクルを扱える
  • コードをまとめることができる
    • ?‍♀️ 機能ベース(何をやっているのか)
    • ?‍♀️ 時の流れベース(ライフサイクルのメソッド毎)

useEffect()の仕組み

  • レンダー毎にuseEffect()内の処理が走る
  • 代替できるメソッド
    • componentDidMount()
    • componentDidUpdate()
    • componentWillUnmount()

useEffect()の使用

パターン① レンダー毎

useEffect(() => {
  console.log('Render!');
  return () => {
    console.log('Unmounting!');
  }
})
  • 基本形
  • useEffect()内にCallback関数を書く
  • Callbackはレンダー毎に呼ばれる
  • returnするCallback関数はアンマウント時に呼ばれる。(クリーンアップ関数)

パターン② マウント時のみ実行

useEffect(() => {
  console.log('Render!');
}, [])
  • 第二引数の配列内の値を前回レンダーと今回レンダーで比較
    • 変更があればCallback関数を実行
  • 第二引数に空の配列を渡すと最初の1回(マウント時)のみ実行される

パターン③ マウント&アンマウント時に実行

useEffect(() => {
  console.log('Render!');
  return () => {
    console.log('Unmounting!');
  }
}, [])
  • ①と②の複合形
  • 通常のCallbackはマウント時のみ
    • 配列が空の場合はUpdating期間は処理が実行されない
  • アンマウント時はretrun内のクリーンアップ関数が実行される

パターン④ 特定のレンダー時に実行

const [limit, release] = useState(true);

useEffect(() => {
  console.log('Render!');
}, [limit])
  • マウント時に実行される
  • limitの値が変わった時に実行される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React モジュールのimport、exportについて

モジュール化について

  • 他言語では昔からある概念
  • JavaScriptではES2015(ES6)から採用
  • 基本的に1ファイル=1モジュール
  • 任意の場所で読み込んで使用できる

モジュール化のメリット

  1. 分割統治できる
    大規模プログラムでも管理しやすくなる
  2. 任意の場所で読み込める
    必要なものを必要な分だけ
  3. 再利用できる
    何度も同じコードを書かなくていい

名前付きexport

1モジュールから複数の関数をexport
クラスはexportできない

index.js
export function Foo() {
  return (
    <h1>FOO</h1>
  );
}
export const Bar = () => {
  return (
    <h1>BAR</h1>
  );
}

名前なし(default)export

  • 1ファイル(1モジュール) 1export
  • ES6で推奨されているexport方法
  • アロー関数は宣言後にexport
  • クラスをexportできる
Foo.js
export default function Foo() {
  return (
    <h1>FOO</h1>
  );
}
Bar.js
const Bar = () => {
  return (
    <h1>BAR</h1>
  );
}
export default Bar;
Hoge.js
export default class Hoge extends Fuga {
  render() {
    return (
      <h1>Hoge</h1>
    );
  }
}

モジュール全体のimport

  • 名前なし(default)exportしたモジュールをimportする
  • モジュール全体のimport
Blog.jsx
import React from 'react';
import Article from "./Article";
Article.jsx
const Article = (props) => {
  return (
    <div>Article</div>
  );
}
export default Article;

関数ごとのimport

  • 名前付きexportされたモジュールをimportする
  • {}内にimportしたい関数名
Hoge.js
import { Foo, Bar } from "./FooBar";
FooBar.js
export function Foo() {
  return (
    <h1>FOO</h1>
  );
}
export const Bar = () => {
  return (
    <h1>BAR</h1>
  );
}

別名import

  • 別名(エイリアス)をつけてimportできる
  • モジュール全体なら * as name
  • モジュール一部なら A as B
Blog.jsx
import React from 'react';
import * as AnotherName from './Article';
import { Foo as MyFoo } from './FooBar';
Article.js
const Article = (props) => {
  return (
    <div>Article</div>
  );
}
export default Article;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React コンポーネントのライフサイクル

コンポーネントのライフサイクル

ライフサイクルとは

  • コンポーネントの「時間の流れ」
  • 生成、変更、破棄までの循環
  • それぞれの段階で必要な処理を記述

3種類のライフサイクル

Mounting:コンポーネントが生成される期間
Updating:コンポーネントが変更される期間
Unmounting:コンポーネントが破棄される期間

なぜライフサイクルを使うのか

  • 関数外に影響を与える関数を記述するため
    e.g. DOM変更、API通信、ログ出力、setState() ...etc
  • 副作用 = 適切な場所に配置すべき処理
order Mounting Updating Unmounting
1 constructor()
2 render() render()
3 componentDidMount()
4 componentDidUpdate()
5 componentWillUnmount()

主要メソッド

Mount

constructor():初期化(stateなど)
render():VDOMを描画(JSXをreturn)
componentDidMount():render()後に一度だけ呼ばれる。リスナーの設定やAPI通信に使われる。

Updating

render():VDOMを描画(JSXをreturn)
componentDidUpdate():再render()後に呼ばれる。スクロールイベントや条件付きイベント。

Unmounting

componentWillUnmount():コンポーネントが破棄される直前に呼ばれる。リソースを解放するために使う。(リスナーの解除など)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactコンポーネントの基本

コンポーネントの基本

コンポーネントとは?

UIは2つに分類される
1. 見た目(View)
2. 機能(Controller)

コンポーネント = 見た目 + 機能

Webページはコンポーネントのツリー構造になっている

なぜコンポーネントを使うのか

  • 再利用性するため
  • 分割統治するため
  • 変更に強くするため

コンポーネントの種類

  1. Class Component : クラスによって定義されたコンポーネント
  2. Functional Component : 関数型で定義されたコンポーネント

Functional Component

  • ES6のアロー関数で記述
  • stateを持たない(stateless)
  • propsを引数に受け取る
  • JSXをreturnする
  • シンプル
Article.jsx
import React from 'react';

const Article = (props) => {
    return (
        <div>
            <h2>{props.title}</h2>
        </div>
    );
};

export default Article;

Class Component

  • React.Componentを継承
  • ライフサイクルやstateを持つ
  • propsにはthisが必要
  • renderメソッド内でJSXをreturnする
Article.jsx
import React from 'react';

class Article extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
              <h2>{this.props.title}</h2>
            </div>
        );
    }
}

export default Article;

最近の主流はFunctional Component

  • 記述量が少ない
  • コンポーネントにstateを持たせたくない

propsでデータを受け渡す

親コンポーネント

Blog.jsx
import React from 'react';
import Article from "./Article";

const Blog = () => {
    return (
        <React.Fragment>
            <Article title={'Hello, React'}/>
        </React.Fragment>
    );
}

export default Blog;

子コンポーネント

Article.jsx
import React from 'react';

const Article = (props) => {
    return (
        <div>
            <h2>{props.title}</h2>
        </div>
    );
};

export default Article;

受け渡せるデータ型

  • {}内に記述
  • 文字列、数値、真偽値、配列、オブジェクトなどなんでも渡せる
  • 文字列は{}なくてもOK
Blog.jsx
import React from 'react';
import Article from "./Article";

const Blog = () => {
    const authorName = 'Eric Clapton';
    return (
        <React.Fragment>
            <Article
                title={'Hello, React.'}
                order={3}
                isPublished={true}
                author={authorName}
            />
        </React.Fragment>
    );
}

export default Blog;

再利用する

コンポーネントは再利用できることが最大の利点

Blog.jsx
import React from 'react';
import Article from "./Article";

const Blog = () => {
    const authorName = 'Eric Clapton';
    return (
        <React.Fragment>
            <Article title={'出生について'}/>
            <Article title={'Cream時代'}/>
            <Article title={'ソロ活動時代'}/>
        </React.Fragment>
    );
}

export default Blog;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React環境構築

環境構築

create-react-app

必要なもの

  • node 8.10以上
  • npm 5.6以上

上記インストールのためにhomebrew、nodebrewが必要

homebrewのインストール

https://brew.sh/index_ja

nodebrewのインストール

$ brew install nodebrew
$ nodebrew -v // インストールの確認

nodeのインストール

参考:https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

$ nodebrew ls remote // インストール可能なnodeのバージョン確認
$ nodebrew install stable // 安定版のインストール
$ nodebrew ls // 現在インストールされているnodeのバージョン一覧
$ nodebrew use v{インストールしたバージョン} // currentへの追加
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zprofile // zshの場合

npmのインストール

nodeを入れた時点でnpmも入る
バージョンが5.6以上であることを確認する

npm -v

create-react-appによるプロジェクトの作成

$ npx create-react-app react-blog-app

create-react-appとは

React開発環境を超簡単に構築できるツール。
Reactを学習するのに最適な環境
(React公式documentから引用)

  • React開発環境の構築は難しい
  • トランスパイラのbabelやバンドラーのwebpackの設定が必要

create-react-appなら1コマンドで環境を整えてくれる

create-react-appの環境構成

  1. src : コンポーネントを作るJSファイルなど
  2. public : htmlファイルや設定ファイルなど。manifest.jsonはPWAを開発する際に使用する設定ファイル
  3. build : 本番環境用のファイル

基本コマンド

$ npm run build

srcとpublic内のファイルを1つにまとめて(バンドル)、buildディレクトリに出力する

$ npm start

ローカルサーバを起動してReactアプリを動かす

$ npm run eject

babelやwebpackの設定を変更したい時に使用する

その他の環境構築ツール

  • Next.js → サーバーサイドレンダリング(SSR)
  • Gatsby → 静的ウェブサイトに最適(SSG)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSXの基礎知識と文法

JSXの基礎知識と文法

JSXとは?

  • JavaScript内でHTMLを簡単に記述するための言語
  • JavaScriptの拡張言語
  • Facebookが開発
  • React公式ドキュメントはほぼJSXで記述されている
  • Reactでは業界標準

なぜJSXを使う?

通常のJavaScriptでHTMLを記述(DOM操作)

const fuga = "<h1>Hello, World!</h1>"
document.getElementById('hoge').innerHTML = fuga;

量が増えると。。

const fuga = "<h1>Hello, World!</h1>"
const foo = "<h2>React Commentary</h2>"
const bar = "Hi, I'm Billy Gibbons."
document.getElementById('hoge').innerHTML = fuga;
document.getElementById('foo').innerHTML = foo;
document.getElementById('bar').innerHTML = bar;

JSXを使うと。。

return (
  <React.fragment>
    <div id="hoge">
      <h1>Hello, World!</h1>
    </div>
    <div id="foo">
      <h2>React Commentary</h2>
    </div>
    <p id="foo">Hi, I'm Billy Gibbons.</p>
  </React.fragment>
)

可読性が高い!

ただJSXは実際のところJavaScriptではない。
JSXの構文をブラウザは理解できない。

そこでトランスパイラが必要。

トランスパイラ

「翻訳」のような役割。
JSX → JavaScript(ES6) → JavaScript(ES5)

ReactのトランスパイラはBabel

トランスパイラを主たる実装として開発されている言語の例
CoffeeScript、TypeScript...etc

もしJSXがなかったら。。

React.createElementを使う

React.createElement(
  "h1",
  null,
  "Hello, World!"
)

JSXを使用して記述したJSをBabelでトランスパイルするとReact.createElementを使用した形に変換される

JSXの基本文法

1.Reactパッケージのインストールが必要

// .jsxファイル内の先頭に宣言
import React from "react";

2.HTMLとほぼ同じ文法(ただclassclassNameに)

const App = () => {
  return (
    <div id="hoge" className="fuga">
      <h1>Hello, World!</h1>
    </div>
  );
};

3.{}内に変数や関数を埋め込める

const foo = "<h1>Hello, World!</h1>"
const App = () => {
  return (
    <div id="hoge" className="fuga">
      {foo}
    </div>
  );
};

4.変数名などは全てキャメルケースで記述する

const fooBar = "<h1>Hello, World!</h1>"
const App = () => {
  return (
    <div id="hoge" className="fuga">
      {fooBar}
    </div>
  );
};

5.空要素は閉じる

const App = () => {
  return (
    <div id="hoge" className="fuga">
      <input type="text" id="blankElement" />
      <img src="/assets/icon/icon.png" />
    </div>
  );
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの基礎知識

Reactの基礎知識

Reactとは?

  • Facebookが開発
  • JavaScriptのライブラリ(フレームワークではない)
  • WebのUIを作る
  • React ≠ SPA

コンポーネントとは?

UIは2つに分類される
1. 見た目(View)
2. 機能(Controller)

コンポーネント = 見た目 + 機能

Webページはコンポーネントのツリー構造になっている

なぜコンポーネントを使うのか

  • 再利用性するため
  • 分割統治するため
  • 変更に強くするため

Virtual DOM

そもそもDOMとは?

→ Document Object Modelの略
→ インターフェース
→ HTMLにアクセスする窓口
→ HTML構造、見た目、コンテンツを変更したいときはDOMを通して操作を行う

Virtual DOMとは?

Reactで管理するDOM。
通常のDOMはブラウザのレンダリングによって管理されるが
Reactではブラウザのレンダリングと別で管理を行う
→効率よくDOM操作できる

通常のDOM操作

document.getElementById('hoge').innerText='fuga';

ReactのVirtual DOM操作

render(
  <div id='hoge'>fuga</div>
);

差分描画

Reactでは変更されたVirtual DOMの差分のみを再描画する

JSX

JavaScript内でHTMLっぽく書ける

ReactDOM.render(
  <div className={hoge}>
    <h1>Hello World!</h1>
  </div>
)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでvideoタグにmutedを効かせる方法(の代替)

Reactでvideoタグにmutedを添えられない問題について、
良い解決案を見かけたのでその紹介をします。

結論、mutedの指定は可能?

答えはNoです。
videoタグにmutedを添えることは、現状のreactでは実現できません。(2020/10/09現在)
(※保証されない、と表現されていますが、実際、mutedはvideoタグに反映されません。)

どうやって解決するの?

本家の該当issueにて、こんな書き込みがありました。

Mute is just equal to volume being zero
(ミュートは、ボリュームゼロと同じ。)

え、たしかに。
なぜmutedにこだわる自分が居たのかが謎になるくらい目からウロコな意見で、
実際に volume = 0 を試したらmuted相当の動きになりました :thumbsup:

ちなみに、自分は使うあてが無かったですが、onVolumeChange なども扱えるようです。

サンプルコード

↓こちら試してないですが、概念として。

const SomeVideoComponent = ({ muted }) => {
  const videoRef = useRef(null);

  ...
  useEffect(() => {
    if (videoRef.current !== null) {
      const mutedCurrently = videoRef.current.volume === 0;
      if (mutedCurrently !== muted) {
        videoRef.current.volume = muted ? 0 : 1;
      }
    }
  }, [videoRef, muted]);

  return <video ref={videoRef} />;
}

そもそもmutedを使えない背景/調査補足

理由は、こちらの本家facebook/reactのissueにて色々議論されているので、ここでは割愛します。

そのため、Qiita: React videoでmutedが消えるバグのパワー系解決 で紹介されているように、
dangerouslySetInnerHTMLなど駆使してどうにか解決しようとするライブラリもあるようです。
ただ、こういったライブラリも、スタイリングしたい場合に鬱陶しかったり、
そもそもこの些細なことのためにライブラリいる?っていう話もあると思います。

この記事もReactでMediaStream扱っている方にとって、何かの足しになればと思います。
単純な話なのですが、ちょっと呆気にとられたのでシェアでした。

それでは良き開発を〜

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む