- 投稿日:2019-07-19T22:56:06+09:00
create-react-app --typescriptでejectした後に動かなかったとき
create-react-appを利用した際に、ejectしたときに動かず一瞬困ったので、記事として書いておきます。
といっても、記事にするほどのものでもなくすごくシンプルな対処です。
- create-react-app: v3.0.1
create-react-app my-app --typescriptまずこれでreactのアプリケーションが作れます。
さて、ejectしてstartしてみます。cd my-app yarn eject yarn startすると、私の環境では以下のようなエラーが出てきました。
Cannot find module '@babel/plugin-transform-react-jsx-source'解決方法は非常にシンプルですが、node_modulesを一旦全部消して再度インストールするだけです。
rm -rf node_modules yarn install
- 投稿日:2019-07-19T16:53:10+09:00
create-react-appを使ったTypeScriptのチュートリアル
Reactにまだ慣れていない、TypeScriptを今からはじめて触れる人向けに、簡単なチュートリアルを書きます。
この記事の他にもTypeScriptのチュートリアル記事はいくつかありますので、そちらも参考にどうぞTypeScriptチュートリアル① -環境構築編-
TypeScript チュートリアル今回はReduxや、Sagaなどのミドルウェアは一切使わずにとてもシンプルなアプリケーションを作ります。
環境構築
create-react-appを使えばReactの環境構築は簡単にできます。
https://github.com/facebook/create-react-app今回はTypeScriptを使うので下記のようにコマンドを入力します
npx create-react-app sample-app --typescript上のコマンドの
sample-appの部分がフォルダ名になります。
create-react-appで作成したフォルダに移動してアプリケーションを起動しますcd sample-app/ npm startコンポーネントを編集してみる
最初に表示されている画面のコードは
src/App.tsxに記載されています。App.tsximport React from "react"; import logo from "./logo.svg"; import "./App.css"; const App: React.FC = () => { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.tsx</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;Reactの開発は基本的にsrcフォルダ内に書いていきます。
まずはこのApp.tsxをもっとシンプルなものに書き換えます。App.tsximport React from "react"; class App extends React.Component { render() { return <p>Hello world</p>; } } export default App;Hello worldが表示されるだけのシンプルな画面になりました。
別のコンポーネントを作成してApp.tsxにimportしてみます。
Sample.tsximport React from "react"; class Sample extends React.Component { render() { return <p>2つ目のコンポーネント</p>; } } export default Sample;App.tsximport React from "react"; import Sample from './Sample' class App extends React.Component { render() { return ( <p>Hello world</p> <Sample/> ); } } export default App;これで立ち上げてみます。
Failed to compile. ./src/App.tsx Line 9: Parsing error: JSX expressions must have one parent elementエラーになりますね。
エラー内容の通り、jsxでは一つの親要素が必要となります。
<React.Fragment>で全ての要素を囲むことでこのエラーを回避できます。App.tsximport React from "react"; import Sample from "./Sample"; class App extends React.Component { render() { return ( <React.Fragment> <p>Hello world</p> <Sample /> </React.Fragment> ); } } export default App;Propsを使ってみる
ReactにはPropsとStateという2つの変数が存在します。
ふたつの変数の違いについてはこちらの記事をどうぞ。
React における State と Props の違い
今からはじめるReact.js〜propsとstate、それからrefs〜先程作成したSample.tsxにApp.tsxから変数を渡してみましょう。
App.tsximport React from "react"; import Sample from "./Sample"; class App extends React.Component { render() { return ( <React.Fragment> <p>Hello world</p> <Sample num={"5"} /> </React.Fragment> ); } } export default App;ここではnumという変数に"5"という文字列を代入しています。
Sample.tsximport React from "react"; class Sample extends React.Component { render() { return <p>{this.props.num}つ目のコンポーネント</p>; } } export default Sample;またエラーになりました。
Type '{ num: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Sample> & Readonly<{}> & Readonly<{ children?: ReactNode; }>'. Property 'num' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Sample> & Readonly<{}> & Readonly<{ children?: ReactNode; }>'. TS2322 7 | <React.Fragment> 8 | <p>Hello world</p> > 9 | <Sample num={"5"} /> | ^ 10 | </React.Fragment> 11 | ); 12 | }Typescriptでは型をちゃんと指定してあげなければなりません。
Sample.tsxを以下のように書き換えてみましょう。Sample.tsximport React from "react"; interface Props { num: string; } class Sample extends React.Component<Props> { render() { return <p>{this.props.num}つ目のコンポーネント</p>; } } export default Sample;これで変数numがstring型であることを定義できました。
エラーも解消され、問題なく画面が表示されます。
Stateを使ってみる
App.tsxを書き換えて、Stateを使って何かしら値を画面に出力してみます。
ボタンを押下されたら文言が変わるという単純な実装をしてみました。
JavaScriptを使ったReactならこのように書くと思います。App.tsximport React from "react"; import Sample from "./Sample"; class App extends React.Component { constructor(props) { super(props); this.state = { message: "最初のメッセージ" }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({ message: "ボタンが押されました" }); } render() { return ( <React.Fragment> <p>Hello world</p> <Sample num={"5"} /> <p>{this.state.message}</p> <button onClick={this.handleClick}>ボタン</button> </React.Fragment> ); } } export default App;しかしこれではエラーになってしまいますね。
Propsのときと同様に、TypeScriptでは型の指定が必要です。
下記のように書き換えましょう。App.tsximport React from "react"; import Sample from "./Sample"; interface Props {} interface State { message: string; } class App extends React.Component<Props, State> { constructor(props: Props) { super(props); this.state = { message: "最初のメッセージ" }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({ message: "ボタンが押されました" }); } render() { return ( <React.Fragment> <p>Hello world</p> <Sample num={"5"} /> <p>{this.state.message}</p> <button onClick={this.handleClick}>ボタン</button> </React.Fragment> ); } } export default App;これで問題なく画面が表示されました。
参考
TypeScriptチュートリアル① -環境構築編-
TypeScript チュートリアル
React における State と Props の違い
今からはじめるReact.js〜propsとstate、それからrefs〜
- 投稿日:2019-07-19T16:24:14+09:00
React Native Error: System limit for number of file watchers reached
React Native Error: System limit for number of file watchers reached
いつものようにyarn startでReactNativeアプリケーションを立ち上げるとこんなエラーが出た。あ〜も〜ReactNativeアプリをイチから再構築か?と諦めながら検索したらどちらかというとubuntuの問題であった。簡単な解決方法。しかしマイubuntuでいろいろ開発してるけどなぜにReactNativeで?の疑問は残る・・・
原因
システムによってモニタリングされているファイルが上限を超えた。
解決方法
$ sudo gedit /etc/sysctl.confでsysctl.confを開いて、ファイルの最終行に以下の一文を追加
fs.inotify.max_user_watches=524288そしたら保存。
そしたら次のコマンドで確認。
sudo sysctl -p以下のように出力されるはず。
$ sudo sysctl -p fs.inotify.max_user_watches = 524288これで無事にyarn startできた。
参考:React Native Error: ENOSPC: System limit for number of file watchers reached
- 投稿日:2019-07-19T13:11:11+09:00
create reat app 環境構築メモ
前の記事で書いたようにReduxはちょっとめんどうです。
業務ではRedux使っているものの、プライベートではReactのみでサイト作ろうと思います。簡単でした。
Reactのひな形を簡単に作れるcreate-react-appを利用します。導入
create-react-appコマンドをインストール、アプリ作成
npm install -g create-react-app create-react-app hello-worldサーバーを開始する
作ったアプリのディレクトリ(今回はhello-world)に行き以下のコマンドを実行
サーバーが起動し、http://localhost:3000 などのURLでアプリを見ることができます。npm starteslint設定
eslintとprettierのモジュールをインストール
yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier yarn add -D eslint-plugin-react eslint-config-react-app eslint-plugin-import eslint-plugin-flowtype eslint-plugin-jsx-a11y yarn add -D eslint-plugin-standard eslint-config-standard eslint-plugin-node eslint-plugin-promiseeslint設定ファイル追加
cat<<EOF > .eslintrc.json { "extends": [ "standard", "plugin:prettier/recommended" ], "plugins": [ "react", "prettier" ], "parser": "babel-eslint", "parserOptions": {}, "env": { "browser": true, "es6": true }, "globals": { "it": false }, "rules": { "prettier/prettier": "error", "react/jsx-uses-react": 1, "react/jsx-uses-vars": 1, "no-console": "warn", // warning Definition for rule 'jsx-a11y/href-no-hash' was not found に対応 "jsx-a11y/href-no-hash": "off", "jsx-a11y/anchor-is-valid": "off" } } EOF cat<<EOF > .eslintignore node_modules **/*.min.js src/registerServiceWorker.js EOF cat<<EOF > .prettierrc { "singleQuote": false, "trailingComma": "es5", "semi": false } EOF参考サイト
Intellijの設定
https://pleiades.io/help/idea/eslint.html
引数:--fix $FilePath$では、自動修正されなかった
- 投稿日:2019-07-19T13:11:11+09:00
create reat app 構築メモ
eslintの設定
インストール
自動修正
--fixをつけると自動修正
Intellijの設定
https://pleiades.io/help/idea/eslint.html
引数:--fix $FilePath$では、自動修正されなかった
- 投稿日:2019-07-19T12:29:30+09:00
【React accordion】cssだけでアコーディオンUIの実装
- 投稿日:2019-07-19T01:31:33+09:00
【React】DOMノードにkeyを設定して再描画を最適化する
概要
公式ドキュメントに記載の通り、ループ処理内で(V)DOMノードを描画する場合はkeyの設定が推奨され、設定が無ければWarningがコンソールに出力される。keyを設定する意義について再度ドキュメントを読み直して調べてみると、再描画時の差分検出を最適化するのに役立つと記載されている。この挙動を検証した結果を以下に書き記す。
ソースコード
See the Pen Usage of Key for ideal reconciliation by Takuya HARA (@takuyahara) on CodePen.
調査方法
描画される内容を全画面で確認するため、Debugモードのページへ移動してDeveloper Toolsを開く。Elementsタブを選択して操作対象の<ul>タグを展開し、<li>タグが表示された状態で各種ボタンをクリックすると、<li>タグの再描画が確認できる。keyありとkeyなしの場合で、それぞれどのように再描画が行われるかを確認する。
調査結果
keyあり
リストに新しい値を末尾に追加する
末尾の<li>の開始タグが点滅している。これは当該の<li>タグが新たに追加されたことを意味する。
追加分のタグだけが再描画されているため、理想的な挙動である。リストに新しい値を先頭に追加する
先頭の<li>の開始タグが点滅している。
理由は同上であり、理想的な挙動である。リストに新しい値を真ん中に追加する
真ん中の<li>の開始タグが点滅している。
理由は同上であり、理想的な挙動である。keyなし
リストに新しい値を末尾に追加する
末尾の<li>の開始タグが点滅している。
必要分のDOMノードの更新のみ行われているため、理想的な挙動である。これは描画済みのDOMツリーと再描画用のDOMツリーが以下のように比較されたからである。上から比較して<li>0</li>から<li>4</li>まで一致し、<li>5</li>のみ一致しない(描画済みDOMツリーに対応するノードが無い)ため、新規に挿入している。<li>5</li>はタグ自体が新たに描画されたため、開始タグが点滅している。
描画済み 再描画用 DOMツリー DOMツリー <li>0</li> <li>0</li> <li>1</li> <li>1</li> <li>2</li> <li>2</li> <li>3</li> <li>3</li> <li>4</li> <li>4</li> <li>5</li> (新規挿入)リストに新しい値を先頭に追加する
全ての<li>の開始タグ、および末尾以外の<li>タグ内の値が点滅している。開始タグとタグ内の値が点滅しているのは、タグ内の値が更新されたことを示している。
無用なDOMノードの更新が行われているため、非効率的な処理をしている。これは描画済みのDOMツリーと再描画用のDOMツリーが以下のように比較されたからである。上から比較して6つの<li>はタグ名が一致するがタグ内の値が異なるため値のみを更新し、開始タグとタグ内の値が点滅して示されている。<li>5</li>はタグ自体が新たに描画されたため、開始タグのみが点滅している。
描画済み 再描画用 DOMツリー DOMツリー <li>0</li> <li>6</li> (値を更新) <li>1</li> <li>0</li> (値を更新) <li>2</li> <li>1</li> (値を更新) <li>3</li> <li>2</li> (値を更新) <li>4</li> <li>3</li> (値を更新) <li>5</li> <li>4</li> (値を更新) <li>5</li> (新規挿入)リストに新しい値を真ん中に追加する
挿入した箇所以降における<li>の開始タグ、および末尾以外の<li>タグ内の値が点滅している。
無用なDOMノードの更新が行われているため、非効率的な処理をしている。これは描画済みのDOMツリーと再描画用のDOMツリーが以下のように比較されたからである。上から比較して、新しい値が挿入された4番目以降の<li>はタグ名が一致するがタグ内の値が異なるため値のみを更新し、開始タグとタグ内の値が点滅して示されている。<li>5</li>はタグ自体が新たに描画されたため、開始タグのみが点滅している。
描画済み 再描画用 DOMツリー DOMツリー <li>6</li> <li>6</li> <li>0</li> <li>0</li> <li>1</li> <li>1</li> <li>2</li> <li>7</li> (値を更新) <li>3</li> <li>2</li> (値を更新) <li>4</li> <li>3</li> (値を更新) <li>5</li> <li>4</li> (値を更新) <li>5</li> (新規挿入)考察
Reactの差分アルゴリズムにより、keyを指定しないと無用なDOMノードの再描画が行われることが分かった。フロントエンド側のチューニング手法の一つとして、ループ処理内で(V)DOMを描画する場合はkeyを忘れないようにしよう。
- 投稿日:2019-07-19T00:35:29+09:00
Firebase Cloud FunctionsでNext.js9を動かす方法
皆さんこんにちは、noriです。
FirebaseSummitのレジストレーションがオープンしましたね。僕は今年も参加しますよ!!
今年はスペインの開催ですねー
楽しみ。最近フロントの環境設定をする機会があったので、登場したばかりのNext.js 9をCloud Functionsにのせてみました。
Cloud Functionsにのせるところまでは、すぐだったんですがReactの扱いが初めてだった僕にとってつまりどころがあったので、同じことをしようとしている人たちのためにこの記事を書くことにしました。
先にサンプルコードを置いておきます。
https://github.com/1amageek/ballcap.ts/tree/master/examples
Firebase HostingとしてNext.jsを動かす
まず今回やったことを図として示します。
ポイントは以下の2つです。
- Next.jsをCloud FunctionsののせてSSR
- REST APIもCloud Functionsを利用する
Next.jsをCloud Functionsにのせる
実はこれはNext.jsのGitHubにサンプルが公開されているので全く難しくありません。
こちらのREADMEに従えば問題なく動作します。https://github.com/zeit/next.js/tree/canary/examples/with-firebase-hosting-and-typescript
※公開当日は動かなかったんですが、僕がPRを送ろうとした頃にすでに日本の方がコミットされてました。?
先こされた。。
https://github.com/zeit/next.js/commit/cdf4f0a20d71b365e69828942ca7bf69498fa825手順を簡単に説明します。
Next.jsからテンプレートを取得
次のコマンドでサンプルコードを取得することができます。
このサンプルコードはそのままプロダクトに転用可能なので今回はこれを使いました。npmの方はこちら
npx create-next-app --example with-firebase-hosting-and-typescript your-appyarnの方はこちら
yarn create next-app --example with-firebase-hosting-and-typescript your-appテンプレートを準備した時点でプロジェクトを実行可能になってます。次のコマンドで実行してみましょう。
npm run devこのコマンドは、Next.jsを開発モードで起動するコマンドです。任意のブラウザが起動し、サンプルコードが起動するのが確認できればOKです。
Cloud Functionsにデプロイ
プロジェクトはすでに起動することが確認できたのでデプロイすれば動くはずですね。
早速Firebase側の設定をやってみましょう。
.firebasercを変更してFirebase プロジェクトの設定をしましょう。
この設定をせずにデプロイするともちろんエラーが出ます。{ "projects": { "default": "YOUR_PROJECT" } }次のコマンドも忘れないように
firebase use defaultこれでFirebaseの設定も整いました。早速デプロイしてみましょう。
npm run deployFirebaseでは通常
firebase deployでCloud Functionsへデプロイしますが、Next.jsを利用する場合はnpm runを利用した方が良さそうです。
package.jsonのscriptsの定義を見ると、それぞれのコマンドの前に色々と処理が入っていることがわかります。例えば、
serveの実行前される、preserveでは次の処理が行われています。
- clean
- build-public
- build-functions
- build-app
- copy-deps
- install-deps
"scripts": { "dev": "next src/app", "preserve": "npm run clean && npm run build-public && npm run build-functions && npm run build-app && npm run copy-deps && npm run install-deps", "serve": "cross-env NODE_ENV=production firebase serve", "deploy": "npm run clean && firebase deploy", "clean": "rimraf \"dist\"", "build-app": "next build \"src/app\"", "build-app-permission": "chmod 755 dist/functions/next/static/media/*.svg", "build-public": "cpx \"src/public/**/*.*\" \"dist/public\" -C", "build-functions": "tsc --project src/functions", "lint-app": "tslint --project src/app", "typecheck-app": "tsc --project src/app", "lint-functions": "tslint --project src/functions", "copy-deps": "cpx \"*{package.json,package-lock.json,yarn.lock}\" \"dist/functions\" -C", "install-deps": "cd \"dist/functions\" && npm i" },他のコマンドの実行に関してもスクリプトを経由した方が安全かと思います。
デプロイに関してはfirebase.jsonに以下のように同じスクリプトが定義されているので実は安全に使えますが、念のためコマンドは入力方針は統一した方がいいでしょう。"functions": { "source": "dist/functions", "predeploy": [ "npm run lint-functions", "npm run lint-app", "npm run typecheck-app", "npm run build-functions", "npm run build-app", "npm run copy-deps", "npm run install-deps", "npm run build-app-permission" ] },デプロイしてFirebase HostingのURLを開ばNext.jsが動作していることが確認できるはずです。
Cloud FunctionsとFirebase Hosting
ここでCloud FunctionsとFirebase Hostingの関係性を説明しておきます。
Firebase Hostingは通常、静的コンテンツの配信を行うStatic Serverとして機能します。
Firebase Hostingで動的コンテンツの配信を行う場合はfirebase.jsonで設定の変更が必要です。"hosting": { "public": "dist/public", "rewrites": [ { "source": "**/**", "function": "nextApp" } ],テンプレートを利用してる時点で設定は終わっているので今回はこのまま利用可能です。
また、Cloud Functionsでは次のようになっています。
import * as functions from 'firebase-functions' import next from 'next' const dev = process.env.NODE_ENV !== 'production' const app = next({ dev, conf: { distDir: 'next' } }) const handle = app.getRequestHandler() export const nextApp = functions.https.onRequest((req, res) => { console.log('File: ' + req.originalUrl) return app.prepare().then(() => handle(req, res)) })これからわかるように、
httpsを通したリクエストは全てNext.jsにコントロールが奪われていることがわかります。Cloud FunctionsをREST APIにも対応させる
Cloud Functionsではexpress.jsを利用してPathをコントロールすることができます。
例えば次のようにconst express = require('express'); const cors = require('cors'); const app = express(); // Automatically allow cross-origin requests app.use(cors({ origin: true })); // Add middleware to authenticate requests app.use(myMiddleware); // build multiple CRUD interfaces: app.get('/:id', (req, res) => res.send(Widgets.getById(req.params.id))); app.post('/', (req, res) => res.send(Widgets.create())); app.put('/:id', (req, res) => res.send(Widgets.update(req.params.id, req.body))); app.delete('/:id', (req, res) => res.send(Widgets.delete(req.params.id))); app.get('/', (req, res) => res.send(Widgets.list())); // Expose Express API as a single Cloud Function: export const widgets = functions.https.onRequest(app);https://firebase.google.com/docs/functions/http-events?hl=ja
expressを使えばうまくいきそうですが、
exportに注目するとexport const nextApp = functions.https.onRequest((req, res) => { return app.prepare().then(() => handle(req, res)) })export const widgets = functions.https.onRequest(app);
functions.https.onRequestをいずれも占拠してしまっているため、共存できなさそうです。今回は次のように設定しました。
index.ts
import API from 'api' const dev = process.env.NODE_ENV !== 'production' const nextApp = next({ dev, conf: { distDir: 'next' } }) const handle = nextApp.getRequestHandler() export const hosting = functions.https.onRequest(async (req, res) => { await nextApp.prepare() const app = express() app.use('/_', API) app.get('*', async (req, res) => { console.log('File: ' + req.originalUrl) await handle(req, res) }) app(req, res) })app.ts
const app = express() app.post('/', (req, res, next) => { res.status(200).send("REST API") }) export default appこうすることで次のことができるようになります。
/で始まるパスのリクエストはNext.jsがコントロールする/_/で始まるパスのリクエストはREST APIがコントロールするこれでNext.jsとREST APIをCloud Functionsに共存できるようになりました✨













