SPA・サーバレスハンズオン part3 React/Firebase Cloud Firestoreでチャットアプリを作る
シリーズ
- part1: SPA・サーバレスハンズオン part1 React/Firebase Hostingで初めてのwebサイト公開 - 未熟学生エンジニアのブログ
- part2: SPA・サーバレスハンズオン part2 一般的なwebページとReactアプリの構成を知る - 未熟学生エンジニアのブログ
- part3: 今回
前提
- part1とpart2をやっている
- html,javascriptを一度でも書いたことがある
- プログラミングの「クラス」と「継承」を使ったことがある
- Firebaseに登録している
今回できるようになること
- Reactの基本の流れがわかる
- Reactのstate管理の方法がわかる
- Firestoreと連携してデータベースを使ったチャットを作れる
- 次にやるべきことがわかる
チャットアプリを作る
今回はチャットアプリを作ります。
今回のアプリは以下の記事のFirestore版のようなものになります。
サンプル
デモページ: https://huit-tetsufe-0506.firebaseapp.com/ ソースコード: https://github.com/TetsuFe/huit_react_handson_20190506
参考
- 元記事: React & Firebaseで簡単なChatアプリを作ってみた - Qiita
- Firestoreドキュメント: https://firebase.google.com/docs/firestore/quickstart?hl=ja
FirebaseのFirestoreを使うための初期設定をする
FirebaseにはHosting以外にもいろんな便利機能があります。
Cloud Firestore(Firestore)は、Firebaseのデータベースサービスで、web上にデータを保存したり、取得したりするのに使えます。
Firestoreの立ち位置としては以下の図のようになります。
さて、Hostingの際と同じように、Firebaseのセットアップをしましょう。
Firebaseのコンソール画面から、Firestoreを選択します。
これでひとまず初期設定は完了です。
今回はテストモードとして誰でもデータの改変が行えるようにしましたが、これは非常に危険です。Firestore側での認証設定やセキュリティルールは適切に設定しておかないとデータの改変が自由に行えてしまいます。(今回のように重要なデータでない場合は問題ありません)
reactページ側からFirestoreにアクセスするためのモジュールをインストールします。
$ npm install firebase --save
FirestoreにアクセスするためにはapiKeyなどの情報が必要です。
Firebaseの画面から、APIキーとprojectidをコピーします。これはサイトに埋め込んでも大丈夫です。
参考:Firebase apiKey ってさらしていいの? ほんとに? - Qiita
上の例では、以下のようにファイルを作成します。(値は全員少し違うはずです)
firebaseフォルダを作り、その中にconfig.jsを作りましょう。
firebase/config.js
export const firebaseConfig = { apiKey: 'AIzaSyBTXINX3dCCSCQcwypUxk7wqRiEYbxqgw0', projectId: 'huit-tetsufe-0506' };
フォルダの構成は以下のようになっているはずです。
huit_handson_20190506 ├── firebase │ └── config.js
さらに、firebase/index.js というファイルを作り、データベースを操作するためのfirebaseDbというインスタンスを他のファイルでも使い回せるようにします。
firebase/index.js
import firebase from 'firebase'; import { firebaseConfig } from './config.js'; export const firebaseApp = firebase.initializeApp(firebaseConfig); export const firebaseDb = firebaseApp.firestore();
フォルダの構成は以下のようになっているはずです。
huit_handson_20190506 ├── firebase │ └── config.js │ └── index.js
Reactとコンポーネント
Reactは、コンポーネントという単位で表示する要素を小さなモジュール(ファイル)に分割することを推奨しています。
要は要素ごとに細かく分けるだけです。難しいことではありません。分ける単位もとりあえずは適当でいいでしょう。
コンポーネントを作る前に、srcフォルダの中に、componentsフォルダを作成しましょう。
componentsというフォルダ名に意味はありません。なぜ作る必要があるのでしょうか?
単にわかりやすいようにフォルダわけしただけです。ただ、これは地味に重要なことです。画面が複雑になればファイル数も多くなり、ファイルはどんどん見つけにくくなるからです。複雑になっていけば、componentsの中にさらにフォルダを作るなどする必要が出てくると思います。
src/components/Message.jsというファイルを作りましょう。
import React from "react"; export default class Message extends React.Component { render() { return ( <div className="Message"> <img className="" src={this.props.message.profile_image} /> <div className=""> <p className="">@{this.props.message.user_name}</p> <p className="">{this.props.message.text}</p> </div> </div> ); } }
さらに、src/components/ChatForm.jsというファイルを作りましょう。
import React from "react"; export default class ChatForm extends React.Component { render() { return ( <div className="ChatForm"> <div className=""> <input name='user_name' onChange={this.props.onTextChange} className="" placeholder="名前" /> <input name='profile_image' onChange={this.props.onTextChange} className="" placeholder="プロフィール画像URL" /> </div> <textarea name='text' className="" onChange={this.props.onTextChange} /> <button className="" onClick={this.props.onButtonClick}>送信</button> </div> ); } }
こんな感じの構造になっているはずです
$ tree src src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── ChatForm.js │ └── Message.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js
src/App.jsを編集して、MessageコンポーネントとChatFormコンポーネントを使いましょう。
import React from 'react'; import './App.css'; import { firebaseDb } from './firebase/index.js' import Message from './components/Message.js' import ChatForm from './components/ChatForm.js' const messages = firebaseDb.collection('messages') class App extends React.Component { constructor(props) { super(props); this.onTextChange = this.onTextChange.bind(this) this.onButtonClick = this.onButtonClick.bind(this) this.state = { text: "", user_name: "", profile_image: "", messages: [] } } componentDidMount() { messages.onSnapshot ((querySnapshot) => { // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう let msgs = [] querySnapshot.forEach((doc) => { // 新しい順に取得される const m = doc.data(); msgs.push({ 'text': m.text, 'user_name': m.user_name, 'profile_image': m.profile_image, }) }); this.setState({ messages: msgs }); }); } onTextChange(e) { if (e.target.name == 'user_name') { this.setState({ "user_name": e.target.value, }); } else if (e.target.name == 'profile_image') { this.setState({ "profile_image": e.target.value, }); } else if (e.target.name == 'text') { this.setState({ "text": e.target.value, }); } } onButtonClick() { // 簡単なバリデーション if (this.state.user_name == "") { alert('user_name empty') return } else if (this.state.text == "") { alert('text empty') return } messages.add({ "user_name": this.state.user_name, "profile_image": this.state.profile_image, "text": this.state.text, }) .then(function (docRef) { console.log("Document written with ID: ", docRef.id); }) .catch(function (error) { console.error("Error adding document: ", error); }); } render() { return ( <div className="App"> <div className="App-header"> <h2>Chat</h2> </div> <div className="MessageList"> {this.state.messages.map((m, i) => { return <Message key={i} message={m} /> })} </div> <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} /> </div> ); } } export default App;
Reactのコードを読み解く
ここからはコードの解説に入ります。
src/App.js を見てください。src/App.jsでは、Appというクラスが宣言されています。今回の場合はこれだけです。ここからはAppクラスについて解説します。
まず、Appクラスですが、これはReact.Componentというクラスを継承する形で宣言されています。
import React from 'react'; // 省略 class App extends React.Component { // 省略 }
React.Componentを継承する理由とはなんでしょうか。どういう意味を持っているかこれから説明します。
React.Componentを継承すると起こること
- 1.
<App/>
などと書くことでReactのコンポーネントとして好きな場所に埋め込むことができる - 2.ライフサイクルメソッドを自動的にいいタイミングで実行してくれる
- componentDidMound()
- render()
- その他
1. <App/>
などと書くことでReactのコンポーネントとして好きな場所に埋め込むことができる
src/index.jsで、<App/>
として使われています。React.Componentを継承すると、Reactのコンポーネントとして扱うことができます。これはとりあえずそういうルールなのだと覚えましょう。(おまじないが嫌い?ならばこれを読みなさい。React.Component – React)
src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; // 省略 ReactDOM.render(<App />, document.getElementById('root')); /// 省略 serviceWorker.unregister();
「好きな場所に埋め込むことができる」とはどういうことでしょうか。これはAppクラスのrender()内を見ると意味がわかります。
以下の<ChatFrom>
もReact.Componentを継承しています。(src/components/ChatForm.jsを見ればわかります)
src/App.js
// 省略 class App extends React.Component { // 省略 render() { return ( <div className="App"> <div className="App-header"> <h2>Chat</h2> </div> <div className="MessageList"> {this.state.messages.map((m, i) => { return <Message key={i} message={m} /> })} </div> <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} /> </div> ); } }
以上のように書くことで、<ChatFrom>
は<div className="App-header">
よりも下になるように配置できます。
2. ライフサイクルメソッドを自動的にいいタイミングで実行してくれる
1はまあそういうものか、という感じだと思ってもらえればOKです。2の方が重要です。
React.Component を継承している Appクラスには、componentDidMound()などの、あるタイミングで自動的に実行されるメソッドがあります。
これらの あるタイミングで自動的に実行されるメソッド群を、ライフサイクルメソッドといいます。
モバイルアプリやフレームワークを使ったフロントエンド開発では同じような概念が出てくるので、ここでライフサイクルメソッドというものの存在に慣れておくとReact以外を使う際に対応しやすいです。
Reactのライフサイクルメソッドの流れは、以下のようになります。
引用元:GitHub - iktakahiro/react-component-lifecycle-diagram
初期化とマウントに関するライフサイクルメソッド
参考:React コンポーネントのライフサイクルイベント その1 - React 入門
Appの初期化の際、まず最初に自動的に実行されるのがconstructor(props)というメソッドになります。
constructor(props)は、Appが作成されたときに一回だけ実行されます。このメソッドでは、this.stateなどの変数の初期化を主に行います。
src/App.js
class App extends React.Component { constructor(props) { super(props); this.onTextChange = this.onTextChange.bind(this) this.onButtonClick = this.onButtonClick.bind(this) this.state = { text: "", user_name: "", profile_image: "", messages: [] } } // 省略 }
次がrender()です。render()は返り値を持ちます。returnで値を返していることに気をつけてください。
render()は、htmlを模したjsxという記法で書かれたオブジェクトを返します。つまり、これはhtmlではありません。jsxという独特の記法で、これはjavascriptのオブジェクトです。<div className="App">
は、htmlではありません。javascriptです。
class App extends React.Component { // 省略 render() { return ( <div className="App"> <div className="App-header"> <h2>Chat</h2> </div> <div className="MessageList"> {this.state.messages.map((m, i) => { return <Message key={i} message={m} /> })} </div> <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} /> </div> ); } }
とはいっても、とりあえずは以下のように書けばよいと考えていればOKです。
class App extends React.Component { // 省略 render() { return ( // ここにhtmlっぽいjsxというものを書く ); } }
jsxの書き方は、色々書いてみてなれるしかないと思います・・
次がcomponentDidMount()です。今回は、Firestoreからのデータ取得と、React内のthis.stateの更新をしています。(実際には、データが更新された際にその処理をするという意味になります)
src/App.js
class App extends React.Component { // 省略 componentDidMount() { messages.onSnapshot ((querySnapshot) => { // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう let msgs = [] querySnapshot.forEach((doc) => { // 新しい順に取得される const m = doc.data(); msgs.push({ 'text': m.text, 'user_name': m.user_name, 'profile_image': m.profile_image, }) }); this.setState({ messages: msgs }); }); } // 省略 }
Reactの状態管理:setStateとライフサイクルメソッド
ここまででAppクラスの初期化の際の流れを知ることができたと思います。ここからは、ReactのsetStateとライフサイクルメソッドの関係について説明します。
Reactを使ったWebアプリでは、ユーザのアクションやDBのデータの変更に応じて変数(状態)を更新し、画面(ページ)を適宜書き換えるということを行います。例えば、チャット中に新しい投稿があった時、それを画面に反映するという場合をイメージしてください。
このような場合に使うのが、setStateメソッドです。
this.setState({ messages: msgs });
先ほどの図を再掲します。
引用元:GitHub - iktakahiro/react-component-lifecycle-diagram
ここで、setStateを実行した時、図の中央の黒丸の「state changed」が起こり、ライフサイクルメソッドが順に実行されます。図中ではshouldComponentUpdateなどのメソッドがありますが、今回はrender()だけ考慮すればOKです。
簡潔に言えば、setStateは、変数(状態)を変更した上で、render()などのライフサイクルメソッドを起動し、画面を更新するためのメソッドです。
src/App.jsをみてみましょう。
ここでは、チャットのDBデータが更新されたとき、データに合わせて画面を更新するという例を考えます。
- setStateは、firestoreから受け取ったデータmsgsを使ってthis.state.messagesを更新
- その後、setStateに反応したライフサイクルメソッドrender()が自動的に実行され、更新されたthis.state.messagesを使ってメッセージリストを画面に表示することができる
setStateはcomponentDidMount()内で実行されているように見えますが、実際にはcomponentDidMount()が呼ばれたタイミングとsetStateが実行されるタイミングは違う(ややこしいですが)のに注意してください。
class App extends React.Component { // 省略 componentDidMount() { messages.onSnapshot ((querySnapshot) => { // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう let msgs = [] querySnapshot.forEach((doc) => { // 新しい順に取得される const m = doc.data(); msgs.push({ 'text': m.text, 'user_name': m.user_name, 'profile_image': m.profile_image, }) }); this.setState({ messages: msgs }); }); } // 省略 render() { return ( <div className="App"> <div className="App-header"> <h2>Chat</h2> </div> <div className="MessageList"> {this.state.messages.map((m, i) => { return <Message key={i} message={m} /> })} </div> <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} /> </div> ); } }
Firestoreに保存するデータ構造
Firestoreに保存するデータ構造としては、messagesという箱(FirestoreではCollectionといいます)を用意して、その中にメッセージを追加していく形になります。以下がデータの例になります。
{ "messages": [ { "1": { "profile_image": "https://pbs.twimg.com/profile_images/1064542710327963648/eN983mCr_400x400.jpg", "text": "こんにちは", "user_name": "僕" } }, { "2": { "profile_image": "http://hyohyolibrary.com/wp-content/uploads/2018/07/oregairuzoku9thumbnail.png", "text": "やっはろー", "user_name": "由比ヶ浜結衣" } } ] }
Firestoreのaddメソッドを使うことで投稿処理を実装します。(注意:addメソッドの場合、新しいデータはcollectionの中で雑な順番で保存されます。全てのメッセージを一気に取得する際、順番はaddした順にはならないということです。チャットなどの場合、上から新しい順に表示するか上から古い順に表示するかという順番は重要です。例えばLINEの場合は上から古い順に表示ですよね。order_byを使うなどすることで、この問題は解決できます。https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja)
src/App.js
onButtonClick() { // 簡単なバリデーション if (this.state.user_name == "") { alert('user_name empty') return } else if (this.state.text == "") { alert('text empty') return } messages.add({ "user_name": this.state.user_name, "profile_image": this.state.profile_image, "text": this.state.text, }) .then(function (docRef) { console.log("Document written with ID: ", docRef.id); }) .catch(function (error) { console.error("Error adding document: ", error); }); }
フォームのボタンを押したとき、onButtonClicked()が呼ばれて(関数が実行されることを関数が呼ばれるという言い方をします)、onButtonClicked()の中でFirestoreのaddメソッドによってDBにデータを追加します。
DBデータの変更に応じたリアルタイム更新
チャットの場合、相手からの返事が来たときに自動的にページに反映されると嬉しいですよね。Firestoreには、それを実現できる機能があります!
FirestoreではonSnapshotメソッドを使うことで、DB上のデータがアップデートされたとき、自動的に接続中のクライアントのデータにも反映することができます。(getメソッドではできません)
ちなみに内部ではwebsocket通信をしているようです
参考:https://firebase.google.com/docs/firestore/query-data/listen?hl=ja
src/App.js
class App extends React.Component { // 省略 componentDidMount() { messages.onSnapshot ((querySnapshot) => { // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう let msgs = [] querySnapshot.forEach((doc) => { // 新しい順に取得される const m = doc.data(); msgs.push({ 'text': m.text, 'user_name': m.user_name, 'profile_image': m.profile_image, }) }); this.setState({ messages: msgs }); }); } // 省略 }
stateとprops
参考:React における State と Props の違い - Qiita
最後に、stateとpropsについて説明します。といっても、以下の2点を覚えておけばとりあえず問題ありません。
またsrc/App.jsの例をみてみます。
class App extends React.Component { constructor(props) { super(props); this.onTextChange = this.onTextChange.bind(this) this.onButtonClick = this.onButtonClick.bind(this) this.state = { text: "", user_name: "", profile_image: "", messages: [] } } componentDidMount() { messages.onSnapshot ((querySnapshot) => { // クエリが非同期処理のため、この中にsetStateなどを書かないと空になってしまう let msgs = [] querySnapshot.forEach((doc) => { // 新しい順に取得される const m = doc.data(); msgs.push({ 'text': m.text, 'user_name': m.user_name, 'profile_image': m.profile_image, }) }); this.setState({ messages: msgs }); }); } render() { return ( <div className="App"> <div className="App-header"> <h2>Chat</h2> </div> <div className="MessageList"> {this.state.messages.map((m, i) => { return <Message key={i} message={m} /> })} </div> <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} /> </div> ); } }
ここで、stateとして使われているのはtext, user_name, profile_image, messagesです。this.state.text, this.state.user_nameのようにアクセスすることができます。これら変数の値はこのクラス内で更新されます。そのため、このクラス内でthis.stateとして宣言します。これがstateです。
constructor(props) { super(props); this.onTextChange = this.onTextChange.bind(this) this.onButtonClick = this.onButtonClick.bind(this) this.state = { text: "", user_name: "", profile_image: "", messages: [] } }
一方で、propsは、他のコンポーネントから受け取った変数のことを指します。もっというと、渡す側はstateとして扱っている変数が、渡された側ではpropsになります。
<div className="MessageList"> {this.state.messages.map((m, i) => { return <Message key={i} message={m} /> })} </div>
この3行目のmessage={m}という部分で、変数の受け渡しが起こっています。Messageコンポーネント側から見ると、messageというpropsを受け取っています。
7行目をみてください。this.props.messageという形で使っていることがわかると思います。
src/components/Message.js
import React from "react"; export default class Message extends React.Component { render() { return ( <div className="Message"> <img className="" src={this.props.message.profile_image} /> <div className=""> <p className="">@{this.props.message.user_name}</p> <p className="">{this.props.message.text}</p> </div> </div> ); } }
propsとstateを分ける意味
propsはあくまで受け取るだけで、その値は受け取ってから変更されることがありません。これは、コードを読むときに変更されうる値かどうかがわかりやすくなるという効果があります。言い換えれば、そのコンポーネント内にpropsを更新する処理が書かれていないことを示します。propsの更新にバグがあると感じたときは、親コンポーネントを見ればいい、ということに瞬時に気づくことができます。
Firebase Hostingを使ってデプロイ(web公開)する
せっかくチャットを作ったので、友達や家族とチャットができるようにweb上に公開しましょう!
前提:Firebase Hostingの初期設定をpart1でやっていること
ターミナルを開いて、以下を実行します。($は省略してください。)
$ npm run build
$ firebase deploy --only hosting
これでみんなとチャットができます!
おまけ:CSSを書いてみる
ここまでのサンプルコードはかなりひどい見た目になっていると思います。
これをまともな見た目にして、恥ずかしくないようにします。
参考
- Vue.js+Firebaseで認証付きチャット | 基礎から学ぶ Vue.js
- DOM Elements – React
- align-items - CSS: カスケーディングスタイルシート | MDN
src/components/Message.js
import React from "react"; export default class Message extends React.Component { render() { const style = { "item": { display: "flex", alignItems: "flex-start", marginBottom: "0.8em", }, "itemImage": { width: "100px", height: "100px", objectFit: "cover", borderRadius: "20px", }, "itemName": { textAlign: "left", fontSize: "75%" }, "itemMessage": { position: "relative", display: "inline-block", padding: "0.8em", background: "#deefe8", borderRadius: "4px", lineHeight: "1.2em", } } return ( <div className="Message" style={style.item}> <img style={style.itemImage} src={this.props.message.profile_image} /> <div className=""> <p style={style.itemName}>@{this.props.message.user_name}</p> <p style={style.itemMessage}>{this.props.message.text}</p> </div> </div> ); } }
「chat」という文字がデカすぎるので、なんとかします。
src/App.css
.App { text-align: center; } .App-header { background-color: #282c34; min-height: 10vh; display: flex; flex-direction: column; align-items: center; justify-content: center; color: white; }
min-height: 10vh
としたので、小さくなりました。
画面が大きいと不恰好ですが、疲れたのでここまでにします。後はcss職人のみなさんの頑張りに期待します。cssむずいです。
ちなみに、CSSは以下の資料がわかりやすいので一度見てみるといいと思います。
Next Step Hint 1: 複数ページのサイトを作る
今回作ったサイトは一ページだけでした。複数のページにまたがっているパターンが普通だと思います。
react-routerというライブラリを使えば、SPAでも複数のページを表現できます。
Next Step Hint 2: デザインをGoogleっぽくする
Google系のサイトで使われているMaterial Designというデザインルールに準拠したコンポーネントを使って見た目をいい感じにすることもできます。Youtubeなどを見るとイメージがわきやすいかと思います。
The world's most popular React UI framework - Material-UI というライブラリを使えば、簡単にMaterial Designなサイトをつくれます。
例えばボタンは以下のリンクに作り方がのってます。
導入方法は以下を参考にすると良いかと思います。
公式サイトに色々なパーツの使い方が載っているので、色々試してみると面白いと思います。
Next Step Hint 3: Firestoreを使ってDBの処理に慣れる
以下の記事がわかりやすそうでした。
React.js Firebase Tutorial: Building Firestore CRUD Web Application
シリーズ
SPA・サーバレスハンズオン part2 一般的なwebページとReactアプリの構成を知る
シリーズ
- part1: SPA・サーバレスハンズオン part1 React/Firebase Hostingで初めてのwebサイト公開 - 未熟学生エンジニアのブログ
- part3: HUITWeb講座 part3 React/Firebase Cloud Firestoreでチャットアプリを作る - 未熟学生エンジニアのブログ
Webページの主な構成要素
ブラウザでは主にhtml・css・javascriptの3種類のファイルを扱う
- HTML
- ページの構造を決める。ブラウザ処理の起点になり、必要なcssやjsはここから読み込む
- CSS
- ページのレイアウト・色などを設定する
- JavaScript(JS)
- ページを書き換える、計算や通信などをするプログラムを実行する
Reactとは
Reactは、状況に応じて動的に書きかわるようなページを書きやすくするためのJavaScriptフレームワークです。使えばなんとなくわかります。
なぜReactを使うのか?
こちらの記事にまとめました。気になる方は読んでみてください。
なぜReact.jsを使うのか、jQueryやVue.jsとの違いをいまさら調べてみた - 未熟学生エンジニアのブログ
create-react-appのデフォルトコードについて
create-react-appをした場合、テンプレートコードが生成されます。
src、publicフォルダの中にあるコードが重要です。
$ tree src src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js
$ tree public public ├── favicon.ico ├── index.html └── manifest.json
起点はsrc/index.jsです。create-react-appで作ったreactアプリは、まずこのindex.jsが実行されます。
src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render(<App />, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
ここで、以下の部分でAppというモジュールがインポートされています。要は、他のファイル(./App.js)にあるAppというモジュールをこのindex.jsでも利用できるようにしているということです。
import App from './App';
そして、以下の行でAppを利用して処理を走らせます。
ReactDOM.render(<App />, document.getElementById('root'));
これは、Appをrootというidを持つDOM要素の中に挿入するという意味になります。どういうことでしょうか?
実は、public/index.html というwebページのベースになるHTMLファイルがあります。
Appは、以下のテンプレートHTMLの中の <div id="root"></div>
の<div id="root">
と</div>
の間に挿入されます。
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This HTML file is a template. If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> </body> </html>
Appが、<div id="root">
と</div>
の間に挿入されることがわかりました。
では、Appとはなんなのか?その答えはsrc/App.jsにあります。なぜここに答えがあるとわかったと思いますか?なぜsrc/App.jsというファイル(答え)を探しあてることができたと思いますか?順をおって説明します。
React.jsを使ったプログラムは、基本的には単にjavascriptで書かれたプログラムです。javascriptがわかっていれば、答えにたどり着くことができます。
index.jsでのAppのインポートの方法を思い出してください。
src/index.js
import App from './App';
この構文は、「./App(./App.js)というファイルの中から、Appというモジュールをインポートする」という意味になります。
つまり、Appは./App.jsの中にあるということがわかり、Appの詳細を知りたければ./App.jsというファイルをみればいいということがわかります。これはReact.js特有の話ではなく、javascript一般に通じる話です。
ちなみに、./App.jsは、src/App.jsという意味です。(src/index.jsファイルの中では、.はsrcを意味します。ただし、これはファイルパスの一部として.が使われたときに限った話で、例えば先ほど出てきた以下のような例の場合は、.は別の意味になります。)
src/App.jsをみればいいということがわかったので、見ていきましょう。
src/App.js
import React from 'react'; import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;
若干語弊はありますが、AppはApp()のことだったと考えてください。
ここで、App()は返り値として<div>
で囲まれたオブジェクトを返しています。
実は、この<div>
で囲まれた部分はreactのコード上ではjsのオブジェクトです。これを変換して、最終的にはHTMLの<div>
タグと同じ意味のものになります。
ここで一度、ターミナルからサーバーを立ち上げて、ブラウザを見てみましょう。
$ npm start
以下のような画面が表示されるはずです。
ページを書き換えてみる
では、"Edit src/App.js and save to reload."を"Hello, my original app!"という文面に変えるには、どうしたらいいでしょうか?
先ほどのsrc/App.jsに、"Edit src/App.js and save to reload."という文字があるはずです。これを変えましょう。
src/App.js
<p> Edit <code>src/App.js</code> and save to reload. </p>
以下のように変えます
<p> Hello, my original app! </p>
編集して保存したら、ブラウザをみてみましょう。(実は、Reactを使うとブラウザのリロード操作がいりません!自動で更新されます。)
やりました!これでReactアプリを自分好みに編集していくことができるようになりました!
まとめ
- javascriptの文法を読み解き、編集すべきファイルsrc/App.jsにたどり着けた
- src/App.jsを編集すれば、自分のオリジナルアプリを書いていける
- ベースとなるファイルindex.htmlに、Appというコンポーネントを挿入する形で要素を追加している
シリーズ
なぜReact.jsを使うのか、jQueryやVue.jsとの違いをいまさら調べてみた
巷の人はなぜReact.jsを使っているのか理解するため、他のJavaScript(js)ライブラリ・フレームワークとの違いなどを調べてみました。
最初にまとめ
- SPAというページの一部を更新するタイプの構成が流行りだよ
- Reactは仮想DOMを使うからページの一部を更新するのが速い
- Reactは宣言的記述ができるから複雑なコードでも読みやすい。間違いにくい
- jQueryは複雑になると読みにくい
- 個人開発ではVue.jsとReact.js好きな方を使えば良い
- Vue.jsも上の同じ特性を持ってるよ
なぜjsが必要なのか?
そもそもjsはなぜ必要なのでしょうか?答えは単純です。ユーザのアクション(入力・クリックなど)やDBのデータ更新などに応じて動的にページを書きかえたいからです。
SPAを作りたい
SPA(シングルページアプリケーション)ではページ遷移を行わず、ページの遷移・更新はjsによるdom(jsで操作できる、HTMLを表現するオブジェクト)操作による差分更新により擬似的に行うという特徴があります。これによりサーバーからのリクエストを最初の一回だけで全てやってしまうのでその後の動作が軽量になるというメリットがあります。
近年はこのSPAの導入によるwebページのパフォーマンスアップが常識になってきているということがReact.js導入の動機となっています。
ここで、なぜSPAならReact.jsが使われるのか、という話になると思います。先ほど述べたようにSPAはページの遷移・更新はjsによるdom操作による差分更新により擬似的に行います。
Reactは、この差分更新を適切にやってくれる+更新処理をわかりやすく書けるため、SPAのweb開発で用いられているのです。以下では、その仕組みについてお話しして行きたいと思います。
参考
仮想DOMという概念
Reactでは、仮想DOMという方法を使い、今までのDOM操作をより効率よく行うことで高速化を実現しています。
ともかく、仮想DOMというものを使ってそこを経由してDOM操作を行うことで、高速になるらしいです。詳しいことはわかりませんが、そういうことです。
ページの一部だけを更新するのがJavaScriptの基本の処理なので、そこが早くなるのは当然嬉しいというわけですね。要は高速な処理ができるからReactはいいぞということ。
参考: VirtualDOMの仕事ってなに?(Reactの表示速度がはやい理由) - Qiita
従来:jQueryなどの手続き型のDOM操作
React.jsでは「宣言的記述」がキーワードとしてあります。jQueryなどの手続き型の記述と違っています。
「宣言的記述」では、テンプレートに変数を書いておき、その変数の値の変更に応じて自動的にレンダリングされます。
「手続き型の記述」では、DOMを直接操作し、例えばリストの値を一つ追加する際には以下の様な手続き型のプログラムを書きます。
「宣言的記述」の例
引用:Reactを使うとなぜjQueryが要らなくなるのか - Qiita
// ItemListのコンポーネント定義(実体は関数) const ItemList = props => { return <ul className="item-list"> {props.items.map(item => <ItemDetail item={item} />)} </ul>; }; // ItemDetailのコンポーネント定義 const ItemDetail = props => { const item = props.item; return <li className={'item' + item.stock === 0 ? ' soldout' : ''}> <div className="item-name">{item.name}</div> <div className="item-price">{item.price}</div> </li>; }; fetch('/api/items').then(res => res.json()).then(data => { ReactDOM.render( <ItemList items={data.items} />, // これを document.getElementById('container') // ここにレンダリングしろ ); });
「手続き型の記述」の例
引用:Reactを使うとなぜjQueryが要らなくなるのか - Qiita
$.getJSON('/api/items').then(data => { const ul = $('ul.item-list').empty(); data.items.forEach(item => { const li = $('<li>').addClass('item').appendTo(ul); if (item.stock === 0) li.addClass('soldout'); $('<div>').addClass('item-name').text(item.name).appendTo(li); $('<div>').addClass('item-price').text(item.price).appendTo(li); }); });
一見、宣言的記述の方が記述量が多いだけのようにも思えるかもしれませんが、宣言的記述では見た目がほぼHTMLで、正しい構造であることを確認しやすいのです。
手続き型の記述の場合、とてもじゃないですがHTMLには見えません。複雑なコードになった際、正しい構造であることを確認しづらくなり、間違ったHTMLタグを混入させてしまう可能性などがあり、バグを生みやすくなります。
こういった点で宣言的記述が有利であり、複雑化しやすいフロントエンドのコードを管理しやすくしています。
類似フレームワークVue.jsとの違い
他のフレームワークとの比較 — Vue.js によると、Vue.jsとReact.jsとの違いはそれほど大きくなさそうです。
大きな違いとしては、htmlの書き方にあります。ReactはJSX、VueはTemplatesという書き方を利用することになります。
我らがReact様のJSXのメリットとしては、JSXはhtmlのように見えるjsであり、jsとしてhtmlを書くことができることです。ただし、見た目はHTMLとは少し違いますし、CSSもJSXに組み込むように書くことが多いので、慣れていない人にとっては慣れるまで時間がかかります。
VueのTemplatesのメリットとしては、HTML・CSSはほぼ普通のそれと同じように書くことができる点です。とにかく、とっつきやすい。
[React] JSXの例
引用:ReactとVueってどう違う?全く同じアプリをReactとVueで作成してみて分かった相違点 | コリス
createNewToDoItem = () => { this.setState( ({ list, todo }) => ({ list: [ ...list, { todo } ], todo: '' }) ); }; handleInput = e => { this.setState({ todo: e.target.value }); }; <input type="text" value={this.state.todo} onChange={this.handleInput}/>
[Vue] Templatesの例
引用:ReactとVueってどう違う?全く同じアプリをReactとVueで作成してみて分かった相違点 | コリス
<script> createNewToDoItem() { this.list.push( { 'todo': this.todo } ); this.todo = ''; } </script> <template> <input type="text" v-model="todo"/> </template>
個人開発においてはどちらを使っても大差はないでしょう。
参考
Flutterメモ 番外編 GitHubとCIツールの連携(Codemagic)
今回やること
Codemagicを選んだ理由
CIツールを使ってみたかったんですが、調べてみるとCodemagicというCIツールが評判がいいらしい。どうやらFlutterに特化しているらしく、競合(Flutterにおいては後発っぽい)のBitriseよりもいろいろ充実してるっぽい。
codemagicの最強なところはSlackからビルド済みのアプリをインストールできること。
— shogo.yamada@Flutterマン (@yshogo87) 2019年4月17日
ビルドの遅さだけ解決してくれたら絶対こっち使う pic.twitter.com/fd8if54qgg
ただ、ビルドが若干遅いらしく、対応中とのことらしいです。
codemagicのiOSのビルドが遅い問題、今最優先で対応してるらしい。
— shogo.yamada@Flutterマン (@yshogo87) 2019年4月18日
ビルドが遅い以外は使い心地が最高なので、ここだけ改善されればずっと使うと思う。 pic.twitter.com/PZZvGO3mxV
今回はこのCodemagicの方を試してみることにしました。
GitHubにリポジトリを用意
今回はこのリポジトリを用意しました。今回はPublicリポジトリですが、CodemagicはPrivateでも使えるっぽいです。
https://github.com/TetsuFe/spring_app_2019github.com
Codemagicへの登録
以下のリンクから登録します。
GitHub連携認証を使ったのですが、登録が完了すると同時に以下のような画面に。
CIを導入したいリポジトリに対して「Start your first build」ボタンを押すと、なんといきなりビルドが始まりました!
簡単すぎる・・(他のCIツールもそうだったかも?)
トリガー
このままでは手動でCIを回さないといけないので、トリガーを設定します。
App Settingsから、Build Triggerを展開します。
以下のように設定できます。
- ブランチ名
- タイミング
- push時(Trigger on every push)
- artifacts(appの生成)はあり
- pull requestのupdate時(Trigger on pull request update)
- artifacts(appの生成)はなし
- push時(Trigger on every push)
また、説明は割愛しますが、走らせる処理をブランチごとに分岐させたい時は、workflow自体を複数作ることで可能らしいです。
参考:CodemagicでFlutterアプリのビルド・配信をする — iOS編 – Flutter 🇯🇵 – Medium
この記事の「トリガー」を参照してください。
slack連携
設定画面(App Settings?)を開いて、publishの項目を展開すると、以下の様にslack連携の項目が現れます。
連携に成功すると、以下の様にチャンネルと通知のトリガーを選択します。
次回のビルドから、以下のようにslack通知が来るようになります!ビルドは結構時間がかかるので、通知を設定しておくと便利そうです。
Adhoc配信
Adhoc配信自体が正直よくわかっていませんが、とりあえずやってみます。
App Settingsから、publishの項目を展開し、さらに「iOS code signing」を展開します。ここのフォームにアカウント情報を登録します。今回はAutomaticを選択します。
もう一度「Start new build」してみます。
slackの通知にipaファイルが追加されています!
iPhoneからリンクをクリックしてみると・・(slackからsafariへ移動しましょう)
インストール確認ダイアログが!
ホーム画面にインストールされました!
ちゃんと起動しました!!簡単すぎる…
このリンクを配布すればいろんな人にダウンロードしてもらえるのでかなり便利です。
GitHubのReadmeにバッジをつける
README.mdにここにあるmarkdown用のスニペットを追加してコミットするだけです。
いい感じです!
参考
Flutterメモ 2. 開発環境の構築(Android)
Flutter環境構築、今回はAndroid編です。
Flutter sdkがインストール済みであることが前提です。よろしければこの記事をご参照ください。
Android Studioのインストール
以下からインストールしましょう。
android用の build tool, ライセンスをインストールする
$ flutter doctor --android-licenses
Android StudioからFlutterプロジェクトを作ってみる
Android Studioを起動して、まずは Android Studio > Preference > Plugin と進み、以下の2つのプラグインをインストールしましょう。
- Dart
- Flutter
成功したら、Android Studioを再起動しましょう。
再起動後は、Start a new Flutter project から、Flutter プロジェクトが作成できるようになっています。
project nameに適当な名前を入れます。
company domainには一意なドメインを入れます。特に公開を考えていない場合はexample.comなどで大丈夫だと思います
実行
Android Studioから、エミュレータを切り替えることができます。iOSデバイスも選択することができます。
Android上でサンプルアプリが起動しました!(デフォルトでカウンターアプリが実装されています)
iOSも実行できます。
参考
Flutterメモ 1. 開発環境の構築(iOS)
flutterの環境構築
Android版の記事もあります。
この記事を読んでできること
- flutter sdkをインストールできる
- flutter コマンドが使える様になる
- Xcodeツールのインストール
- コマンドからのflutterアプリの作成
- flutterアプリをiOS Simulatorで動かす
環境
flutter sdkのインストール
https://flutter.io/setup-macos/ これに従ってインストールしました。
現在は少し変わっている様なので、こちらを参照した方が良いかもしれません。 MacOS install - Flutter
まずは上のサイトから、sdkをダウンロードしましょう。Downloads以下にダウンロードされるはず。
/Users/xxx/development に移動させます。ターミナルを開きましょう。
$ mkdir /Users/xxx/development $ cd /Users/xxx/development $ mv /Users/xxx/Downloads/flutter .
flutter コマンドを使える様に、パスを通します。
~/.bash_profile
#flutter export PATH=/Users/xxx/development/flutter/bin:$PATH
$ source ~/.bash_profile
flutter docker
flutter doctorというコマンドで、うまくインストールできているか確認することができます。
$ flutter doctor
こんな感じならOKです。
Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel dev, v1.2.1, on Mac OS X 10.14.4 18E194d, locale ja-JP)
xcode用ツールのインストール
$ brew install --HEAD libimobiledevice $ brew install ideviceinstaller $ brew install ios-deploy $ brew install cocoapods # cocoapodsをインストール済みの場合は、brew upgrade cocoapods $ pod setup
flutter doctorで確認します。開発環境がちゃんと整っているか確認できるのは嬉しいですね。
$ flutter doctor
こんな感じならOKです。
Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel dev, v1.2.1, on Mac OS X 10.14.4 18E194d, locale ja-JP) [✓] iOS toolchain - develop for iOS devices (Xcode 10.2.1)
プロジェクト(アプリ)の作成・起動
$ flutter create first_sample
出力
(前略) All done! In order to run your application, type: $ cd first_sample $ flutter run Your main program file is lib/main.dart in the first_sample directory.
上のガイドにしたがって、以下のようにしてFlutterアプリを起動します。
$ cd first_sample
$ flutter run
Launching lib/main.dart on iPhone 8 in debug mode... Starting Xcode build... ├─Assembling Flutter resources... 2.0s └─Compiling, linking and signing... 13.2s Xcode build done. 18.0s Syncing files to device iPhone 8... 6.4s 🔥 To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R". An Observatory debugger and profiler on iPhone 8 is available at: http://127.0.0.1:58361/ For a more detailed help message, press "h". To quit, press "q".
ここまできたら、勝手にiOSシミュレータが起動して、サンプルアプリが起動します!(iOSシミュレータをインストールしていない人は、Xcodeからインストールしましょう)
ターミナル上で qを押すと、終了します。
SPA・サーバレスハンズオン part1 React/Firebase Hostingで初めてのwebサイト公開
シリーズ
- part2: SPA・サーバレスハンズオン part2 一般的なwebページとReactアプリの構成を知る - ヨシオ個人開発ブログ
- part3: SPA・サーバレスハンズオン part3 React/Firebase Cloud Firestoreでチャットアプリを作る - ヨシオ個人開発ブログ
part3までやるとできるもの
このようなチャットアプリが作れます。
デモページ: https://huit-tetsufe-0506.firebaseapp.com/ ソースコード: https://github.com/TetsuFe/huit_react_handson_20190506
事前準備
- gmailアカウントを一つ作っておくこと
大事なこと(講習会用)
- ついていけなくなったら周りの人にすぐ相談しよう
- それで解決しないときには発表者に相談する
- 参加者同士の交流を促すためで、発表者に質問をしないでほしいというわけではないです
1. 環境構築
参考 サーバーサイドエンジニアも知っておくべきフロントエンドの今
1.1. エディタインストール(VSCode)
ファイルを編集するためのエディタをインストールします。すでになんらかのエディタをインストールしている人は、改めてインストールする必要はありません。
今回はVSCodeを使います。VSCodeはMicrosoft社製の軽量なエディタで、stackoverflowのランキングでもSublime TextやAtomなどの類似エディタを抑えて一位にランクインしています。
Stack Overflow Developer Survey 2018
どれくらい便利かは使ってみればわかります。以下からダウンロードしましょう。
やらなくていいです:興味のある人向け
コードのフォーマットなどを気をつけたい場合は以下リンク等を参考にESLint・Pretitterなどを導入すると良いらしいです。
Prettier 入門 ~ESLintとの違いを理解して併用する~ - Qiita
1.2. Vscodeの統合ターミナルを開く(ターミナル・コマンドプロンプト・端末などでも良い)
インストールしたVS Codeを開きましょう。
そして、ctrl + shift + @ または Terminal > New Terminal からターミナルを起動します。
起動したら以下のように出てきます。(緑色の部分がターミナル)
1.3. homebrewインストール(mac)
windowsやlinux系OSを使っている人は飛ばしてください。
macOSの人は、homebrewというツールをインストールします。これは、後にnode.jsをインストールするために使います。https://brew.sh/index_ja に書いてあるコマンドをターミナル上にコピー&ペーストして、実行します。以下のような感じです。
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
以下、$ から始まるものはターミナル上で実行してください。
1.4. node.jsのインストール
$ brew install node
windowの人は、このリンクからインストールしてください。 Node.js
node.jsとnpmというパッケージ管理ツールがインストールされます。
- node.js
- javascript実行環境。
- 実行環境とは、簡単に言うとプログラムを解釈し、実行してくれるプログラム。
- この環境で動く便利ツールが多いから使うという印象。
- Node.js - Node.jsはなぜインストールする必要があるの?|teratail
- npm
- node.jsで使えるパッケージを管理するためのツール。今回使うreactなど、便利なツールをインストールしたりアンインストールしたりするために必要。
- npm (パッケージ管理ツール) - Wikipedia
- npmはpipなどに似たパッケージ管理機能だけでなく、スクリプトのエイリアスとしても使えます。その例が以下に出てくる
$ npm start
や$ npm build
です。これらの定義はpackage.jsonに書かれています
要は、reactやその他jsライブラリを使った開発を快適に使うための環境をセットアップしたと思ってください。
1.5. chromeのインストール(開発用ツール)
人によって動作しないということをなくすために、Chromeをインストールしてください。本当はバージョンも合わせるべきですが面倒なのでそこまではやりません。
2. 初めてのReactサイト作成
2.1. とりあえずhello, world
まずはとりあえずサンプルプログラムを動かしてみましょう!
今回は公式インストールガイド Create a New React App – React にしたがって、create-react-appを使います。
ターミナルを開いて、以下を実行します。
$ npm -v 6.7.0 $ node -v v8.11.4 $ npx create-react-app huit_handson_20190506
出力
We suggest that you begin by typing: cd huit_handson_20190506 yarn start Happy hacking!
こんな感じに出たら、以下のようにターミナル上でコマンドを実行します。
$ cd huit_handson_20190506 $ npm start
これでReactアプリケーションが実行できました! $ npm start
というコマンドを打つと、Reactアプリケーションのサーバーが起動します。これにwebブラウザからアクセスすれば、webページとして見ることができます。
Chromeを開いて、localhost:3000 と入力しましょう。(自動的に開く場合はしなくてもよいです)
localhost:3000 はlocalhostが自分のサーバーの名前で、3000はポート番号のことです。後に説明しますが、localhost:3000 とURLバーに入力することで、ブラウザは僕らが実行しているReactアプリケーションを見つけ出すことができます。
うまくいけば、ブラウザ上に以下のような画面が表示されているはずです。$ npx create-react-app huit_handson_20190506
というスクリプトによってデフォルトのアプリケーションコードが生成されたので、今はこんな表示になっています。
まとめ
- create-react-appというツールを使って、reactアプリケーションを作った。
- npm start というコマンドを打つとreactアプリケーションサーバーが起動する。
- ChromeのURLバーにlocalhost:3000と入力するとwebページとして表示できる。
2.2 公開しよう
デフォルトのままですが、いきなり全世界公開してしまいましょう!
いきなり公開までやってしまうのには以下のような理由があります。
- コードが複雑でない方が、公開が簡単・失敗した時の原因が特定しやすい
- 先に公開までやっておけばローカル(自分のPC)で完成した時にすぐ公開できて、他の人に見てもらえる
2.2.1 webサイト公開の仕組み
今現在の方法は、自分のPCだけで完結しています。
後述するFirebase Hostingのようなサービスを使って外部のサーバーを使ってサイトをweb上に公開する場合、以下のようになります。
さらに詳しくするとこんな感じです。
以上の図から、webサイトを公開するためには、だいたい以下のような手順が必要になります。
- 1.サーバーを起動する
- 2.webサイトの要素をサーバーに置く
- 3.ブラウザが見つけ出せるようにする
2.2.2 Firebase Hostingを使う
今回は、公開のためにFirebase Hostingというサービスを使います。
firebase.google.com からアカウント登録しましょう。アカウント登録が終わったら、一旦ターミナルに戻ります。
まずは、reactアプリケーションを公開用のコンパクトなhtml・css・jsファイルに変換します。
ターミナルを見ると、以下のように表示されていて、何もキー入力を受け付けない状態かもしれません。これはサーバーが起動しているためです。
ctrl + c を押して一旦サーバーを停止すると、入力できるようになります。
入力できるようになったら、以下のコマンドを入力します。
$ npm run build
なんか色々出てきますが最後に以下のように出てくればOKです。
The build folder is ready to be deployed. You may serve it with a static server: yarn global add serve serve -s build Find out more about deployment here: https://bit.ly/CRA-deploy
次に、Firebaseのサーバーのセットアップをします。
コンソールに移動します。
「プロジェクトを追加」をクリックします
こんな感じに入力します。
プロジェクト名は自分の好きな名前にすればOKです。
「プロジェクトの作成」を押すと、プロジェクトが作成されます。
続いて、Firebase Hostingの初期設定をします。
このポップアップにしたがってターミナルに以下を実行します
$ npm install -g firebase-tools
終わったら、「次へ」をクリックします
このポップアップにしたがってターミナルに以下を実行します
$ firebase login
ログインできたら、
$ firebase init
今度は、処理の途中で入力することがあります。
Hostingを選択しましょう。(カーソルキーで切り替え、「半角」スペースで選択、エンターで入力を完了)
紐づけるプロジェクトを選択しましょう。これはカーソルキーで選択してエンターを押せばOKです。
最後に2つ聞かれますが、一つ目の質問には「build」、二つ目の質問には「y」を入力しましょう。(5/8修正:画像にはpublicとありますが、buildと入力してください)
ここでpublicと表示されていたとき、index.htmlが書き変わってしまうことがあるようです。なのでその際はindex.htmlの中に<div id="root"></div>
を追記してみてください。
これでfirebase hostingの初期セットアップは完了です!さっそくwebページを公開(デプロイ、deploy)しましょう!
$ firebase deploy --only hosting
以下のように出力が出ます。
=== Deploying to 'huit-tetsufe-0506'... i deploying hosting i hosting[huit-tetsufe-0506]: beginning deploy... i hosting[huit-tetsufe-0506]: found 1 files in public ✔ hosting[huit-tetsufe-0506]: file upload complete i hosting[huit-tetsufe-0506]: finalizing version... ✔ hosting[huit-tetsufe-0506]: version finalized i hosting[huit-tetsufe-0506]: releasing new version... ✔ hosting[huit-tetsufe-0506]: release complete ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/huit-tetsufe-0506/overview Hosting URL: https://huit-tetsufe-0506.firebaseapp.com
最後の Hosting URL: https://huit-tetsufe-0506.firebaseapp.com
を見てください。ここにアクセスすると、、、
あれ・・?
どうやら公開するディレクトリの設定が間違っていたようです。
この設定を変更するには、reactアプリフォルダ内に作られた設定ファイル「firebase.json」を編集すればOKです。
VS Codeでreactアプリのフォルダを開いて、編集できるようにしましょう。
Open Folderから、reactアプリのフォルダを選択しましょう。
ターミナルを開き直しましょう。(ctrl + shift + @ または Terminal > New Terminal)
左の部分を左クリックして、New Fileから、firebase.jsonを作成
以下のように書いて保存します。
{ "hosting": { "public": "build" } }
ここにbuildとするのは、先ほど $ npm run build
で生成されたコンパクトなhtml・css・jsファイル群のフォルダ名がbuildだからです。
VS Codeのフォルダツリー(左側に出ているやつ)をみれば、buildフォルダがあることがわかると思います。
参考:(初心者向け)Firebase HostingへReactプロジェクトを公開する手順 - Qiita
次に、以下のようにしてこのターミナル上で設定をもう一度行います。(ここの説明は割愛します)
$ firebase use --add
その後、もう一度公開してみましょう。
$ firebase deploy --only hosting
終わったら、https://<自分の設定した名前>.firebaseapp.com/ にアクセスすると、今度こそ作成したReactページが表示されるはず!
2019/06/15 追記:これでうまくいかない場合は、firebase initをした時などにindex.htmlがfirebaseによって書き換えられていることが原因かもしれません。$ npm run build
をもう一度行い、index.htmlを最新の状態にしましょう。
Firebase hostingは自動でURLを発行してくれる(自動DNS設定)
Firebase hostingは自動でURLを発行してくれる ことがわかったと思います。
これは、先に説明したDNSの設定を自動で行ってくれていると言い換えられます。
実際にはあるIPアドレスを持ったサーバー上にreactアプリのファイルがあるという状態になっています。
これによって、IPアドレスという覚えにくい名前ではなく、ドメイン名というわかりやすい名前でユーザからアクセスしてもらえることができるようになっています。
また、ターミナルでnslookupコマンドを実行すると、ドメイン名とIPアドレスの対応を調べることができます。
$ nslookup huit-tetsufe-0506.firebaseapp.com
出力
Server: 192.168.0.254 Address: 192.168.0.254#53 Non-authoritative answer: Name: huit-tetsufe-0506.firebaseapp.com Address: 151.101.1.195 Name: huit-tetsufe-0506.firebaseapp.com Address: 151.101.65.195
Addressという項目がIPアドレスです。ちなみに、IPアドレスが複数あるのは、サーバーの数を複数台にしてアクセスの際にどちらにもアクセスできるようにすることで、負荷を分散させるためらしいです。
やってみよう:IPアドレスを直接ブラウザに打ち込んだらどうなる?
繋がるか繋がらないか自分の頭の中で一度考えてから、やってみてください。
まとめ
- firebase hostingを使えばサイトをwebに公開できる
- firebase hostingは主にターミナルでコマンドを実行して設定する
- firebase hostingは自分のHTMLファイルなどを公開サーバーにおくことができる
- firebase hostingは勝手にそのサーバのIPアドレスとドメイン名をDNSに登録してくれる
- わかりやすい名前でクライアントから見つけてもらえるように設定してくれる
参考
- Deploy React.js app on Firebase. QUICK GUIDE: React.js + Firebase 🔥 | by Julien Rioux | Medium
- https://firebase.google.com/docs/hosting/quickstart?hl=ja
- (初心者向け)Firebase HostingへReactプロジェクトを公開する手順 - Qiita