20190719のReactに関する記事は8件です。

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

これでとりあえず初期画面が表示されました。簡単。
スクリーンショット 2019-07-19 15.53.45.png

コンポーネントを編集してみる

最初に表示されている画面のコードはsrc/App.tsxに記載されています。

App.tsx
import 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.tsx
import React from "react";

class App extends React.Component {
  render() {
    return <p>Hello world</p>;
  }
}

export default App;

スクリーンショット 2019-07-19 16.11.47.png

Hello worldが表示されるだけのシンプルな画面になりました。

別のコンポーネントを作成してApp.tsxにimportしてみます。

Sample.tsx
import React from "react";

class Sample extends React.Component {
  render() {
    return <p>2つ目のコンポーネント</p>;
  }
}

export default Sample;
App.tsx
import 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.tsx
import 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;

これで問題なく画面が表示されました。
スクリーンショット 2019-07-19 16.22.18.png

Propsを使ってみる

ReactにはPropsとStateという2つの変数が存在します。
ふたつの変数の違いについてはこちらの記事をどうぞ。
React における State と Props の違い
今からはじめるReact.js〜propsとstate、それからrefs〜

先程作成したSample.tsxにApp.tsxから変数を渡してみましょう。

App.tsx
import 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.tsx
import 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.tsx
import 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型であることを定義できました。
エラーも解消され、問題なく画面が表示されます。
スクリーンショット 2019-07-19 16.33.42.png

Stateを使ってみる

App.tsxを書き換えて、Stateを使って何かしら値を画面に出力してみます。
スクリーンショット 2019-07-19 16.45.26.png
スクリーンショット 2019-07-19 16.45.45.png
ボタンを押下されたら文言が変わるという単純な実装をしてみました。
JavaScriptを使ったReactならこのように書くと思います。

App.tsx
import 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.tsx
import 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〜

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

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


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

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 start

eslint設定

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-promise

eslint設定ファイル追加

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-19 14.34.03.png

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

create reat app 構築メモ

eslintの設定

インストール

https://www.bunkei-programmer.net/entry/2018/04/20/220156#Babel%E7%89%88eslint--prettier%E3%81%AE%E8%A8%AD%E5%AE%9A

自動修正

--fixをつけると自動修正

Intellijの設定

https://pleiades.io/help/idea/eslint.html
引数: --fix $FilePath$ では、自動修正されなかった
スクリーンショット 2019-07-19 14.34.03.png

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

【React accordion】cssだけでアコーディオンUIの実装

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

【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あり

リストに新しい値を末尾に追加する

Screen Shot 2019-07-19 at 0.31.22.png
末尾の<li>の開始タグが点滅している。これは当該の<li>タグが新たに追加されたことを意味する。
追加分のタグだけが再描画されているため、理想的な挙動である。

リストに新しい値を先頭に追加する

Screen Shot 2019-07-19 at 0.31.32.png
先頭の<li>の開始タグが点滅している。
理由は同上であり、理想的な挙動である。

リストに新しい値を真ん中に追加する

Screen Shot 2019-07-19 at 0.31.40.png
真ん中の<li>の開始タグが点滅している。
理由は同上であり、理想的な挙動である。

keyなし

リストに新しい値を末尾に追加する

Screen Shot 2019-07-19 at 0.32.14.png
末尾の<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> (新規挿入)

リストに新しい値を先頭に追加する

Screen Shot 2019-07-19 at 0.32.21.png
全ての<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> (新規挿入)

リストに新しい値を真ん中に追加する

Screen Shot 2019-07-19 at 0.32.27.png
挿入した箇所以降における<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を忘れないようにしよう。

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

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を動かす

まず今回やったことを図として示します。

Next.js on Firebase Hosting, Cloud Functions

ポイントは以下の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-app

yarnの方はこちら

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 deploy

Firebaseでは通常firebase deployでCloud Functionsへデプロイしますが、Next.jsを利用する場合はnpm runを利用した方が良さそうです。
package.jsonscriptsの定義を見ると、それぞれのコマンドの前に色々と処理が入っていることがわかります。

例えば、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

こうすることで次のことができるようになります。

  1. /で始まるパスのリクエストはNext.jsがコントロールする
  2. /_/で始まるパスのリクエストはREST APIがコントロールする

これでNext.jsとREST APIをCloud Functionsに共存できるようになりました✨

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