20210511のReactに関する記事は7件です。

Nest.jsとPostgresを使って郵便番号検索フォームを作成

前回はデータベースに全国郵便番号データをCSV形式で入れる事ができたので、今回は実際にサーバーで郵便番号が検索できるフォームを作成します。前回の記事でPostgres(データベース)を確認してみるとencoding collate ctypeがen_US.UTF-8になっていて、ログ確認時にブラウザで文字化けを起こしている挙動を得ました。なので、新たにaddressというデータベースを作りエンコーディングをja_JP.UTF-8に書き換えることにしました。 create database address template template0 ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF-8' LC_CTYPE = 'ja_JP.UTF-8'; 同時にテーブルも作り直し、以下の通り作成し直しました。 Table "public.address" Column | Type | Collation | Nullable | Default ----------------+-------------------+-----------+----------+--------- post | character(21) | | not null | prefectures | character varying | | not null | municipalities | character varying | | not null | address | character varying | | not null | \COPYでCSVファイルをデータベースに保存し、select * from address;で確認。 post | prefectures | municipalities | address --------------+-------------+--------------------+---------------------------------------------- 600000 | 北海道 | 札幌市中央区 | 以下に掲載がない場合 640941 | 北海道 | 札幌市中央区 | 旭ケ丘 600041 | 北海道 | 札幌市中央区 | 大通東 600042 | 北海道 | 札幌市中央区 | 大通西(1~19丁目) 640820 | 北海道 | 札幌市中央区 | 大通西(20~28丁目) 600031 | 北海道 | 札幌市中央区 | 北一条東 600001 | 北海道 | 札幌市中央区 | 北一条西(1~19丁目) 640821 | 北海道 | 札幌市中央区 | 北一条西(20~28丁目) : 結局、ブラウザ確認時の文字化けは解消されずでしたが。細かな検証は今後の課題として、進んで行きたいと思います。今回は、React(JavaScriptのライブラリ)のSSR(サイバーサイドレンダリング)を可能にするJavaScriptのフレームワークであるNest.jsを導入し実装していきたいと思います。node.jsがインストールされていない方はこちらをインストールして下さい。チュートリアルに従って、ターミナルから次のコマンドを実行します。 npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter" ディレクトリであるNESTJS-BLOGが作成され、各パッケージが入りました。 続けてIDE(統合開発環境)を整えます。開発時からエラーに気づけるように設定を書く為、タッチコマンドでtsconfig.jsonを入れ、npmやtypescriptなどのパッケージをsave-devの方にインストール。一度npm run devを実行します。 touch tsconfig.json npm install --save-dev typescript @types/react npm run dev の準にターミナルで実行すると、以下の画面になり、 http://localhost:3000で確認すると、無事ローカル上で挙動を確認できました。 ここまで来たら、いよいよ本題であるページのレンダリング(データをもとに表示内容を作る)やルーティング(pagesディレクトリ内のファイルの結び付け)、Reactフック(クラスを作成せずに、状態やその他のReact機能を使用を可能にするもの)を使ってコードを書いていきましょう。pagesディレクトリ内にapiディレクトリを作ってaddress.tsファイルを作成し記述していきます。まずはSequelizeを使用してデータベースを呼び出します。Sequelizeとは、Node.js上でORMを提供するライブラリです。以下のコマンドでインストールしましょう。 npm install --save sequelize 構文・継承を用いたclass 子クラス extends 親クラスでAddressとModelを定義し、データベースのテーブルに文字列で値を渡します。init()内に各テーブルの要素を入れます。Sequelizeでデータベースのカラムを変更せずModelを定義するために、主キーに対応するpost(カラム)にprimaryKey:trueを追加。createdAt,updatedAtを追加しないfreezeTableName:trueとtimestamps:falseを利用してテーブルのスキーマを現状の形で使えるようにします。郵便番号データをasyncで以下のように記述。 pages/api/address.ts import { Sequelize, Model, DataTypes } from 'sequelize'; const sequelize = new Sequelize({ database: 'address', password: null, dialect: 'postgres', port: 5432, host: 'localhost', timezone: 'Asia/Tokyo', }); class Address extends Model {} Address.init({ post: {type:DataTypes.STRING,primaryKey:true}, prefectures: DataTypes.STRING, municipalities: DataTypes.STRING, address: DataTypes.STRING }, { sequelize, modelName: 'address',freezeTableName:true,timestamps:false }); export default async(req, res) => { await sequelize.sync(); console.log(await Address.count()); const address = await Address.findOne({ where: { post: req.query['post'], } }); console.log(address) res.status(200).send(JSON.stringify(address)) } pagesディレクトリ内にあるindex.jsファイルをindex.tsxに書き換えてコードを以下のように記述します。 pages/index.tsx import Head from 'next/head' import React, { Props } from "react"; const Home = (props: Props) => { const [value, setValue] = React.useState(""); const [prefectures, setPrefectures] = React.useState(""); const [municipalities, setMunicipalities] = React.useState(""); const [address, setAddress] = React.useState(""); React.useEffect(()=>{ fetch('/api/address?post='+value) .then(response => response.json()) .then(data => { if(data !== null){ setPrefectures(data.prefectures) setMunicipalities(data.municipalities) setAddress(data.address)}}); },[value]) return ( <div> <h3>郵便番号を入力して下さい</h3> <label style={{ display: "block" }}> <input type='text' placeholder="半角数字で入力" maxLength={8} size={14} value={value} onChange={event => setValue(event.target.value.replace("-",""))} /> </label> {prefectures!=""&& <h3>住所</h3>} {prefectures!=""&& <div><label style={{ display: "block" }}> <input type='text' value={prefectures} onChange={event => setValue(event.target.value)} /><br/> </label> <label style={{ display: "block" }}> <input type='text' value={municipalities} onChange={event => setValue(event.target.value)} /><br/> </label> <label style={{ display: "block" }}> <input type='text' value={address} onChange={event => setValue(event.target.value)} /><br/> </label> </div>} </div> ) } export default Home; ターミナルで実行してみます。 nmp run dev http://localhost:3000/にて挙動を検証。 郵便番号を入力してみます! なんとか、それらしきものが出来上がりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React アプリを AWS EC2 にデプロイ

