- 投稿日:2020-10-10T23:37:05+09:00
LaravelでExample.jsを表示させてみる
Reactを使う準備はできた
じゃあExample.jsの描画はどうやるんだ?ということで私的まとめ環境
- laravel 7.28.4
準備
npm run devこれを実行する理由は以下
ブラウザで確認するためにnpm run watchコマンドを実行しておきます。npm run watchコマンドを実行しておくとファイルを更新するその更新を検知し自動でコンパイルを実行してくれます。
参考:初めてのLaravel6.xとReact入門ファイルが変更されるたびにビルドをしてくれるってことですな
実行しておきましょう表示
表示させるコンポーネントはこちら
公式のままExample.jsimport 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.phpRoute::get('/sample', function () { return view('sample'); });これで
locahost:8000/sample
にアクセスすれば表示がされる疑問
- 無意識に
locahost:8000/sample
にアクセスしたから表示されたもののポートの管理はどこでやっているのか- php artisan serveを実行していないのにlocalhost:8000につないだらLaravelのwelcomeページが表示されることからちゃんとルーティングがされているのはなぜか
参考
- 投稿日:2020-10-10T23:12:17+09:00
Reactコンポーネントの雛形生成を自動化するスクリプトファイルを作る
エンジニアにとって無駄な作業はストレス。
繰り返しの作業を自動化したい。という事でコンポーネントファイルを自動生成するスクリプトを書いた。
スクリプトファイルの使い方
まずは使い方から。
ルートディレクトリでbash ./etc/scripts/make-component-template.sh components Layoutというように、ディレクトリ名とコンポーネント名を指定するだけ。
ルートディレクトリ直下にcomponents
ディレクトリがある事を想定しています。
自動生成するファイル
./etc/scripts/make-component-template.sh components Layout
を実行すると以下のファイルを生成します。components └── Layout ├── Layout.jsx ├── index.jsx └── style.cssファイルの中身
index.jsxexport { default } from './Layout'Layout.jsximport 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 Containerstyle.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.txtimport 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!?
- 投稿日:2020-10-10T21:24:31+09:00
Reactの開発環境をDockerで作る
動機
React/Reduxの開発環境をDockerで作成した際の備忘録。
環境
macOS Catalina 10.15.5
Docker version 19.03.13
docker-compose version 1.27.4Dockerfile
From node:latest WORKDIR /usr/src/app RUN npm install -g create-react-appdocker-compose
docker-compose.ymlversion: '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
開発環境:developmentcommand
イメージの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 logindocker hubにpush
docker push <DockerhubID>/<image名>:タグ名これでDockerhubのレポジトリに/が追加される。
自動ビルド設定
GitHubと連携する手順を示す。事前にGitHubにレポジトリを用意する必要がある。
Dockerhubから対象のrepositoryを選択 → 「Builds」 → 「Link to GitHub」
Repositoryを選択すると下の選択画面が開く。AUTESTをOffにすると自動ビルドされないので、それ以外を選択。REPOSITORY LINKSはお好きに。BUILD RULESは自動ビルドの詳細設定。デフォルトでよければこれで。
作成当初はSave and Buildがいいっぽい。
これでGitHubにpushされると、DockerHubは自動ビルドになる。
- 投稿日:2020-10-10T21:24:31+09:00
Reactの開発環境をDockerで作る。イメージの作成→コンテナ起動→DockerHubにpush
動機
React/Reduxの開発環境をDockerで作成した際の備忘録。
環境
macOS Catalina 10.15.5
Docker version 19.03.13
docker-compose version 1.27.4Dockerfile
From node:latest WORKDIR /usr/src/app RUN npm install -g create-react-appdocker-compose
docker-compose.ymlversion: '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
開発環境:developmentcommand
イメージの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 logindocker hubにpush
docker push <DockerhubID>/<image名>:タグ名これでDockerhubのレポジトリに/が追加される。
自動ビルド設定
GitHubと連携する手順を示す。事前にGitHubにレポジトリを用意する必要がある。
Dockerhubから対象のrepositoryを選択 → 「Builds」 → 「Link to GitHub」
Repositoryを選択すると下の選択画面が開く。AUTESTをOffにすると自動ビルドされないので、それ以外を選択。REPOSITORY LINKSはお好きに。BUILD RULESは自動ビルドの詳細設定。デフォルトでよければこれで。
作成当初はSave and Buildがいいっぽい。
これでGitHubにpushされると、DockerHubは自動ビルドになる。
- 投稿日:2020-10-10T19:47:58+09:00
ReactHookFormの導入と簡単な使い方
はじめに
今回はReactHookFormの導入と簡単な解説をしていこうと思います。
Reactの環境構築はできているものとします。一応、JavasciptとTypescriptの両方のコードを載せているので自分が使っている方を見てください。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.jsximport 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.jsximport 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.tsximport 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で入力の有無の状態を取得する方法をまとめたいと思います。
- 投稿日:2020-10-10T11:51:33+09:00
初心者がReact & Firebaseを使って収支管理アプリを作成(解説編)
今回作ったもの
ログイン機能付きの収支管理アプリです。
いわゆる家計簿アプリ的な物です。
毎月の収入と支出をリスト化し月の残高を表示します。感想編については、別途記事を書いてるので、ご興味ございましたら是非ご覧下さいませ!
初心者が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 startFirebaseの準備
プロジェクトを作成
作成方法は手順に従えば大丈夫です。
簡単なので割愛しますが、全体の流れはこちらのYoutubeを参考にしました。
日本一わかりやすいReact入門【実践編】#3...Firebaseプロジェクトの作成と初めてのデプロイ注意点として、node.jsとfirebaseのバージョンにより少し挙動が動画内容と異なります。
ポイントは、
- ロケーション設定は初めにやる。東京なら「asia-northeast1」
- プランは現在の安定node versionだとSparkではなくBlazeプラン(従量制)になります
- Blazeプランなので、念の為Google Cloud Platformを作成して、アクセス& 料金状況/アラーム通知を受信できるよう設定します(任意)
Authenticationの設定
Sign-in methodよりステータスを有効にする
今回は「メール/パスワード」を使用
Cloud Firestoreでデータベース作成
- テストモードで実行
- とりあえずコレクション/ドキュメントを追加してみる(イメージのため)
最終的なコレクション/ドキュメント構成はこちらです。
Firebaseの設定
Firebaseの情報をwebアプリに登録します。
プロジェクト内容は一応セキュリティを考慮し.env
に登録します。
.env
をgitignore
すれば公開される心配がないということですね。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の初期化処理が行われます。
auth
とdb
も作り、毎回全部書かなくて済むようにします。src/firebase/Firebase.jsimport 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.jsconst 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.jsimport 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
関数
引数にpassword
、history
を渡して非同期処理を行います。
auth.createUserWithEmailAndPassword(email, password)
は、
firebaseのメソッドでpassword
を元にアカウントが作成されます。
その後user
の情報を取得し、CurrentUser
にセットします。
history.push("/")
は、reactRouterの画面遷移させる機能です。
今回はログイン後、Home画面に行きます。
login
関数
同じ様に、ユーザーがログインしたら情報を取得しCurrentUser
を更新するようにします。
auth.signInWithEmailAndPassword(email,password)
これもまたfirebaseのメソッドです。あとは、最初にログインしてるか確認する為、
useEffect
で一回だけauth.onAuthStateChanged
を実行します。
※一回だけ実行したいので、第二引数には空の配列[]
を渡します。PrivateRoute.js
アプリのメイン画面
Home.js
は、PrivateRoute
に指定します。
ここで、ユーザーがログインしてれば→メイン画面を表示。
未ログインの場合→ログイン画面を表示。の作業を行なってます。src/auth/PraveteRoute.jsimport 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
が実行される時、入力されたpassword
の内容をAuthProvider
で作ったsignup
関数の引数に渡してデータが登録されます。
アップデート後のhistory(情報)を渡すために、最後exportの時withRouter(SignUp)
を使っています。src/auth/SignUp.jsconst 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.jsconst 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ステートの作成/更新
親コンポーネントから子コンポーネントに渡す為、ステートは全て
Home.js
で作成します。作成したステート達
src/compoments/Home.jsconst [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.jsconst 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
を用意。引数にはユーザーが入力したtext
とamount
を渡す。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.jsconst 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
を作成- その配列にドキュメントの
data
とid
をpush
(追加)する- reactアプリ側の
incomeItems
の配列を更新するポイントは、
orderBy
のメソッドでdate
を昇順にしてます。
リストがバラバラに表示されてしまうので、制御する為に必要です。
また、orderBy
で昇順にしようとするとfirebaseから「indexを作れ」というエラーが出ます。
その際、親切にURLが表示されるのでそこにアクセスしてindexを作ればOKです(結構時間かかります)
参考記事:複合index
startAt
とendAt
は、その月の分だけ表示させる為です。
この引数の中身については、後ほど詳細を書きます。これと同じ内容で出費expense用の関数も用意すれば、無事Firestoreからデータを取得できて、Reactのステートに更新/表示がされます。
尚、データを取得するタイミングは、
useEffect
を使って操作します。
- 最初の1回のみ実行してほしい。引数は空の配列。
date
が更新されるたびに実行してほしい。※date
は次で解説してますがヘッダーの月の部分です。src/compoments/Home.jsuseEffect (() => { getIncomeData(); getExpenseData(); }, []); useEffect(() => { getIncomeData(); getExpenseData(); }, [date]);月ごとにデータを表示させる
月ごとにデータを分けて表示させる為、ステートで作った
date
を使います。
初期値は現在の日時が入ってます。src/compoments/Home.jsconst [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
では、現在の月を表示させる為、year
とmonth
を作り、
隣に前月と次月ボタンを表示させます。src/compoments/Header.jsconst 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.jsexport 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
の引数に ユーザーの入力内容inputText
とinputAmount
を渡すexp
タイプも同様→Firestoreのデータが追加され、reactアプリのステートも更新されるという流れ。
ヘッダーが今月なのか、今月でない月かによって表示方法を変える為、条件付きレンダーを行ってます。
(※今月のみ追加フォームを表示させる為)この条件に使ってる
selectedMonth
とthisMonth
は、
他のコンポーネント(リスト)でも使うので、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
を行い、IncomeItem
とExpenseItem
をそれぞれ表示します。src/components/ItemsList.jsexport 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.jsexport 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.jsconst deleteIncome = (docId) => { db.collection('incomeItems').doc(docId).delete() }firebaseのメソットを使い、
incomeItems
コレクションにある、該当ドキュメントを削除してます。あとは、スタイル上、表示の仕方を変えたいので、ここでも条件付きレンダーを行ってます。
→showThisMonth
とshowPastMonth
で分ける。
これで今月以外、削除ボタンを表示させないようにしてます。これと同じようにexpenseバージョンも作ればOK
収入/支出の値を計算する
収入と支出の各合計
値の計算はそれぞれの
incomeItems
とexpenseItems
の配列から、amount
を取り出して計算します。src/components/IncomeExpense.jsexport 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> ) }
map
とreduce
については、こちらを元に別で記事を書いています。
※filter
は途中色々変えたので、結局今回のアプリに使っていません。支出expenseの合計計算
-expenseAmount
にexpenseItems
の中のamount
だけ取り出した配列を代入します。
-expenseAmount
を使って、累計を計算し、expenseTotal
とします。これを収入
incomeItems
でも同じことをします。
ただincomeバージョンについては、他のコンポーネントでも使うので、共通関数ファイルTotalIncome.js
を作成してます。
TotalIncome.js
で関数totalCalc
を作り、Home.js
でincomeItems
を引数に渡してます。src/components/Home.js// calculate % and show total const incomeTotal = totalCalc(incomeItems);src/components/TotalIncome.jsexport const totalCalc = (incomeItems) => { const incomeAmounts = incomeItems.map(incomeItem => incomeItem.amount); return incomeAmounts.reduce((acc, cur) => acc += cur, 0); };こちらを使う他のコンポーネントとは、
割合%を表示するIncomeExpense.js
、ExpenseItem
と、計算に必要なBalance.js
になります。残高の計算
総合計の残高を計算をする
Balance.js
コンポーネントでは、
IncomeTotal - ExpenseTotal
をすれば計算できます。src/components/Balance.jsconst balance = incomeTotal - expenseTotalアプリを公開
あとは公開するだけです!
私の場合はfirebase login
とinit
は先に済ませてました。> firebase login > firebase init - 選択項目は FireStore, Functions, Hosting - publicディレクトリはbuildにする > npm run build > firebase deployあとがき
今回のアプリは全てデータをpropsで親から子供に渡しているので、
どうしてもメインのHome.js
が少しボリューミーになってしまいました。(そういうものなのか?)
初心者が書いたコードですので、ご理解いただければと思います。
こうした方が良い等ありましたら、是非ご指摘お願いします!
日々勉強して、もっと良い書き方でコードを書けるよう頑張ります。参考
- 投稿日:2020-10-10T10:37:04+09:00
ユーザーに画像をトリミングしてもらう(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 --savereact-bootstrapのインストール
画像をアップロードしてもらった後に、bootstrapのModalを簡単に利用するために、今回インストールしました。
公式 を参照し、react-bootstrapをインストールしました。bootstrap自体は既にインストール済みだったので今回は省略しました。npm install react-bootstrap --save使い方
使い方は、GitHubを参考にしています。
公式ではHooks使っていますが、勉強不足のため今回はクラスを使用しました。render
ReactCrop
コンポーネントをModalの中に入れました。ReactCrop
には様々な値を渡します。今回利用したのは以下の4つです。詳細はGitHubや会津ラボのサイトが参考になると思います。
- src (画像のソース)
- crop(トリミング時の幅などのプロパティ)
- keepSelection(領域外をクリックしたときに選択を解除するかどうか)
- onChange(トリミング幅や位置が変わったときに発火するイベント)
app.jsimport 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.jsexport 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.jsfunction 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.jsexport 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.jsexport 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.jsfunction 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.jsexport 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にアップロードするところでつまずいています。。。)
- 投稿日:2020-10-10T06:47:04+09:00
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とほぼ同じ文法(ただ
class
はclassName
に)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のインストール
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 -vcreate-react-appによるプロジェクトの作成
$ npx create-react-app react-blog-appcreate-react-appとは
React開発環境を超簡単に構築できるツール。
Reactを学習するのに最適な環境
(React公式documentから引用)
- React開発環境の構築は難しい
- トランスパイラのbabelやバンドラーのwebpackの設定が必要
create-react-appなら1コマンドで環境を整えてくれる
create-react-appの環境構成
src
: コンポーネントを作るJSファイルなどpublic
: htmlファイルや設定ファイルなど。manifest.json
はPWAを開発する際に使用する設定ファイルbuild
: 本番環境用のファイル基本コマンド
$ npm run buildsrcとpublic内のファイルを1つにまとめて(バンドル)、buildディレクトリに出力する
$ npm startローカルサーバを起動してReactアプリを動かす
$ npm run ejectbabelやwebpackの設定を変更したい時に使用する
その他の環境構築ツール
- Next.js → サーバーサイドレンダリング(SSR)
- Gatsby → 静的ウェブサイトに最適(SSG)
コンポーネントの基本
コンポーネントとは?
UIは2つに分類される
1. 見た目(View)
2. 機能(Controller)コンポーネント = 見た目 + 機能
Webページはコンポーネントのツリー構造になっている
なぜコンポーネントを使うのか
- 再利用性するため
- 分割統治するため
- 変更に強くするため
コンポーネントの種類
Class Component
: クラスによって定義されたコンポーネントFunctional Component
: 関数型で定義されたコンポーネントFunctional Component
- ES6のアロー関数で記述
- stateを持たない(stateless)
- propsを引数に受け取る
- JSXをreturnする
- シンプル
Article.jsximport 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.jsximport 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.jsximport React from 'react'; import Article from "./Article"; const Blog = () => { return ( <React.Fragment> <Article title={'Hello, React'}/> </React.Fragment> ); } export default Blog;子コンポーネント
Article.jsximport React from 'react'; const Article = (props) => { return ( <div> <h2>{props.title}</h2> </div> ); }; export default Article;受け渡せるデータ型
{}
内に記述- 文字列、数値、真偽値、配列、オブジェクトなどなんでも渡せる
- 文字列は
{}
なくてもOKBlog.jsximport 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.jsximport 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.jsximport 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.jsxrender() { 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モジュール
- 任意の場所で読み込んで使用できる
モジュール化のメリット
- 分割統治できる
大規模プログラムでも管理しやすくなる- 任意の場所で読み込める
必要なものを必要な分だけ- 再利用できる
何度も同じコードを書かなくていい名前付きexport
1モジュールから複数の関数をexport
クラスはexportできないindex.jsexport function Foo() { return ( <h1>FOO</h1> ); } export const Bar = () => { return ( <h1>BAR</h1> ); }名前なし(default)export
- 1ファイル(1モジュール) 1export
- ES6で推奨されているexport方法
- アロー関数は宣言後にexport
- クラスをexportできる
Foo.jsexport default function Foo() { return ( <h1>FOO</h1> ); }Bar.jsconst Bar = () => { return ( <h1>BAR</h1> ); } export default Bar;Hoge.jsexport default class Hoge extends Fuga { render() { return ( <h1>Hoge</h1> ); } }モジュール全体のimport
- 名前なし(default)exportしたモジュールをimportする
- モジュール全体のimport
Blog.jsximport React from 'react'; import Article from "./Article";Article.jsxconst Article = (props) => { return ( <div>Article</div> ); } export default Article;関数ごとのimport
- 名前付きexportされたモジュールをimportする
- {}内にimportしたい関数名
Hoge.jsimport { Foo, Bar } from "./FooBar";FooBar.jsexport function Foo() { return ( <h1>FOO</h1> ); } export const Bar = () => { return ( <h1>BAR</h1> ); }別名import
- 別名(エイリアス)をつけてimportできる
- モジュール全体なら
* as name
- モジュール一部なら
A as B
Blog.jsximport React from 'react'; import * as AnotherName from './Article'; import { Foo as MyFoo } from './FooBar';Article.jsconst 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.state
とthis.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の値が変わった時に実行される
- 投稿日:2020-10-10T06:44:00+09:00
React Hooks学習メモ
React Hooks
関数コンポーネントでもstateを扱う
Hookとは
- クラスの機能(stateやライフサイクル)を
Functional Component
でも使える機能- React 16.8から導入された(2020/2に正式リリース)
- 100%後方互換
→古い書き方をしているコンポーネントなどに影響を及ぼさない
→小さく導入できるなぜHookを使うのか
シンプルさを保つため
Class Component
は複雑になりやすい
this
という悪魔- stateを扱うロジックが複雑
- 複数のライフサイクルメソッドに副作用のある処理がまたがる
useState()
を使う
- ステートフックと呼ばれる
- クラスコンポーネントでいう
this.state
とthis.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の値が変わった時に実行される
- 投稿日:2020-10-10T06:42:50+09:00
React モジュールのimport、exportについて
モジュール化について
- 他言語では昔からある概念
- JavaScriptではES2015(ES6)から採用
- 基本的に1ファイル=1モジュール
- 任意の場所で読み込んで使用できる
モジュール化のメリット
- 分割統治できる
大規模プログラムでも管理しやすくなる- 任意の場所で読み込める
必要なものを必要な分だけ- 再利用できる
何度も同じコードを書かなくていい名前付きexport
1モジュールから複数の関数をexport
クラスはexportできないindex.jsexport function Foo() { return ( <h1>FOO</h1> ); } export const Bar = () => { return ( <h1>BAR</h1> ); }名前なし(default)export
- 1ファイル(1モジュール) 1export
- ES6で推奨されているexport方法
- アロー関数は宣言後にexport
- クラスをexportできる
Foo.jsexport default function Foo() { return ( <h1>FOO</h1> ); }Bar.jsconst Bar = () => { return ( <h1>BAR</h1> ); } export default Bar;Hoge.jsexport default class Hoge extends Fuga { render() { return ( <h1>Hoge</h1> ); } }モジュール全体のimport
- 名前なし(default)exportしたモジュールをimportする
- モジュール全体のimport
Blog.jsximport React from 'react'; import Article from "./Article";Article.jsxconst Article = (props) => { return ( <div>Article</div> ); } export default Article;関数ごとのimport
- 名前付きexportされたモジュールをimportする
- {}内にimportしたい関数名
Hoge.jsimport { Foo, Bar } from "./FooBar";FooBar.jsexport function Foo() { return ( <h1>FOO</h1> ); } export const Bar = () => { return ( <h1>BAR</h1> ); }別名import
- 別名(エイリアス)をつけてimportできる
- モジュール全体なら
* as name
- モジュール一部なら
A as B
Blog.jsximport React from 'react'; import * as AnotherName from './Article'; import { Foo as MyFoo } from './FooBar';Article.jsconst Article = (props) => { return ( <div>Article</div> ); } export default Article;
- 投稿日:2020-10-10T06:41:19+09:00
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()
:コンポーネントが破棄される直前に呼ばれる。リソースを解放するために使う。(リスナーの解除など)
- 投稿日:2020-10-10T06:40:31+09:00
Reactコンポーネントの基本
コンポーネントの基本
コンポーネントとは?
UIは2つに分類される
1. 見た目(View)
2. 機能(Controller)コンポーネント = 見た目 + 機能
Webページはコンポーネントのツリー構造になっている
なぜコンポーネントを使うのか
- 再利用性するため
- 分割統治するため
- 変更に強くするため
コンポーネントの種類
Class Component
: クラスによって定義されたコンポーネントFunctional Component
: 関数型で定義されたコンポーネントFunctional Component
- ES6のアロー関数で記述
- stateを持たない(stateless)
- propsを引数に受け取る
- JSXをreturnする
- シンプル
Article.jsximport 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.jsximport 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.jsximport React from 'react'; import Article from "./Article"; const Blog = () => { return ( <React.Fragment> <Article title={'Hello, React'}/> </React.Fragment> ); } export default Blog;子コンポーネント
Article.jsximport React from 'react'; const Article = (props) => { return ( <div> <h2>{props.title}</h2> </div> ); }; export default Article;受け渡せるデータ型
{}
内に記述- 文字列、数値、真偽値、配列、オブジェクトなどなんでも渡せる
- 文字列は
{}
なくてもOKBlog.jsximport 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.jsximport 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;
- 投稿日:2020-10-10T06:38:07+09:00
React環境構築
環境構築
create-react-app
必要なもの
- node 8.10以上
- npm 5.6以上
上記インストールのためにhomebrew、nodebrewが必要
homebrewのインストール
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 -vcreate-react-appによるプロジェクトの作成
$ npx create-react-app react-blog-appcreate-react-appとは
React開発環境を超簡単に構築できるツール。
Reactを学習するのに最適な環境
(React公式documentから引用)
- React開発環境の構築は難しい
- トランスパイラのbabelやバンドラーのwebpackの設定が必要
create-react-appなら1コマンドで環境を整えてくれる
create-react-appの環境構成
src
: コンポーネントを作るJSファイルなどpublic
: htmlファイルや設定ファイルなど。manifest.json
はPWAを開発する際に使用する設定ファイルbuild
: 本番環境用のファイル基本コマンド
$ npm run buildsrcとpublic内のファイルを1つにまとめて(バンドル)、buildディレクトリに出力する
$ npm startローカルサーバを起動してReactアプリを動かす
$ npm run ejectbabelやwebpackの設定を変更したい時に使用する
その他の環境構築ツール
- Next.js → サーバーサイドレンダリング(SSR)
- Gatsby → 静的ウェブサイトに最適(SSG)
- 投稿日:2020-10-10T06:37:07+09:00
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とほぼ同じ文法(ただ
class
はclassName
に)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> ); };
- 投稿日:2020-10-10T06:35:52+09:00
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> )
- 投稿日:2020-10-10T01:26:07+09:00
Reactでvideoタグにmutedを効かせる方法(の代替)
Reactでvideoタグにmutedを添えられない問題について、
良い解決案を見かけたのでその紹介をします。結論、mutedの指定は可能?
答えはNoです。
videoタグにmutedを添えることは、現状のreactでは実現できません。(2020/10/09現在)
(※保証されない、と表現されていますが、実際、mutedはvideoタグに反映されません。)どうやって解決するの?
Mute is just equal to volume being zero
(ミュートは、ボリュームゼロと同じ。)え、たしかに。
なぜmutedにこだわる自分が居たのかが謎になるくらい目からウロコな意見で、
実際にvolume = 0
を試したらmuted相当の動きになりましたちなみに、自分は使うあてが無かったですが、
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扱っている方にとって、何かの足しになればと思います。
単純な話なのですが、ちょっと呆気にとられたのでシェアでした。それでは良き開発を〜