- 投稿日:2020-03-26T23:35:36+09:00
react hook の useReactRouter の push でデータを渡す方法
<Button onClick={() => history.push({ pathname: '/page1', state: { aaa: "ここやで" }})}>const { location } = useReactRouter(); {location.state.aaa}
- 投稿日:2020-03-26T23:07:30+09:00
【TypeScript】Next.jsでqueryパラメータを取得する方法
- 投稿日:2020-03-26T21:48:55+09:00
正気を失わないReact導入
概要
ReactとTypeScriptが使いたいだけの記事です。
公式サイトのチュートリアルを見るとcreate-react-app
をするよう促されますが、ブラックボックスになっているのが嫌なので自力で環境構築をしていきます。npmの導入
npmを導入してください
npmのプロジェクトの作成
mkdir myReactSample
npm init
webpackの導入
以下の手順ではwebpackの公式チュートリアルを参考にwebpackの導入を進めます
webpackのインストール
コンソールnpm install --save-dev webpack webpack-cli
webpack
モジュールとwebpack-cli
モジュールを両方developerモードでインストールするということですね。多分。ファイルの作成
myReactSample |- package.json + |- /dist + |- index.html + |- /src + |- index.js
このような階層になるように
index.html
とindex.js
を作ります。index.htmlの内容を作成
dist/index.html<!doctype html> <html> <head> <title>Getting Started</title> </head> <body> Hello! </body> </html>
index.html
をこのようにします。
index.html
ファイルをブラウザで開くと、当然Hello!
と表示されます。index.jsの内容を作成
src/index.jsfunction component() { const element = document.createElement('div'); element.innerHTML = "Webpack!" return element; } document.body.appendChild(component());このようにします。
body
に<div>Webpack!</div>
を追加しているだけですね。index.htmlからmain.jsを読み込む
dist/index.html<!doctype html> <html> <head> <title>Getting Started</title> </head> <body> Hello! <script src="main.js"></script> </body> </html>bodyに
<script src="main.js"></script>
を追加します。webpackではデフォルトでsrc/index.js
をエントリーポイントとしてdist/main.js
にコンパイルします。そのため、dist
内のindex.html
は相対パスでmain.js
を指定して読み込めばよいです。webpackでのビルド
コンソールnpx webpackこのコマンドを打ち込むと、
src/index.js
がdist/main.js
にコンパイルされます。myReactSample |- package.json |- /dist |- index.html + |- main.js |- /src |- index.js
このような階層になっているはずです。この状態で
index.html
をブラウザで開くとHello!
の下にWebpack!
が表示されるはずです。ここまででWebpackの導入が終わりです。次にWebpack DevServerの導入をします。
webpack DevServerの導入
Webpack Devserverのインストール
Webpack DevServerをインストール
コンソールnpm install --save-dev webpack-dev-serverプロジェクトのルートに
webpack.config.js
をつくって以下のようにします。webpack.config.jsvar path = require('path'); module.exports = { mode: 'development', devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000 } };コンソールでWebpack DevServerを起動します。
コンソールnpx webpack-dev-serverlocalホストでサーバーが起動します。どのポートで起動するかはコンソールのログに出力されるので、そのポートを指定してブラウザで開いて下さい。
うまく行かない場合は
webpack.config.js
でコロンが足りないなどでエラーになっている事があります。ここまで来れば、
index.js
を編集すると自動でリビルドが走ります。コンテンツ監視の設定
webpack.config.jsvar path = require('path'); module.exports = { mode: 'development', devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000, watchContentBase: true } };
devServer:
の中にwatchContentBase: true
を追記します。
コンソールでWebpack DevServerを再起動します。Ctrl + c
してから、コンソールnpx webpack-dev-serverをすると、jsファイルだけでなく、htmlファイルを更新した際も自動でリビルドが走ります。
babelの導入
babelのインストール
コンソール$ npm install --save-dev @babel/core @babel/polyfill @babel/preset-env @babel/react babel-loaderwebpack.configの変更
jsxのビルドのためにbabelを導入します。
webpack.config.jsvar path = require('path'); module.exports = { mode: 'development', module: { rules: [ { test: /\.js$/, use: [ { loader: "babel-loader", options: { presets: [ "@babel/preset-env", "@babel/react" ] } } ] } ] }, devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000, watchContentBase: true } };Reactのインストール
コンソールnpm install --save-dev react react-domReactを使う!
index.htmlに
<div>root<div>
を追加して、index.jsでReactから読みます。dist/index.html<!doctype html> <html> <head> <title>Getting Started</title> </head> <body> <div id="root"></div> <script src="main.js"></script> </body> </html>dist/index.jsimport React from 'react' import ReactDOM from 'react-dom' class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ) } } function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ) } ReactDOM.render( <App />, document.getElementById('root') );これで動くはずです。パソコンの電源が切れたのでTypeScriptなど続きはまた今度です。
- 投稿日:2020-03-26T20:35:22+09:00
React + TypeScript の開発環境を作る方法を丁寧に
仕事で新しい機能を開発する際に、フロントエンドにはVue.jsをよく採用しています。しかし個人的な開発でReactを使ってみたところ、書き味を気に入ってしまって、最近はReact推しです。
さて、開発環境構築の記事を書くにあたって、記事の有効期限が気になりました。JavaScriptアプリケーション周辺技術の栄枯盛衰の速度を考えると、単純に叩いたコマンドやファイルの内容を列挙していくだけでは、使用しているツールのバージョンの違い等で容易にエラーを起こすことが考えられます。ツール単体の導入方法は公式ページにあるんですから、そっちを見たら良いですしね。使用するツールのバージョンを全て固定することも考えられますが...。
そんなわけで、有効期限が少しでも長くなることを狙ったReactアプリ開発環境構築の記事を書く試みです。
方針:
- コマンドや設定の詳細は適時省き、一次ソース(公式ページ)を参照できるようにします。
- 最終的に index.html、main.js、index.cssを出力し、それだけで完結するSPAアプリになります。
この記事で選択する技術です。今自分が新しいJSアプリを作るならこうなります。選択理由も併記。
- React: 好き。TypeScriptと相性がいい。
- TypeScript: 型。Elmが恋しい。
- Webpack: ビルド構成の自由度の高さ。Create React AppはPostCSSを足しにくいので却下。
- PostCSS: 最低限autoprefixerを入れたい。
- Prettier: 機械にできることは機械任せ、人間は創造力を発揮しましょう。
それでは順番に導入していきましょう。
プロジェクト作成
mkdir my-app cd my-app git init npm init -yWebpackでJavaScriptをビルドできるようにする
https://webpack.js.org/guides/getting-started/
このページを見ながら、Pure JavaScriptをビルドできるようにします。
ただし、index.jsは以下の内容でいいでしょう。lodashのインストールは不要です。src/index.jsalert('JS Loaded');最終的に以下のようなファイル構成になります。
my-app |- package.json |- webpack.config.js |- /dist |- main.js |- index.html |- /src |- index.js |- /node_modulesこのステップでの確認事項:
- package.jsonに
npm run build
コマンドの内容を記述し、webpackコマンドが実行されるようにした。- webpack.config.jsで入力と出力を設定した。
npm run build
コマンドを打った時、src/index.jsを入力としてdist/main.jsが出力される。webpack-dev-serverを導入する
開発時に便利なlive reloading(ファイルを更新したら画面を自動でリロードしてくれる)とHot Module Replacement(HMR、ファイルを更新したら画面全体をリロードするのではなくモジュール単体を更新してくれる)はこの時点で入れておきます。
https://webpack.js.org/guides/development/#using-webpack-dev-server
このページの"Using webpack-dev-server"を参考に設定し、npm run start
した時にブラウザでdist/index.htmlが開かれるようになればOKです。
https://webpack.js.org/guides/hot-module-replacement/
このページを参考に、HMRを有効にしておきます。まだ別モジュールを作っていないのですが、このページで行っているように別ファイルを作って試してみるのもいいでしょう。このステップでの確認事項:
- webpack.config.jsにdev server用の設定をした。
- package.jsonで
npm run start
コマンドの内容を記述し、そこではwebpack-dev-serverが起動するようにした。Prettierでコードフォーマットできるようにする
https://prettier.io/docs/en/install.html
このページを見てインストール。
https://prettier.io/docs/en/cli.html
このページを見て使い方を知る。prettier --write .
と書けばいいそうですから、package.json{ ... "scripts": { ... "format": "prettier --write ." } }としておきましょう。実行してみると分かりますが、distディレクトリ内のファイル等、フォーマットしてほしくないファイルもあります。
https://prettier.io/docs/en/ignore.html#ignoring-files
このページを参考に、.prettierignoreファイルを用意して、distディレクトリはフォーマットしないようにします。このステップでの確認事項:
- package.jsonでnpm run formatコマンドの内容を記述し、prettierが実行されるようにした。
TypeScriptで書けるようにする
https://webpack.js.org/guides/typescript/
WebpackのページにTypeScriptのセットアップ方法があります。
この記事を書いた時点ではコンパイラとしてts-loaderを使い、ES5まで変換しています。ゆえにbabelは使いません。
https://www.typescriptlang.org/docs/handbook/compiler-options.html
tsconfig.json compilerOptionsの設定値は確認しておきましょう。ただ、設定値に悩まないコツは、不要な設定をしないことだと今のところ思います。何か問題が起こって、必要に迫られた時に設定を追加変更していくぐらいでも良いです。逆に最初からstrictに行くぜ!って人は、"strict": true, "allowJs": false, "noImplicitAny": true, "strictNullChecks": true
でどうでしょう。このステップでの確認事項:
npm run build
すると、TypeScriptで書かれたファイルがJavaScirptファイルに変換された形でdistディレクトリに出力される。React導入
ここでどこを参照したら良いか悩みます。いくつかの情報を組み合わせることにしました。
https://blog.usejournal.com/creating-a-react-app-from-scratch-f3c693b84658
WebpackでReactアプリを作成する手順として、React公式ページからリンクされた記事です。今babelを使わないのでそれを差っ引いて参考にします。
https://www.typescriptlang.org/docs/handbook/react-&-webpack.html
TypeScript公式ページの、ReactとWebpackセットアップ解説。Node.jsでホストすることを想定しているようなのでそれを踏まえて参考にします。
https://reactjs.org/docs/components-and-props.html
React v16現在、コンポーネントの定義方法がFunctionとClassの2つあります。前者が新しい方法です。
https://reactjs.org/docs/hooks-intro.html
便利なReact HooksもFunctionコンポーネント前提です。Functionコンポーネントを採用することにしましょう。
https://reactjs.org/docs/getting-started.html
もしかしたら将来React Getting Startedのページに適切な一次情報が増えるかもしれないので、そちらも探してみてください。パッケージインストール
npm install react react-dom npm install --save-dev @types/react @types/react-dom source-map-loaderesModuleInteropについて
TypeScript公式ページ通りにtsconfig.jsonを設定してみます。
tsconfig.json{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "commonjs", "target": "es6", "jsx": "react" } }src/index.tsxをこう書くと...
src/index.tsximport React from "react"; import ReactDOM from "react-dom"; import App from "./App.js"; ReactDOM.render(<App />, document.getElementById("root"));エラー。
https://stackoverflow.com/a/56348146
なぜこうなるかの説明がありました。ReactはCommonJSモジュールのようですね。
このままだと以下のようにimportしないといけなくなるわけです。src/index.tsximport * as React from "react"; import * as ReactDOM from "react-dom";tsconfig.jsonに
"esModuleInterop": true
設定を足すと、最初に書いたようにimportできるようになります。こっちを採用します。Reactセットアップ
tsconfig.json{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "commonjs", "target": "es6", "jsx": "react", "esModuleInterop": true } }webpack.config.jsconst path = require("path"); module.exports = { entry: "./src/index.tsx", output: { filename: "main.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ }, { enforce: "pre", test: /\.js$/, loader: "source-map-loader" } ] }, resolve: { extensions: [".tsx", ".ts", ".js"] }, devtool: "source-map", devServer: { contentBase: "./dist", hot: true } };src/index.tsximport React from "react"; import ReactDOM from "react-dom"; import App from "./app/App.tsx"; ReactDOM.render(<App />, document.getElementById("root"));src/app/App.tsximport React from "react" const App: React.FC = () => { return <div>Hello React App</div> } export default Appdist/index.html<!DOCTYPE html> <html> <head> <title>Getting Started</title> </head> <body> <div id="root"></div><!-- Reactをマウントする場所を用意 --> <script src="main.js"></script> </body> </html>ここまで書けたらnpm run startしてみてください。コンソールにエラーが出ず、画面にHello React Appと表示されたら成功です。
src/app/App.tsxのReact.FCについて補足しておきます。JavaScriptでReact Functionコンポーネントを定義する時は下のようになります。
App.tsxconst App = () => { return <div>Hello React App</div> }Functionコンポーネント用のinterface FunctionComponentが@types/reactに定義されています(公式での説明が見つかりませんでしたが)。この省略形としてtype FCが定義されています。propsの型チェック等に便利なので使用していきます。
HMR対応
さて、npm run start(webpack-dev-serverで画面表示)している状態で、src/app/App.tsxを編集保存してみてください。ブラウザが自動リロードして画面が更新されると思います。本来HMRではAppが描画している部分だけが更新してほしいので、対応します。
https://webpack.js.org/guides/hot-module-replacement/
https://webpack.js.org/concepts/hot-module-replacement/
https://webpack.js.org/api/hot-module-replacement/src/index.tsximport React from "react"; import ReactDOM from "react-dom"; const render = () => { const App = require('./app/App').default; ReactDOM.render(<App />, document.getElementById("root")); } render(); if (module.hot) { module.hot.accept('./app/App', () => { render(); }) }module.hot.acceptの第一引数で指定したファイルが更新されると第二引数の関数が実行されるので、再度renderします。
TypeScriptで書いているので、module.hotに関する型情報が必要です。
npm i -D @types/webpack-env
src/app/App.tsxを編集保存してブラウザがリロードせずに画面が更新されれば成功です。
PostCSS導入
https://github.com/postcss/postcss-loader
src/index.cssを用意しsrc/index.tsxで import "./index.css"; した上で、上のページのとおり設定していきます。このステップでの確認事項:
- index.tsxからindex.cssをimportした
- webpack.config.jsでpostcss-loaderのルールを追加した
- postcss.config.jsを作成し設定を行った
CSSを外部ファイル化したい場合は、以下を見るといいでしょう。
https://github.com/postcss/postcss-loader#extract-css
https://webpack.js.org/plugins/mini-css-extract-plugin/Webpack設定の補足など
webpack.config.jsのmodeを今まで無視してきました。
https://webpack.js.org/configuration/mode/
npm run build時とnpm run start時それぞれでmodeを切り替えたい。上のページでもCLIからmodeを渡す方法を紹介しています。
https://webpack.js.org/guides/production/
このようにwebpack-mergeを使っているプロジェクトもよく見ます。
個人的には、postcss.config.js等の外部ファイルでも動作を切り替えたいので、cross-envでNODE_ENVを定義することが多いです。
https://www.npmjs.com/package/cross-env今回dist/index.htmlは自前で作成しましたが、HtmlWebpackPluginを使うと、Webpackが出力するJSやCSSをロードするHTMLファイルを生成してくれます。
https://webpack.js.org/plugins/html-webpack-plugin/最後に
以上で解説は終了です。
環境構築記事では、このツールとこのツールを組み合わせることが可能か、組み合わせるのにはどうしたら良いかという点にオリジナリティが出ます。そこを書くにはどうしても設定方法を詳細に書くことになります。詳細に書くと記事の有効期限が短くなりがち。結論、環境構築記事は生もの!常に更新しない限りすぐ腐る。ただ、信頼できる一次ソースを参考文献としてちゃんと示すと、読んだ人が判断しやすい記事になりそうだなとは思いました。
- 投稿日:2020-03-26T13:55:20+09:00
【React】ReduxToolkitで非同期処理を実装する
概要
- Reduxを使って非同期処理を実装する際はReduxThunkのようなmiddlewareを使うケースが多いと思います
- しかしmiddlewareを使うと実装がややこしくなってしまうケースも多く悩ましい場面もあります
- ReduxToolkitはReduxThunkが内包されていて実装量少なくReduxで非同期処理を作ることができます
事前準備
- CreateReactAppでReactアプリの雛形を作っておきます
npx create-react-app redux-thunk-sample
- 以下のコマンドで起動できます
yarn start # open http://localhost:3000
ReduxToolkitによる非同期処理の実装
- 今回はQiitaのAPIをたたいて記事一覧を取得してみます
ライブラリの追加
- Redux関連ライブラリをインストール
yarn add react-redux @reduxjs/toolkit通信処理の作成
- QiitaのAPIを叩く部分を作成します
src/api/qiitaApi.jsexport async function getItems() { const res = await fetch('https://qiita.com/api/v2/items'); const json = await res.json(); if (!res.ok) throw new Error(json.message); return json; }
- 今回のメインとなるReduxの処理を書くファイルを作成します
src/store/qiitaSlice.jsimport { createSlice } from '@reduxjs/toolkit'; import { getItems } from '../api/qiitaApi'; // Slice export const qiitaSlice = createSlice({ name: 'qiita', // stateの初期値を設定 initialState: { loading: false, error: null, items: [] }, reducers: { // 通信を開始した時に呼ぶ関数 fetchStart(state, action) { state.loading = true; state.error = null; }, // 通信が失敗した時に呼ぶ関数 fetchFailure(state, action) { state.loading = false; state.error = action.payload; }, // 通信が成功した時に呼ぶ関数 fetchSuccess(state, action) { state.loading = false; state.error = null; state.items = action.payload; }, }, }); // Actions export const { fetchStart, fetchFailure, fetchSuccess } = qiitaSlice.actions; // 外部からはこの関数を呼んでもらう export const fetchItems = () => async dispatch => { try { dispatch(fetchStart()); dispatch(fetchSuccess(await getItems())); } catch (error) { dispatch(fetchFailure(error.stack)); } }; // Selectors export const selectQiita = ({ qiita }) => qiita; // Reducer(must be default export) export default qiitaSlice.reducer;
- 通信中かどうかを示す
loading
、エラー情報を入れるerror
、Qiitaから取得した記事を入れるitems
の3つのStateを持ちます- 非同期ではない場合は
reducers
に定義した関数を外部からdispatchしていました
- 非同期処理の場合は処理の開始時と終了時にStateを更新したくなります
- なので外部から直接
reducers
に定義した関数をdispatchせずにワンクッション挟む関数(ここではfetchItems)をdispatchしてもらうことで対応できます記事一覧を表示するコンポーネントの作成
- Reduxの処理を呼び出して取得した情報を表示するコンポーネントを作成します
src/components/QiitaItems.jsimport React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { selectQiita, fetchItems } from '../store/qiitaSlice'; function QiitaItems() { const dispatch = useDispatch(); const { loading, error, items } = useSelector(selectQiita); useEffect(() => { // fetchItemsを実行 dispatch(fetchItems()); }, [dispatch]); if (loading) return <p>...loading</p>; if (error) return <p>{error}</p>; return items.map(item => ( <div key={item.id}> <h2><a href={item.url}>{item.title}</a></h2> <p>{item.created_at} by {item.user.id}</p> </div> )); } export default QiitaItems;
- useEffectで
fetchItems
をdispatchしているのでコンポーネント生成時に通信処理を呼び出している感じです- 通信中は
...loading
と表示されて完了すると記事の情報が表示されるようにしていますReduxのセッティング
- Reduxを使うための設定を入れておきます
- App.jsの修正
src/App.jsimport React from 'react'; import { Provider } from 'react-redux'; import { store } from './store'; import QiitaItems from './components/QiitaItems'; function App() { return ( <Provider store={store}> <QiitaItems /> </Provider> ); } export default App;
- Storeの作成
src/store/index.jsimport { configureStore } from '@reduxjs/toolkit'; import qiitaItemsReducer from './qiitaItemSlice'; export const store = configureStore({ reducer: { qiitaItems: qiitaItemsReducer, }, });動作確認
- うまくいっていれば以下のようになっているはずです
まとめ
- ReduxToolkitを使うと通信処理もそれほど複雑さを感じずに実装できました
- 実装されたコードもわかりやすくていいですね
- 投稿日:2020-03-26T13:14:43+09:00
react-bootstrapでWarning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>
react-bootstrapでこのエラーに遭遇して対処法を調べたのですが日本語の記事が少なくて解決に時間がかかったのでメモとして残しておきます。
エラーの原因
コンソールには以下のエラーが出力されます
Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>.原因がわからずググってみたら、このサイトによると
<div>
タグを<p>
タグでラップしているとこのエラーが起こるようです。
しかし、<p>
タグを使っている箇所がない、、、解決策
原因となっていたのは以下のコードでした
<Card.Text> <Row style={{ clear: "both" }}> <SelectButton isSelected={selectedPictureList.includes(picture)} target={picture} selectedFunc={selectPicture} unselectedFunc={unselectPicture} /> <PicturesCommentNewContainer pictureId={picture.id} /> </Row> </Card.Text>こちらのサイトによると、
<Card.text>
の出力は<p>
タグになってしまうそうです。
その中のコンテナ(PicturesCommentNewContainer
)でdiv
タグを返す処理をしていたので、エラーが起こっていたみたいですね...<Row style={{ clear: "both" }}> <SelectButton isSelected={selectedPictureList.includes(picture)} target={picture} selectedFunc={selectPicture} unselectedFunc={unselectPicture} /> <PicturesCommentNewContainer pictureId={picture.id} /> </Row>このように、単純にを使わなければエラーは無事なくなりました
bootstrapめんどい!
- 投稿日:2020-03-26T10:16:42+09:00
ライフサイクルメソッドがわかればReactの50%は理解したと言えなくもない
ライフサイクルの話をする前に・・・
まず前提として下記のことが言えると思う。
Reactとはそもそも、状態の管理、更新を適切に描画に反映するためのライブラリである。さらに、Componentの最も大きな役割は描画(render)をすることである。
効率良く描画の表示、更新をするためにはComponentのライフサイクルを理解することが必須であると言える。
ライフサイクルとは
クラスコンポーネントを扱う際にライフサイクルは大きく分けて3つの期間がある。順番にMounting(マウント時)、Updating(更新時)、Unmounting(マウント解除時)。
公式でも下記のような図が紹介されているのでこちらを元に説明をする。
React lifecycle methods diagram
Mounting(マウント時)
Componentが表示されるまでの期間のこと。
- constructor()
- getDerivedStateFromProps() // 一般的ではないライフサイクル
- render() // ここで描画される
- componentDidMount()
componentDidMountで描画された後に何かしらの変更を加えたい、描画されてから出ないとできないことをここで行うことになる。ちなみに、最初にrenderされるときだけこの期間に入る。
例えばユーザー一覧を表示させるUIだとしたら、基本的なTableの見出しとユーザーの取得が完了するまでの間に表示させる
Loading...
などがMounting時のrenderに相当する。そこまでの描画が完了したらcomponentDidMount
でユーザー一覧のAPIを叩く。Updating(更新時)
Componentが表示され、Stateの更新を行う期間のこと。
- getDerivedStateFromProps() // 一般的ではないライフサイクル
- shouldComponentUpdate() // 一般的ではないライフサイクル
- render()
- getSnapshotBeforeUpdate() // 一般的ではないライフサイクル
- componentDidUpdate()
Stateの更新がされたら、1 ~ 5の順番でメソッドが呼ばれ再描画される。
先ほどの例でいけば、
componentDidMount
でユーザー一覧のAPIを叩いた後に、取得が完了すればStateのusersにデータが入ることになる。state = { users: [ // ここの部分に入る ] }このusersが
[]
から[{id: 1, name: 'hogehoge'}]
と更新されたことを検知して再描画 > TableにUserを表示させることができる。では、
componentDidUpdate()
はどのタイミングで使うべきなのか例を挙げてみる。ユーザーを新たに追加したいとしよう。UIでは一覧上で
新規登録
ボタンを押すとモーダルが出てきて ユーザー名を入力 > 登録とすることで追加することができるとする。その場合、登録を押すことで APIを叩いてpostしているわけだが、送り終わったことがわかるStateの変更を検知して、ユーザー一覧の再取得をすることができる。
componentDidUpdate(prevProps) { if(!prevProps.succeeded && this.props.succeeded) { // ユーザー一覧を再取得 } }このときわかりにくいのが、prevPropsである。読んで字のごとく以前のPropsである。では、どういうことかというと
componentDidUpdate
では更新される以前のPropsまたはStateと更新後のPropsまたはStateを比較することができる。それにより、特定のState(Props)が更新されたときにだけ何かしらの処理をすることができる。Unmounting(マウント解除時)
他のComponentに移るときにだけ呼ばれる期間。
- componentWillUnmount()
現在のコンポーネントを破棄する直前に呼ばれるメソッド。
よく見落とされるのがアニメーションやタイマーを設定を
ComponentDidMount
でしていた場合はcomponentWillUnmount
で破棄すること。そうしないと新しいコンポーネントのサイクルが始まった後も、その分のメモリが開放されないままになってしまう。unsafeメソッドについて
実は以前は、他にもライフサイクルメソッドが存在していた。(今もしているけど非推奨)例えば、
- componentWillMount()
- componentWillUpdate()
- componentWillReceiveProps()
である。これらを自身のプロジェクトで見つけたらこっそり書き換えてあげると良い。
ちなみに、完全に個人的な見解だけど、結局ライフサイクルメソッドがたくさんあると、どこで何が変更されたなど、タイミングが難しくなってしまうことが多々ある。人に優しくないコードが簡単にできてしまうのではないかなと。
なので、基本的には
componentDidMount
,componentDidUpdate
でまかなえるようにComponentの設計をすべきであると思う。その流れが hooksのuseEffectに引き継がれているのかなと。
Functional Componentの場合のライフサイクル
以前までは クラスコンポーネントでないとライフサイクルを使うことができなかった。が、hooksの登場により Functional Component でもライフサイクルを使うことができるようになった。
useEffect
を使うことで簡単に 特定Stateの更新を検知することができる。React.useEffect(() => { // なんかやる }, [// 検知したいState ])例えば、エラーコードが検知したら何かやりたい場合は以下のような感じになる。
const [code, setErrCode] = React.useState<string>('') React.useEffect(() => { if (code) { // codeが変更されたら何かやる } }, [code])ライフサイクルメソッドの問題点?
状態の管理、更新を適切に描画をするのには必須と言えるライフサイクルメソッドだが、問題が発生するケースもある。
よくあるのが、componentDidUpdate内でifが乱立していて更新の検知が適切に行えているのかわかりにくいことがあげられる。
この問題の根本はライフサイクルメソッドが悪いのではなくComponentの設計に問題があることがほとんど。
Componentがやたらと大きくなりすぎていて多機能すぎていると言える。
次回はComponentの設計あたりを記事にする予定・・・
- 投稿日:2020-03-26T09:31:19+09:00
ReactでHTTPボディを送る方法
やりたいこと
reactから
req.body.id
をnodeへ送って、nodeで、req.body.id
を受け取りたかった。その後、req.body.id
の値を使ってmongoから値を取得する流れreact
const fetchData = async () => { var params = new URLSearchParams() params.append('id', pass) const result = await axios.post('http://localhost:3000/signin', params);node
app.post('/signin', function(req, res) { User.findOne({_id: req.body.id}, function(err,obj) { console.log(obj);参考
axiosの使い方まとめ (GET/POST/例外処理)
[Web] HTTPリクエストの中身を学んでみた。GETやPOSTの違いなど
- 投稿日:2020-03-26T08:16:01+09:00
Laravel7+Reactの環境を構築する
Laravel7 で React を利用した開発を可能にする手順をご紹介いたします。
ここでは Laravel と React はインストール済みとして進めていきます。開発環境
- Laravel 7.2.1
- React 16.13.1
- PHP 7.4.3
Laravel で React を利用できるようにする
React を利用する Laravel のプロジェクトを作成し、
作成されたプロジェクトフォルダに遷移します。composer create-project laravel/laravel hoge-project cd hoge-project次に Laravel が React を利用するように設定を変更します。
ui artisan コマンドを使用するので、laravel/ui パッケージを先にインストールしています。composer require laravel/ui php artisan ui react最後に下記コマンドを実行すると、Laravel で React を使えるようになります。
npm install && npm run devLaravel の動作確認をする
Laravel が正常に動作するか確認します。
php artisan servehttp://127.0.0.1:8000 にアクセスして、Laravel のページが表示されたら正常に動作しています。
js ファイルの変更を常時反映させるようにしておく
現状のままだと js ファイルの変更が検知されないので、常時変更を検知してくれるように設定します。
別のターミナルを起動し、Laravel のプロジェクトフォルダ内でnpm run watch
を実行します。cd hoge-project npm run watchReact のサンプルファイルで文字列を表示する
React を利用して画面上に文字列を表示できるよう、既存ファイルを変更していきます。
まずは React を使用しているサンプルファイル(resources\js\components\Example.js)を見てみましょう。resources\js\components\Example.jsimport React from 'react'; import ReactDOM from 'react-dom'; function Example() { return ( // 省略 ); } export default Example; if (document.getElementById('example')) { ReactDOM.render(<Example />, document.getElementById('example')); }id が "example" の HTML タグに文字列を表示する処理だとわかります。
それでは Example.js が出力する内容を表示する HTML タグを記述しましょう。
http://127.0.0.1:8000 にアクセスすると表示される内容は welcome.blade.php(resources\views\welcome.blade.php)に記述されているので、
下記のように編集します。resouces/views/welcome.blade.php<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel</title> </head> <body> <div id="example"></div> <script src="{{ mix('js/app.js') }}"></script> </body> </html>body タグ内に、React 出力データの挿入場所である
<div id="example"></div>
と、
Example.js を処理するファイルapp.js
の読み込みを記述します。http://127.0.0.1:8000 にアクセスし、下記の通りに表示されていれば成功です。
もし Example.js 以外のファイル、例えば Hoge.js を読みこませたい場合は、
app.js(resources\js\app.js)を下記のように編集すると良いでしょう。\resources\js\app.jsrequire('./bootstrap'); require('./components/Example'); // 下記を追記 require('./components/Hoge');以上です。
- 投稿日:2020-03-26T05:59:45+09:00
Gatsby.jsのsourceNodesライフサイクルでresourceを参照する方法
Gatsby.jsでノードを作るライフサイクルの
sourceNodes
でresourceのデータを参照したい時に詰まったのでメモ。経緯
あるresourceのデータに外部画像のURLがあり、それを gatsby-image で使うために、gatsby-source-filesystem の createRemoteFileNode で外部画像用のNodeを生やす必要がありました。
新たにNodeを生やすのでsourceNodesのライフサイクルで
- 内部のスキーマに対してGraphQLで対象のURLを取得
- そのURLに対してcreateRemoteFileNodeでノードを作成する
で良いのかなと思ったのですが、sourceNodesではまだschemaがないのでGraphQLのAPIを呼べませんでした。
ということで、sourceNodesライフサイクルでresourceを参照する方法です。
方法
getNode API で直接resouceのノードの情報を参照する。以上です。 schemaはないのですが、ノードはあるのでアクセスできるのですね。
const node = getNode("参照したいノードのID")実際のコードはこちらです。
sourceNodesでfacebookのfeedのresourceから画像情報を取得してcreateRemoteFileNodeから外部画像用のノードを作成する例ですgatsby-node.js"use strict"; const { createRemoteFileNode } = require(`gatsby-source-filesystem`); exports.sourceNodes = async ({ actions: { createNode, createNodeField }, createNodeId, cache, store, getNode }) => { // クエリは投げられないでNodeから直接取得。 // 画像がないfeedもあるのでfilterで除外 const feedData = getNode("ノードID").feed.data.filter( u => u.full_picture != null ); await Promise.all( feedData.map(async data => { // 外部画像のダウンロードとノードの構築 const fileNode = await createRemoteFileNode({ url: data.full_picture, cache, store, createNode, createNodeId }); await createNodeField({ node: fileNode, name: "feedImage", value: "true" }); await createNodeField({ node: fileNode, name: "feedId", value: data.id }); return fileNode; }) ); };参考
以下参考にさせて頂きました。良記事ありがとうございます。
- 投稿日:2020-03-26T00:26:07+09:00
GraphQLサーバーにクエリを実行する方法
GraphSQL
Queries And Mutations
Arguments
REST API: リクエスト内のクエリパラメータとURLセグメントのみ
GraphQL: オブジェクトが独自の引数を取得できる -> 複数のAPIフェッチを完全に置き換えることができます.{ human(id: "1000") { name height(unit: FOOT) } }{ "data": { "human": { "name": "Luke Skywalker", "height": 5.6430448 } } }Alias
Arguments
の例だと, 異なる引数を持つフィールドを複数もつことができないNG
graphql
{
human(id: "1000") {
name
height(unit: FOOT)
},
human(id: "1001") {
name
height(unit: FOOT)
}
}
GraphQLでは, Aliasを指定して対応可能.
{ human1000: human(id: "1000") { name } human1001: hero(id: "1001") { name } }{ "data": { "human1000": {...}, "human1001": {...} } }Fragments
再利用可能なユニット
重複するフィールドセットはFragmentsに書き出す.{ leftComparison: hero(episode: EMPIRE) { ...comparisonFields } rightComparison: hero(episode: JEDI) { ...comparisonFields } } fragment comparisonFields on Character { name appearsIn friends { name } }{ "data": { "leftComparison": { "name": "Luke Skywalker", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Han Solo" }, { "name": "Leia Organa" }, { "name": "C-3PO" }, { "name": "R2-D2" } ] }, "rightComparison": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } }Fragments + variables
query HeroComparison($first: Int = 3) { leftComparison: hero(episode: EMPIRE) { ...comparisonFields } rightComparison: hero(episode: JEDI) { ...comparisonFields } } fragment comparisonFields on Character { name friendsConnection(first: $first) { totalCount edges { node { name } } } }{ "data": { "leftComparison": { "name": "Luke Skywalker", "friendsConnection": { "totalCount": 4, "edges": [ { "node": { "name": "Han Solo" } }, { "node": { "name": "Leia Organa" } }, { "node": { "name": "C-3PO" } } ] } }, "rightComparison": { "name": "R2-D2", "friendsConnection": { "totalCount": 3, "edges": [ { "node": { "name": "Luke Skywalker" } }, { "node": { "name": "Han Solo" } }, { "node": { "name": "Leia Organa" } } ] } } } }Operation name
特になし
ref: https://graphql.org/learn/queries/#operation-nameVariables
Variable definitions
optional な場合
!
を付与するquery HeroForEpisode($ep: Episode!) { hero(episode: $ep) { name ... on Droid { primaryFunction } ... on Human { height } } }Default variables
デフォルト値を設定する場合以下のように表現する
query HeroNameAndFriends($episode: Episode = JEDI) { hero(episode: $episode) { name friends { name } } }Directives
Directiveはフィールドまたはフラグメントに添付でき,
サーバーが望む方法でクエリの実行に影響を与える可能性がありますクエリのフィールドを追加および削除するために文字列操作を行う必要がある状況を回避するのに役立ちます
Mutations
RESTではGETでデータを更新することは推奨されていない.
同様にGraphQLでも、クエリを実装してデータを書き込むことは推奨されない。書き込みを引き起こす操作は、ミューテーションを介して明示的に送信する必要があるという規則を確立すると便利です
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }Inline Fragments
GraghQLは, interfaceとunion typeを持つ.
query HeroForEpisode($ep: Episode!) { hero(episode: $ep) { name ... on Droid { primaryFunction } ... on Human { height } } }Meta Fields
GraphQLサービスから返されるタイプがわからない状況がある場合、クライアントでそのデータを処理する方法を決定する方法が必要である
クエリの任意の時点でメタフィールドである__typename
を要求して、その時点でのオブジェクトタイプの名前を取得できる{ search(text: "an") { __typename ... on Human { name } ... on Droid { name } ... on Starship { name } } }