はじめに フロントエンド React + バックエンド Django REST Framework でアプリを作成したが、ローカル環境でしか動かすことができないので、AWS EC2 インスタンス上にデプロイして外部からもアクセスができるようにする。今回はフロントエンドである React アプリをデプロイすることとし、Web サーバーソフトウェアには、nginx を用いる。なお React アプリはすでに作成されており、コードは GitHub に push してあることを前提とする。React/Next.jsアプリケーションを作成し、AWS EC2を使って本番環境にデプロイするまでを参考にした。 EC2 インスタンスの作成 Amazon Linux 2 AMI (HVM), SSD Volume Type という無料利用枠対象のものを使用して作成した。OS は RHEL7 / CentOS7 をベースとしているらしい。セキュリティグループ設定で HTTP 設定の追加を忘れないこと。設定されていると、以下のようになっているはず。 その他インスタンス作成に関することはここでは割愛する。以下の作業はすべて作成した EC2 インスタンスに SSH 接続を行った状態での作業であるので注意。接続方法は SSH を使用した Linux インスタンスへの接続を参照。 nginx のインストールおよびセットアップ 以下コマンドで nginx をインストールする。 $ yum update -y $ sudo amazon-linux-extras install nginx1 以下コマンドで、nginx の自動起動設定を行い、起動する。 $ sudo systemctl enable nginx # 自動起動設定 $ sudo systemctl start nginx.service # 起動 この状態で、EC2 インスタンスの「パブリック IPv4 アドレス」または「パブリック IPv4 DNS」にアクセスして、問題なく接続できることを確認する。 次に、/etc/nginx/nginx.conf(一部抜粋)に以下を追記する。 /etc/nginx/nginx.conf http { ... server { ... location / { proxy_pass http://localhost:3000; } ... } } 上記でリバースプロキシの設定ができたので、このインスタンスに対するリクエストは localhost:3000 へ転送される。 設定を反映させるために、一度 ngnx を再起動しておく。 $ sudo systemctl restart nginx React アプリの起動 GitHub に push されているアプリをクローンして起動する。まず git をインストールして、レポジトリをクローンする。 $ sudo yum -y install git $ git clone https://github.com/<your repository> React アプリが動作する環境を作成するため、パッケージ管理ツールである npm をインストールする。(以下コマンドは Node.js をインストールしているが、合わせて npm もインストールされる。)インストール方法の詳細はNode.js Binary Distributions を参照。 $ sudo su - root # curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - # yum install -y nodejs アプリをビルドして、起動する。 $ cd <your repository> $ npm install # package.json に記載されたパッケージを一括インストール $ npm run build # ビルド実行 $ npm run start 再度 EC2 インスタンスに接続すると、アプリが表示されていることが確認できる。 おわりに フロントエンドのアプリを EC2 上にデプロイした。バックエンド側も合わせてデプロイしたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React チュートリアルを Hooks + TypeScript へリファクタリングする

