- 投稿日:2020-06-30T23:22:27+09:00
React.jsをDockerで動かした時のメモ
環境
$ cat /etc/linuxmint/info RELEASE=19.3 CODENAME=tricia EDITION="Cinnamon" DESCRIPTION="Linux Mint 19.3 Tricia" DESKTOP=Gnome TOOLKIT=GTK NEW_FEATURES_URL=https://www.linuxmint.com/rel_tricia_cinnamon_whatsnew.php RELEASE_NOTES_URL=https://www.linuxmint.com/rel_tricia_cinnamon.php USER_GUIDE_URL=https://www.linuxmint.com/documentation.php GRUB_TITLE=Linux Mint 19.3 CinnamonDockerのインストール
$ apt update $ apt install docker-ce docker-ce-cli docker-composenode.jsとcreate-react-appのインストール
$ apt install nodejs $ npm install create-react-app yarnサンプルReact.jsアプリを作る
$ create-react-app docker-test $ cd docker-testとりあえず普通に起動してみる。
yarn startブラウザで http://localhost:3000 にアクセスしReact.jsが動作していることを確認。
確認できたらCtrl-Cで落とす。Dockerfileとdocker-compose.ymlを作成
作成したReactアプリのルートディレクトリに以下のような内容で
Dockerfile
とdocker-compose.yml
を作る。DockerfileFROM node:14.4.0-buster-slim WORKDIR /usr/src/app RUN npm install --save prop-types RUN npm install -g create-react-appdocker-composeversion: '3' services: node: build: context: . tty: true environment: - NODE_ENV=production volumes: - ./:/usr/src/app command: sh -c "yarn start" ports: - "3000:3000"上記のような内容だと、作成したReactアプリのルートディレクトリがDocker環境内では
/usr/src/app
にマウントされるため、 WORKDIRを/usr/src/app
に指定してあれば、実行するスクリプトはsh -c "yarn start"
だけとなるため、これをcommand
で指定している。docker-hub nodeを見に行くと、
alpine
buster
stretch
などの名前がバージョンについています。なんのことかと思ったらLinuxディストリビューションですね。
slim
とついているものは、Debianディストリビューションでエクストラパッケージが含まれないものです。いずれにしても今回はDockerでnodeが動けばいいのでnode:14.4.0-buster-slim
を選択しました。
ちなみにDebianの現在のバージョンは buster(10.0) であり、それ以前は以下のようになります。Debian 10 (buster) — 現在の安定版リリース Debian 9 (stretch) — 過去の安定版リリース Debian 8 (jessie) — 過去の安定版リリース Debian 7 (wheezy) — 過去の安定版リリースalpine linuxはパワーユーザー向けの軽量Linuxのようです。軽量だけにイメージが小さく抑えられるかもしれませんが、私はDebianやUbuntuしか使ったこと無いので
alpine
の使用はやめておきました。Dockerイメージをビルド
$ docker-compose buildDockerでReact.jsアプリを実行する
起動
$ docker-compose upブラウザで http://localhost:3000 にアクセスしReact.jsが動作していることを確認。
Ctrl-C
で停止。バックグラウンドで起動
$ docker-compose up -d停止
$ docker-compose downイメージ情報を確認
$ docker images | grep dockertest dockertest_node latest daf22d9465c7 26 minutes ago 194MBおまけ
すべてのDockerイメージを停止
$ docker stop $(docker ps -q)
- 投稿日:2020-06-30T23:10:38+09:00
Firebase Realtime DatabaseをReactで使ってみる
はじめに
Firebase Realtime DatabaseをReact使ってみた今日このごろ。
備忘録も兼ねて使い方をまとめておく。
今回はReact(クライアント側)の設定だけで、Firebaseのプロジェクトの設定などは割愛する。
※一応やったことはWebUIポチポチしただけ。TL;DR.
Reactプロジェクト作成
create-react-app
からスタートする。
サクッと用意。$ npx create-react-app realtime-db-react --template typescriptFirebase周りの設定
プロジェクトの準備
FirebaseCLIを使って設定を進める。
設定は公式のリファレンスを見ながら行う。
また、プロジェクトは作ってある前提とする。# ログインしていなければログインする $ npx firebase login # Firebaseプロジェクト初期化 $ npx firebase init # 設定した内容は下記の通り >? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. ❯◉ Database: Deploy Firebase Realtime Database Rules ? Please select an option: ❯ Use an existing project ? Select a default Firebase project for this directory: ❯ 作成しておいたプロジェクト # デフォルトのまま ? What file should be used for Database Rules? database.rules.json ✔ Firebase initialization complete!DBのルール変更
上記手順で作ったままではDBに読み書きできないようになっているので少し修正する。
生成されているdatabase.rules.json
を下記のように修正。{ "rules": { // 読むのは誰でも可能 ".read": true, // 書くのは認証済みの場合のみ ".write": "auth != null" } }ルール反映
json変えたではもちろん反映されないので、
firebase deploy
コマンドを叩いて反映する。
反映できているかどうかは下記手順で確認できる。
表示されるURLからコンソールを表示
→左カラムにあるDatabase
を選択
→メインカラム上部のプルダウンからRealtime Database
を選択
→ルールタブを表示$ npx firebase deploy # ↓が表示されればOK ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/hogehoge/overviewテスト用データ追加
確認用にWebUIからデータ追加しておく。
追加したのは↓の感じ。{ "sample": { "key1": "value1", "key2": "value2" } }データを取得できるようにする
まずは登録したデータを取得するところまでやる。
Firebase SDKとFirebase Admin SDKのインストール
アプリからFirebaseを使うために必要なライブラリをインストールする。
adminの方は認証がいる場合に必要なのでどちらも入れておく。$ yarn add firebase firebase-admin
初期化周り
初期化に必要な情報をWebUIから取ってくる。
プロジェクトのページ左カラムにある歯車をクリック
→プロジェクトの設定をクリック
→Settingsページ、全般タブ下側にあるマイアプリにある</>
マークをクリック
→ウェブアプリにFirebaseを追加のページに飛ぶので、お好みの名前を入れて登録をクリック(hostingはお好みで。今回はやらない)
→スクリプトが表示されるので、firebaseConfig
の内容だけコピーする必要な情報を取れたのでコードを書いていく。
初期化はこれでOKimport firebase from 'firebase/app'; // 認証周りやDB周りで必要なためimportしておく import 'firebase/auth'; import 'firebase/database'; // コピーしてきたfirebaseConfigそのまま // 元がvarで宣言されているので、constに変更 const firebaseConfig = { // コピペ }; firebase.initializeApp(firebaseConfig); export { firebase };データベースに接続する
データのやり取りは
firebase.database.Reference
を通してやり取りされる。
今回は決まったパスのデータを取得したいので、パスを指定した上でメモ化しておく。// カスタムフックにしておく const useDatabase = () => { // 同じパスでは毎回同じ結果が得られるのでmemo化しておく return useMemo(() => firebase.database().ref('/sample'), []); };データを取得する
↑で作ったReferenceを受け取る関数を作成する。
Referenceのイベントをlistenすることでデータを取得できる。
指定したパスのデータに対する更新をすべて検知するにはvalue
を指定すれば良い。// hooksを使いたいのでカスタムhooksにしておく const useFetchData = (ref: firebase.database.Reference) => { const [data, setData] = useState<{[key: string]: string}>(); useEffect(() => { // イベントリスナーを追加するにはonを使う ref.on('value', snapshot => { // パスに対する全データを含むsnapshotが渡される // ない場合はnullが変えるので存在をチェックしておく if (snapshot?.val()) { setData(snapshot.val()); } }); return () => { ref.off(); }; // refの変更に応じて再取得する }, [ref]); // データを返却する return { data }; } // 実際に呼び出す際はこちらを使う export const useFetchAllData = () => { // refを取得して const ref = useDatabase(); // ref渡してデータを取得する return useFetchData(ref); };Componentからデータ取得する
useFetchAllData
を使ってデータを取ってくるコンポーネントを作成する。
取得したデータはobject形式なので、list形式に変換してから表示する。import React, { useMemo } from 'react'; import { useFetchAllData } from '../firebase/firebaseDB'; export const ListComponent: React.FC = () => { // dataを取ってくる const { data } = useFetchAllData(); // object形式なので使いやすいように{key, value}形式のリストに変換する // また、データが変わらない限り結果は同じなのでメモ化しておく const dataList = useMemo(() => Object.entries(data || {}).map(([key, value]) => ({ key, value })), [data]); return <dl>{dataList.map(({ key, value }) => <React.Fragment key={`${key}${value}`}> <dt>key: {key}</dt> <dt>value: {value}</dt> </React.Fragment> )}</dl> };起動した際にデータを表示できれば取得はOK。
データを登録できるようにする
次にデータの登録ができるようにする。
FirebaseのWebUIで設定が必要なのでそこから始める。FirebaseでGoogleログインできるようにする
WebUIから設定をする。
手順は下記の通り。
左カラムのAuthenticationをクリック
→デフォルトでUsersタブにいると思うので、そのまま表示されているログイン方法を設定
ボタンクリック
→一覧の中からGoogleを選択
→右上のトグルを有効に切り替える
→プロジェクトのサポートメール
にメールアドレスを入力して保存をクリックログイン処理実装
公式ドキュメントを参考に実装する。
const signInWithPopup = () => { // Googleプロバイダオブジェクトのインスタンスを作成 const googleAuthProvider = new firebase.auth.GoogleAuthProvider(); // 別タブでログイン画面に飛ばしたいため、signInWithPopupを使う // リダイレクトでログイン画面に飛ばしたい場合はsignInWithRedirectを使う return firebase.auth().signInWithPopup(googleAuthProvider); }ログアウト処理実装
const signOut = () => { // signOutを呼び出すだけでOK return firebase.auth().signOut(); }ログイン、ログアウトボタン実装
ログイン、ログアウトは↑の処理で良いが使いやすい用にボタンにする。
合わせてログイン/ログアウトの判定をするため、ログインしているかどうかを確認する処理も追加する。// ログインしているかチェックするカスタムフックを作る const useFirebaseLogin = () => { // stateでログイン状態を保持 const [loggedin, setLoggedin] = useState(false); useEffect(() => { // 現在ログインしているユーザを取得 firebase.auth().onAuthStateChanged(user => { // ユーザ情報が取れればログイン状態 setLoggedin(!!user); }); }, []) // ログイン情報を返却 return loggedin; }; // ログイン、ログアウトボタンを作る export const FirebaseAuthComponent: React.FC = () => { const loggedin = useFirebaseLogin(); if (!loggedin) { // ログインしていなければログインボタンを表示 return <button onClick={() => signInWithPopup()}>ログイン</button>; } // ログインしているならログアウトボタンを表示 return <button onClick={() => signOut()}>ログアウト</button>; }登録処理作成
Realtime Databaseに登録するための処理を作成する。
登録にはfirebase.database.Reference.set()
を使えば良い。
ただしこの登録はsetに渡した値での登録となる。
つまり、既存のデータも含めて渡してあげないと登録済みのデータが消える。const useSetDocument = (ref: firebase.database.Reference) => { const updateDocument = useCallback( (document: unknown) => { // refについては前回の記事参照 // setに登録したいデータを渡してあげれば登録できる ref.set(document); }, [ref] ); return updateDocument; }; export const useRegisterData = () => { // 前回作ったuseDatabase()を使いref取得 const ref = useDatabase(''); const setDocument = useSetDocument(ref); // 登録済みのデータを全部取得する const {data: registeredData} = useFetchAllData(); // データを登録する関数を返却する const registerData = useCallback((registerData: { [key: string]: string }) => { // 既存のデータと登録するkey-valueを合わせて登録関数に渡す setDocument({...registeredData, ...registerData}); }, [setDocument, registeredData]); return registerData; };登録フォーム作成
key-valueを登録するフォームを作成する。
今回は登録できることだけを目的として一旦必要な諸々は見なかったことにする。export const FormComponent: React.FC = () => { // データを登録する関数 // ↑で作成したuseRegisterDataを使う const registerData = useRegisterData(); const [keyData, setKeyData] = useState<string>(''); const [valueData, setValueData] = useState<string>(''); return <> {/* 今回は登録できれば良いとして、keyとvalueについてそれぞれ登録するinputフォームを作る */} <label>Key: <input placeholder="key" onChange={(event: ChangeEvent<HTMLInputElement>) => setKeyData(event.target.value)}/></label> <label>Value: <input placeholder="value" onChange={(event: ChangeEvent<HTMLInputElement>) => setValueData(event.target.value)}/></label> {/* 登録ボタンを押したときにsetDocument関数を呼び出してデータを追加する */} <button onClick={() => setDocument({[keyData]: valueData})}>登録</button> </>; }App.tsxを修正
前回、何もしないでListComponentを呼び出すだけにしてあった
App.tsx
を修正する。
修正はfirebaseへのログイン周りと登録フォームを新たに追加するのみ。const App: React.FC = () => { return ( <> <FirebaseAuthComponent /> <ListComponent /> <FormComponent /> </> ); }おまけ
取得、登録ができるところまで行った。
おまけとして部分更新や削除の場合もまとめる。部分更新したい場合
更新には
firebase.database.Reference.update()
を使う。
setを使うとfirebase.database().ref('/sample')
で指定したパス以下全てが更新されてしまうが、updateを使うと指定したパス以下でkeyが一致するオブジェクトをのみを変更できる。
{"key1": "value1", "key2": "value2"}
というデータが登録されている状態で、update({key1: 'value2'})
というデータを渡してupdateを呼び出すと{"key1": "value2", "key2", "value2"}
というように部分的に更新ができる。
一致するキーが無ければ新規登録になる。
(なので、1件ずつ登録する今回のサンプルではsetではなく、updateを使っても変わらない)setの時と同じく関数を作っていく。
const useUpdateDocument = (ref: firebase.database.Reference) => { // ref.updateがObjectを受け取るので、Objectを引数に取る関数を定義 const updateDocument = useCallback((document: Object) => ref.update(document), [ref]); return updateDocument; } export const useUpdateData = () => { // setの時と同じくrefを取得して、 const ref = useDatabase(); // 関数呼び出して const updateDocument = useUpdateDocument(ref); // 更新処理を作成する const updateData = useCallback((registerData: {[key: string]: string}) => { updateDocument(registerData); }, [updateDocument]); return updateData; }登録フォームのComponentに更新ボタンを追加する。
export const FormComponent: React.FC = () => { const registerData = useRegisterData(); // 更新処理を呼び出す const updateData = useUpdateData(); const [keyData, setKeyData] = useState<string>(''); const [valueData, setValueData] = useState<string>(''); return <> <label>Key: <input placeholder="key" onChange={(event: ChangeEvent<HTMLInputElement>) => setKeyData(event.target.value)}/></label> <label>Value: <input placeholder="value" onChange={(event: ChangeEvent<HTMLInputElement>) => setValueData(event.target.value)}/></label> <button onClick={() => registerData({[keyData]: valueData})}>登録</button> {/* ボタン押したときに対象のデータを更新 */} <button onClick={() => updateData({[keyData]: valueData})}>更新</button> </>; }削除したい場合
削除には
firebase.database.Reference.remove()
を使う。
remove()を使うとfirebase.database().ref('/sample')
で指定したパス以下のデータ全てが削除される。こちらも同じく関数を作っていく。
const useRemoveDocument = (ref: firebase.database.Reference) => { // 特に引数が必要ないのでただ呼び出すのみ const deleteDocument = useCallback(() => ref.remove(), [ref]); return deleteDocument; } // set、updateと同じなので割愛 export const useDelteData = () => { const ref = useDatabase(); const removeDocument = useRemoveDocument(ref); const deleteData = useCallback(() => removeDocument(), [removeDocument]) return deleteData; }同じく登録フォームのComponentに削除ボタンを追加する。
export const FormComponent: React.FC = () => { const registerData = useRegisterData(); const updateData = useUpdateData(); // 削除処理を呼び出す const deleteData = useDelteData(); const [keyData, setKeyData] = useState<string>(''); const [valueData, setValueData] = useState<string>(''); return <> <label>Key: <input placeholder="key" onChange={(event: ChangeEvent<HTMLInputElement>) => setKeyData(event.target.value)}/></label> <label>Value: <input placeholder="value" onChange={(event: ChangeEvent<HTMLInputElement>) => setValueData(event.target.value)}/></label> <button onClick={() => registerData({[keyData]: valueData})}>登録</button> <button onClick={() => updateData({[keyData]: valueData})}>更新</button> {/* ボタン押したときに対象のデータを全消し */} <button onClick={() => deleteData()}>全消し</button> </>; }まとめ
Firebaseを使って一通り読み込み、登録、更新ができるところまでやった。
今度は実際に何かを作ってみたいところ。参考サイト
- 投稿日:2020-06-30T22:23:44+09:00
JSFiddleでReactを実行する
Reactとは
Reactとは、WebサイトのUIパーツを構築するためのJavaScriptライブラリです。
Facebookが開発し、OSSとして公開されています。
また、Reactは「宣言的な」ライブラリなので、Webデザイナー向けとも言われています。JSFiddleとは
HTML、CSS、およびJavaScriptを簡単に実行できるオンラインのIDEサービスです。
■JSFiddle
https://jsfiddle.net/JSFiddleでReactを実行する
JSFiddleでReactを実行してみましょう。
今回は「Hello World」を表示してみます。
ソースコードは以下の2つです。hello.html<!-- Reactの読み込み --> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <div id="hello_container"></div>hello.jsReactDOM.render( React.createElement('h1', null, 'Hello World'), document.getElementById('hello_container') );JSFiddleの画面にて、上記のHTMLとJavaScriptを貼り付けて、左上の「Run」ボタンをクリックするだけでReactが実行できます。
実行結果は右下に表示されます。
「hello_container」の要素に対して、「Hello World」が出力されていればOKです。
- 投稿日:2020-06-30T11:44:34+09:00
モノリスはクラピカ
Reactを学習中のRailsエンジニアです。学習していて思うのは、Reactは(サービス開発全体のことを考えると)Railsよりはるか学習コストが高いということ。似たようなサービスを作るために必要な手数も、考えなければいけないこともかなり多いです。
正直最初は「技術が大好きなエンジニアの自己満だろ」「Railsに飽きた奴らがやってるだけだろ」くらいに思っていたのですが、そうとも限らないことが腹落ちして理解できるようになったので、現在の考えをまとめてみようと思います。
おことわり
この記事の中で登場する
SPA
という単語はフロントエンドとバックエンドを明確に分けて開発されるアプリケーション
くらいの意味として解釈してください。フロントエンドのロジックをNuxt.jsで作ってNetlifyにデプロイして、Goで作ったAPIをAWSで動かす、みたいな構成のやつです。乱暴ですみません。他に良い表現があればコメントください。
モノリス
はRailsやLarabelでviewファイルの生成まで行っているアプリケーションを指します。SPA⇄MPA
マイクロサービス⇄モノリス
で比較しろよ、って話ですが、マイクロサービスについて語れるほどの知識はないし、MPAというよりモノリスの話がしたかったので、雰囲気で読んでください。SPAが台頭した理由
よりリッチな表現ができる(UXの向上)
ページ遷移が高速、DOMを色々動かしてもコードがカオスになりにくいなど。SPAのメリットとして真っ先にあげられることが多いので、みなさんもよくご存知かと思います。
フロントエンドとバックエンドが疎結合になる
疎結合になると、新技術を部分的に採用することが容易になります。またサービスの規模が大きくなっても、コードがカオスになりづらいです。Railsエンジニアをやっていると、成長し大規模化したRailsアプリ開発者がつらそうにしている記事をよく見かけます。
クロスプラットフォーム対応
SPAを採用すると、web、iOS、Android、macOS用アプリ、windows用アプリで同じAPIを使うことができます。
元々web以外のプラットフォームでは、表示周りやページ切り替え等のロジックを先にインストールして、他に必要なデータのやりとりだけをAPIを使って行う、というスタイルで統一されていました。webも同じスタイルに揃うと構成がキレイになってすっきりしますね。マネージドサービスの充実
この記事を書くに至った理由です。この視点を得て、SPAが普及したことの必然性を理解しました。
2020年現在、ざっと思いつくだけでも以下のようなマネージドサービスが存在します。認証: Firebase Auth, Auth0, Cognito
決済: Stripe
検索: Algolia
サーバーレスコンピューティング: Lambda, Cloud Functions
NoSQL: CloudFirestore, DynamoDB
ストレージ: S3
メール送信: SendGridここで言えるのは、バックエンドで自前で実装しなければいけない機能が大幅に減ったということです。
「外部サービスをどれだけ有効に活用できるか」が重要になってくると、モノリスの魅力は相対的に薄れていきます。そもそもRuby on Railsが登場した2004年にはAWSすら存在しておらず、モノリスがwebアプリ開発のど真ん中に鎮座していた2010年代前半にも、上記で紹介したサービスの多くは存在していませんでした。
いくらRuby on Railsも進化しているとはいえ、これだけ状況が変わってしまえば、開発のメインストリームから外れてしまうのは仕方のないことだと感じます。
また余談ですが、Rubyの認証ライブラリで1番人気があるDeviseを使っている人は、全員つらそうな顔をしています。
その他
- コンポーネント単位で分割することで保守性や再利用性が高くなったり、デザイナーとの協業がしやすくなる
- TypeScriptとVSCodeの連携がすごい
などなど他にもSPAのメリットは色々とありそうですが、これらはどちらかというと副産物に近く、SPAが台頭したメインの理由では無いと考えています。
モノリスは用済みになったのか
全くそんなことは無いと考えています。
以下の条件を満たすアプリケーション開発では、今でもモノリスがファーストチョイスです。
「モノリスでも問題ない」ではなく、「モノリスの方が圧倒的に良い」です。
- webだけで良い
- 関わる開発者が少ない
- UXの要件がそれほど厳しくない
具体的な場面としては
- 多くのwebアプリののプロトタイプ
- リリース後の修正が少ないと予想されるシステム
- 機能要件が指定された受託開発など
- 「この機能を削ればこの予算で実現できます」など仕様をモノリスに寄せることで双方が得をする場面は必ず存在する
- 小~中規模のwebアプリの一部
- 必要な要件や、想定されるユーザー数などから技術選定。モノリスの方が効率良く開発できるサービスは、今後もそこそこの割合で残り続けるはず。
などが考えられます。
モノリスの最大の弱点とされる密結合は、必ずしも悪ではないのです。
分割は短期的な生産性を下げます。
密結合は短期的な生産性を上げます。
この点を考慮して技術選定をするべきです。結論
特定の制約の上で、モノリスはもの凄い力を発揮します。
参考
- 投稿日:2020-06-30T04:30:04+09:00
フロントエンド React, Vue.js, Angular フレームワークを全部試した結果、Angular が一番良かった。
最近、暇で以下のサービスを作った。
https://deau-project.herokuapp.com/フロントエンド React, Vue.js, Angular フレームワークを全部試した結果、Angular が一番良かった。
Angular, Material UI, NgRx が、一番まとまりがよく、簡単に実装できる。フロントには、React を使った。
仕事では、Angular => React => Angular => Vue.js => Angular => React と使用してきた。今回、だいぶ久しぶりに React を使い、フォームの作成が面倒なことに気づいた。
どのフレームワークにも、デザインのフレームワーク?モジュール?があり、
Vue.js: Vuetify、React: Material-UI、Angular: Material UI がある。
このデザイン周りは、どのフレームワークでも同じぐらい。使いやすいし、デザインもいい。React のフォームは、React Hook Form を使用した。
Material-UI で実装したかったが、よくわからなかった。
このよくわからない理由は、情報が多すぎる点である。React には、ベストプラクティスがない。
Babel を使うかどうか?や、Redux を使うかどうか?や、axios 使うかどうか?など。検索するたびに、問題が解決するというより、あれ?こっちのモジュールの方がいいかも?なんて、インストールして試すことに時間がかかった。
今回、React で Redux を使っていない。
Vue.js, Angular では使用した。
もしかしたら、React Redux でフォームの実装が楽になるのかもしれない。速度は比較したわけではないけど、Vue.js が遅く感じる。
なんか、モッサリしてる。だからもし、私が新たにサービスを始めるなら、Angular, Material UI, NgRx にするだろう。