はじめに React 公式チュートリアルの三目並べ (Tic Tac Toe) を React Hooks と TypeScript (以下、TS )でリファクタリングしてみます。 リファクタリングするにあたっては、以下のようなドキュメントが参考になると思います。 公式チュートリアルの最終結果 See the Pen Tic Tac Toe by Dan Abramov (@gaearon) on CodePen. 準備 create-react-app の TypeScript テンプレートを利用してプロジェクトフォルダを作成します。 bash % npx create-react-app qiita --template typescript Creating a new React app in /Users/sprout/Downloads/qiita. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template-typescript... ~ snip ~ We suggest that you begin by typing: cd qiita npm start Happy hacking! % cd qiita React Tutorial JS と React Totorial CSS コードをそれぞれ index.tsx, index.css という名前で src フォルダに配置します。 index.tsx の先頭に以下の3行を加えます。 index.tsx import React, { useState } from "react"; import ReactDOM from "react-dom"; import "./index.css"; 元は JavaScript で書かれたコードに tsx の拡張子をあたえているため、VSCode のようなコードエディタでプロジェクトを開くと以下のようにたくさんのエラーが表示されます。 これらのエラーを一つ一つ修正していくことをこのリファクタリングの方針とします。 手順 1. 末尾の calculateWinner 関数を冒頭に移動する calculateWinner 関数はコードの末尾に置かれていますが、TS では関数をその定義より前に呼び出すことはできないので冒頭に移動させます。 移動させるついでにアロー関数に書き換えます。 index.tsx import ReactDOM from "react-dom"; import "./index.css"; const calculateWinner = (squares) => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }; アロー関数に書き換えることで JavaScript の this にまつわる煩雑さが軽減されます。 2. squares の型を検討する calculateWinner 関数の引数 squares へ型を与えないといけません。 親コンポーネントである Game コンポーネントのステートを見ると、squares は9つの要素を持つ配列であると分かります。 index.tsx class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null), }, ], stepNumber: 0, xIsNext: true, }; } いっぽう Square 関数コンポーネントでは、三目並べの各マスに入る 'X' または 'O' ( string 型)もしくは何もなし( null 型)として props を受け取っています。 index.tsx function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } よって squares の型は string 型もしくは null 型の配列となります。 index.tsx type SquaresType = (string | null)[]; 配列の要素数を指定する型定義も可能ですが、やや難易度が上がるのでここでは参考記事を紹介するに留めます。 index.tsx const calculateWinner = (squares: SquaresType) => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }; 3. Square コンポーネントへの props の型を検討する Square 関数コンポーネントを SquareProps 型の props を引数とするコンポーネントとして定義します。 index.tsx const Square: React.VFC<SquareProps> = (props) => { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }; SquareProps の型定義を検討しなければいけません。 props.value はすでに見てきた通り、各マスへ与えられる 'X' または 'O' の string 型、もしくは null 型です。 props.onClick は、いまのところ引数も返り値もない関数であるように見えますので () => void という型定義を与えておきます。 index.tsx interface SquareProps { value: string | null; onClick: () => void; } 4. Board コンポーネントを関数コンポーネントへ書き換える クラスコンポーネントである Board を BoardProps 型の props を引数にとる関数コンポーネントに置き換えます。 index.tsx const Board: React.VFC<BoardProps> = (props) => { renderSquare 関数をアロー関数に書き換える クラスメソッドであった renderSquare をアロー関数に書き換えます。 また、すでにクラスではなくなったので this. は削除します。 index.tsx const renderSquare = (i) => { return ( <Square value={props.squares[i]} onClick={() => props.onClick(i)} /> ); } 引数の i は下の render 文から number 型であると判断できます。 index.tsx render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} renderSquare 関数の引数に number 型を付与します。 index.tsx const Board: React.VFC<BoardProps> = (props) => { const renderSquare = (i: number) => { return ( render メソッドを通常の return 文にする render() と this. を削除します。 index.tsx return ( <div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); 5. Board コンポーネントへの props の型を検討する BoardProps 型の定義が必要となりました。 renderSquare 関数から以下のことが分かります。 props.squares は SquaresType 型の配列である props.onClick は number 型の引数を取り、返り値はない index.tsx const renderSquare = (i: number) => { return <Square value={props.squares[i]} onClick={() => props.onClick(i)} />; }; 結果、BoardProps の型定義は以下のようになります。 index.tsx interface BoardProps { squares: SquaresType; onClick: (i: number) => void; } 6. Game コンポーネントを関数コンポーネントに書き換える これ以降は、親コンポーネントである Game コンポーネントを関数コンポーネントへ置き換えていきます。このリファクタリングでの一番の難所となるかも知れません。 とりあえず React 関数コンポーネントとして定義し、 index.tsx const Game: React.VFC = () => { 上の Board コンポーネント同様に以下の編集をおこないます。 render メソッドを通常の return 文にする this. を削る 7. Game コンポーネントのステートを useState フックに置き換える クラスコンポーネントではコンストラクタの中で初期化されていたステートの内容を検討します。 index.tsx class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null), }, ], stepNumber: 0, xIsNext: true, }; } stepNumber ステートが number 型、xIsNext が boolean 型であることは一目瞭然ですね。 useState フックの構文 const [foo, setFoo] = React.useState('bar'); foo: 現在のステートの値 setFoo: ステートを更新するメソッド 'set' + ステート(キャメルケース)とするのが通例 setFoo('boo') のようにしてステートを更新する useState: 引数はステートの初期値 現在のステートと、それを更新するための関数とをペアにして返す stepNumber ステートと xIsNext ステートを useState フックで初期化します。 index.tsx const [stepNumber, setStepNumber] = useState(0); const [xIsNext, setXIsNext] = useState(true); History 型を定義する コンストラクタでの state.history の初期設定を参照すると、 this.state = { history: [ { squares: Array(9).fill(null), }, ], history は、squares というプロパティを持つオブジェクトの配列で、squares プロパティの値は SquaresType 型であることが分かります。 squares プロパティをもつオブジェクトを History 型として定義します。 index.tsx interface History { squares: SquaresType; } history ステートは History 型オブジェクトの配列となるので、以下のように useState フックで初期化します。 index.tsx const [history, setHistory] = useState<History[]>([ { squares: Array(9).fill(null) } ]); Game コンポーネントのコンストラクタは削除します。 index.tsx const Game: React.VFC = () => { const [history, setHistory] = useState<History[]>([ { squares: Array(9).fill(null) } ]); const [stepNumber, setStepNumber] = useState(0); const [xIsNext, setXIsNext] = useState(true); 8. 各メソッドをアロー関数に書き換える 旧クラスメソッドをそれぞれアロー関数に書き換えます。 handleClick メソッド 引数 i が number 型であることは上の通り state. を削除 index.tsx const handleClick = (i: number) => { const history = history.slice(0, stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = xIsNext ? "X" : "O"; setState({ history: history.concat([ { squares: squares, }, ]), stepNumber: history.length, xIsNext: !xIsNext, }); } handleClick メソッドでの名前の衝突を解消する アロー関数にしたことでメソッド内の変数名がステートと重複してしまったので、これを解消していきます。 関数内では history ステートの配列を直接書き換えることは避けて、historyCurrent というコピーを操作する必要があります。 index.tsx const handleClick = (i: number) => { const historyCurrent = history.slice(0, stepNumber + 1); const current = historyCurrent[historyCurrent.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) return; handleClick メソッドでの setState 文を setHoge(value) へ変換する setHistory には、やはりコピーである historyCurrent を渡します。 index.tsx setHistory(historyCurrent.concat([{ squares: squares }])); setStepNumber(historyCurrent.length); setXIsNext(!xIsNext); 上を少しモダンに書くと下のようになります。 index.tsx setHistory([...historyCurrent, { squares }]); jumpTo メソッドをアロー関数に index.tsx const jumpTo = (step: number) => { setStepNumber(step); setXIsNext(step % 2 === 0); }; 9. Game (=親コンポーネント) での名前の衝突を解消する 各メソッド以外でもステートと変数名の衝突が起きているので、これらも解消していきます。 handleClick メソッドでの要領と同じ state. は削除する index.tsx const historyCurrent = [...history]; const current = historyCurrent[stepNumber]; const winner = calculateWinner(current.squares); map メソッド内の desc 変数や status 変数でもモダンな(?)記法を使います。 index.tsx const moves = history.map((_step, move) => { const desc = move ? `Go to move #${move}` : "Go to game start"; return ( <li key={move}> <button onClick={() => jumpTo(move)}>{desc}</button> </li> ); }); index.tsx const status = winner ? `Winner: ${winner}` : `Next player: ${xIsNext ? "X" : "O"}`; 10. ここまでの結果 See the Pen LYWpWWo by sprout2000 (@sprout2000_jp) on CodePen. 続編
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React DnD: v11.0以降の後方互換性を破るアップデート

React DnDは、Reactアプリケーションでドラッグ&ドロップを実現するライブラリです。公式の「Tutorial」にもとづいて1年前につくった作例が、少し前にライブラリをアップデートしたら動かなくなりました。昨年使ったライブラリがv10.0.2、最新はv14.0.2です。更新が頻繁なのは喜ばしいというべきでしょう。けれどその間、後方互換性を破る更新がいくつかありました。それらを書きとめておきます。 なお、v14.0に合わせてチュートリアルシリーズ「Create React App + React DnD」(全4回)を書きました。「Tutorial」の作例を実際につくってみたい方は、ぜひご覧ください(各回の解説の結びにCodeSandboxのサンプルを公開しています)。 HTML5Backendは名前つきでimportする まず、v11.0.0で、HTML5Backendがexport defaultでなくなりました。したがって、HTML5Backendは名前つきでimportしなければならないということです。 // import HTML5Backend from 'react-dnd-html5-backend' import { HTML5Backend } from 'react-dnd-html5-backend' 理由は、つぎのように説明されています。つまり、ライブラリの開発方針としてexport defaultはできるだけ使わないようにしたそうです。実際、公式「Tutorial」の作例も、コンポーネントのexportは基本的に名前つきになっていました。 In general, throughout the app we've minimized the usage of default exports as well. useDragとuseDropの引数はコールバック関数で仕様オブジェクトを返す v13.0.0では、useDragとuseDropの引数が、仕様オブジェクトでなく、仕様オブジェクトを返すコールバック関数になりました。そして、第2引数には依存配列を与えます。つまり、ReactのuseMemoと同じ構文になったのです。 これも、1年前の作例が動かなくなった理由かと思ったら、違いました。v13.1.0で、もともとの仕様オブジェクトを渡す構文も復活したからです。 useDrag(() => spec, [deps]) // useMemo型構文 useDrag(spec) // 復活 ただし、第2引数は残ります。けれど、省いて構いません。デフォルトの依存配列が用いられるからです。構文について詳しくは、「useDragフック」および「useDropフック」をご参照ください。 useDragで定める仕様オブジェクトのtypeプロパティは直下に置く 少しやっかいだったのは、v14.0.0で加えられたつぎの変更です。なにしろ「Breaking Changes」の表記がありません。内部的な改善のために、typeとitemを切り離したようです。 useDrag(() => { // item: { type: BOX } }, type: BOX, }) 以上を改めたところ、1年前の作例も動くようになりました(useDragとuseDropは、useMemo型の構文に書き替えました)。でき上がりはCodeSandboxに公開してあります。つくり方については、前出「Create React App + React DnD」(全4回)をお読みください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでドロップダウン(dropdown)メニューを実装する

経緯 Reactでドロップダウンメニューを実装する必要が出てきた。 ドロップダウンメニューを実装するには、領域外のクリックを検知する必要がある。 React上で領域外を検知する方法と、簡単にドロップダウンメニューを作る。 必要な知識 React React hooks Code import { useState, useRef, useEffect } from 'react' const Menu = () => { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(); useEffect(() => { document.addEventListener("mousedown", handleOutsideClick); return () => document.removeEventListener("mousedown", handleOutsideClick); }, []); const handleOutsideClick = (e) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { setIsOpen(false); } }; return( <> <div ref={dropdownRef} className="relative inline-block text-left"> <span className="rounded-md shadow-sm"> <button onClick={() => setIsOpen(!isOpen)} type="button" className="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" id="options-menu" aria-haspopup="true" aria-expanded={isOpen}> MENU <svg className="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#4B5563"> <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" /> </svg> </button> </span> {isOpen && ( <div className='absolute top-12 right-2 w-64 p-4 bg-white z-50 shadow-lg border border-gray-100 rounded'> <ul> <li><a>menu1</a></li> <li><a>menu2</a></li> <li><a>menu3</a></li> </ul> </div> )} </div> </> ) } export default Menu 仕組み useEffectで描画時に、イベントリスナーでmousedown(クリック)を登録しておく。 また、returnでイベントリスナーのクリーンアップも設定する。 mousedown時に、mousedownされた要素が返されるため、handleOutsideClickでその要素に、ドロップダウンメニューの要素が含まれるか判定する。(ドロップダウンメニューの要素はuseRefを使用して取得) 含まれる場合、それはドロップダウンメニュー上でのクリックなので、ドロップダウンメニューは表示し続ける。 含まれない場合、それはドロップダウンメニュー外でのクリックのためドロップダウンメニューを非表示にする。 今回は、要素を丸ごと消しているが、display: hidden;やdisplay: none;でも実装可能である。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】React.memoによる表示パフォーマンス改善

以前、コード分割によるReactの表示パフォーマンスの改善についてまとめました。 それに引き続き、今回はReact.memoが表示にどのような影響を与えるのかを検証し、その内容をまとめました。 React.memoとは React.memoは高階コンポーネント(あるコンポーネントを受け取って新規のコンポーネントを返すような関数)です。 あるコンポーネントに同じpropsを与えられて同じ結果をレンダーするとき、そのコンポーネントをReact.memoでラッピングしてあげることで、結果を記憶させることができます。 Reactが記憶したレンダー結果を再利用することで、表示パフォーマンスを向上させることができます。 ただ、React.memoはパフォーマンス最適化のためだけの方法なので、レンダーを「抑止する」ために使用することは推奨されていません。 バグを引き起こす可能性があるので、下手に使わないほうがいいということですね。 ちなみに、Classコンポーネントで同様の実装を行うには、React.PureComponentやshouldComponentUpdate()というライフサイクルメソッドを使用します。 検証 Toggle PersonでPersonコンポーネントを表示させて、Increase CountでButton Countを1ずつ増やしていくようなケースを考えました。 PersonコンポーネントはAppコンポーネント内部にあります。 Appの作成 Personの親コンポーネントであるAppを作成します。 showPersonの状態(true/false)でPersonの表示を切り替えます。 App.js import React, useState from 'react'; import logo from './logo.svg'; import './App.css'; import Person from './person.component'; const App = () => { const initialState = { count: 0, person: { name: 'Jack', age: 22 }, showPerson: false, } const [state, setState] = useState(initialState) return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> {state.showPerson ? <Person person={state.person} /> : null} Button Count: {state.count} <button onClick={() => setState((prevState) => ({ ...prevState, count: prevState.count + 1, })) } > Increase Count </button> <button onClick={() => setState((prevState) => ({ ...prevState, showPerson: !prevState.showPerson, })) } > Toggle Person </button> </header> </div> ); } export default App; Personの作成 Personコンポーネントをつくります。 React.memo未使用 通常のデフォルトエクスポートを行います。 person.component.jsx import React from 'react'; const Person = ({ person }) => { console.log('rendering'); return ( <div> <p>{person.name}</p> <p>{person.age}</p> </div> ); }; export default Person; React.memo使用 PersonをエクスポートするときにReact.memoでラッピングします。 person.component.jsx import React from 'react'; const Person = ({ person }) => { console.log('rendering'); return ( <div> <p>{person.name}</p> <p>{person.age}</p> </div> ); }; export default React.memo(Person); 結果比較 Toggle Personを一回押し、Increase Countを何回か押してみたときの結果を比較しました。 Personのレンダリング回数 React.memoを使用しない場合、Toggle Personのあとに一回レンダリングされ、Increase Countを押すごとにレンダリングされていきます。 このように、親コンポーネントのAppのstateが変化したときに子コンポーネントも再レンダーされます。 一方、React.memoを使用した場合には、一度レンダリングされたら何回ボタンを押そうと再レンダリングされません。 このように、React.memoを使うと、Personのpropsの変更のみをチェックするため、親コンポーネントのAppのstateが変化してもPersonのレンダリングには影響がありません。 Personのレンダリング時間 React.memoを使用しない場合のPersonの初期レンダリング時間は0.8 msです。 一方、React.memoを使用した場合のPersonの初期レンダリング時間は0.9 msと、使用しない場合よりも長くなります。 メモ化に伴い、最初のレンダリングに関してはReact.memoを使用した方が時間がかかってしまいます。 このように、React.memoにはデメリットもあるので、表示速度をdev-toolで測りながら、必要なときだけ利用を検討していくのがベストらしいです。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactコンポーネントのテストケースをChromeでデバッグする

ReactというよりJestの機能ですが、Reactコンポーネントのユニットテストは、Chromeでデバッグできます。 1. テストケースでBreakPointを設定したい場所にdebugger文を書く 通常のデバッグと同じです。 expect(tree).toMatchSnapshot(); debugger // manually trigger the callback 2. デバッグオプション付きでJestを起動 $ node --inspect-brk node_modules/.bin/jest --findRelatedTests [テストケースへのパス] デバッガの接続待ちで停止する Debugger listening on ws://127.0.0.1:9229/4e252f35-9932-4144-ad7e-cd9c659a0b86 For help, see: https://nodejs.org/en/docs/inspector 3. ChromeのURLからchrome://inspectを入力 4. Target - inspectをクリックしてデバッガを立ち上げる 右上のResume script Executionで実行すると、BraekPointを設定した位置で停止する。 通常のデバッグと同じく、consoleから変数の内容の確認などができる 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む