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

React チュートリアル パート11 (npm install react-toastify )

目標 アプリケーションでこのようなポップを見たことないですか 今日は、これを実装します! 公式ドキュメント ”npm install react-toastify”をインストール まず、Vscodeのターミナルを開いて”npm install react-toastify”をインストールしてください。 アプリケーションとサーバーを起動する npm start と npm run serverをターミナルに入力 App.jsに記述 【4と5行目】ToastContainerとtoast,このパッケージ用のcssをimportします。 【45~48行目】ここで実装しています。 【56行目】ToastContainerを記述します。 ブラウザで確認 次回 npm の便利なパッケージを紹介します!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 基礎知識から Props, useState, useEffect について

学習の経緯 一番の理由はフロント側に興味津々で早く触りたいと思ってたためです。 また、周りで触っているエンジニアさんも多かったこともあり、Reactを習得することを決意。 前提 CodeSandboxを使用 JSX記法のルール JSXとは JavaScript を拡張し、UI 要素を記述するのに HTML のようなタグ構文が使えるようにしたJavascriptの拡張構文のこと コード説明(下記参照) import React from "react"; React でコードを書くためのオマジナイ import ReactDom from "react-dom"; HTML にコンポーネントを反映するための記述 const App Hello! と Tom が出力される関数の定義 複数要素の場合は、returnを()で囲う必要がある 且つ、 return する HTML の内容は1つのタグ(<> </>)で囲う必要がある ReactDom.render(<App />, document.getElementById("root")); 画面に反映させるための記述 構文 : ReactDom.render(関数等(element), 反映させる場所(container)); index.js import React from "react"; import ReactDom from "react-dom"; const App = () => { return ( <> <h1>Hello!</h1> <p>Tom</p> </> ); }; ReactDom.render(<App />, document.getElementById("root")); コンポーネント UIの一部分となるビュー(View)を切り出したもの。 また、単にUIを切り出すだけでなく、新たにコードを含むことも可能で、プログラムとして再利用できる。 import React from "react"; Reactでコードを書くためのオマジナイ export default 記述することで他のファイルで使用することができる xxx.jsx React のコンポーネント専用に用意されている拡張子 xxx.jsでもエラーは起きないが、明示的に示すことができる 【コンポーネントの作成その①】 src/App.jsx import React from "react"; const App = () => { return <h1>Hello!</h1> }; export default App; App.jsxコンポーネントを使用するための記述 import は相対パスで記述する("./App") src/index.js import React from "react"; import ReactDom from "react-dom"; import App from "./App"; // 追加 ReactDom.render(<App />, document.getElementById("root")); 【コンポーネントの作成その②】 src/App.jsx import React from "react"; export const App = () => { return <h1>Hello!</h1> }; export const App = () => { 関数コンポーネント前にexportを記述する src/index.js import React from "react"; import ReactDom from "react-dom"; import { App } from "./App"; ReactDom.render(<App />, document.getElementById("root")); import { App } from "./App"; インポートは、分割代入をして記述する 命名規則 ファイル名・・・パスカルケースで記述する  パスカルケース・・・先頭が大文字で始まり、単語の区切りを大文字とする記法のこと ○ App.jsx ✕ app.jsx ○ SomeComponents.jsx ✕ someComponets.jsx HTMLのタグ内で扱うイベント名・・・キャメルケースで記述する キャメルケース・・・先頭が小文字で始まり、単語の区切りを大文字とする記法のこと ○ onClickButton ✕ OnClickButton ✕ on-click-button CSS(スタイル)の当て方 HTMLの要素にstyles.cssを適用 jsxファイルの場合、classではなく、classNameとする <div className="クラス名"> HTMLの要素内に記述 値を記述する際の1つ目の{ }は、JavaScriptを記述するためのもので、2つ目の{ }は、オブジェクトを記述するためのもの プロパティーの値は必ずダブルコーテーション""もしくは、シングルコーテーション''で囲うこと <h1 style={{ color: "red" }}>Hello!</h1> 変数に定義する スタイル名はキャメルケースで記述する ○ fontSize ✕ font-size const App = () => { const contentStyle = { color: "blue", fontSize: "18px" }; return <h1 style={contentStyle}>Hello!</h1> Props コンポーネントに渡す引数のようなもの componentsディレクトリ直下に、コンポーネントファイルを作成 ■ 文字色とメッセージを渡すコンポーネントファイルの場合  ・ 画面にHello!(青色)とTom(ピンク色)を表示 src/components/ColorfulMessage.jsx import React from "react"; const ColorfulMessage = (props) => { console.log(props); // {color: "blue", message: "Hello!"}, // {color: "pink", message: "Tom"} const { color, message } = props; const contentStyle = { color, message }; return <h1 style={contentStyle}>{message}</h1>; }; export default ColorfulMessage; const ColorfulMessage = (props) => { 関数の引数(props)は、任意の文字でも問題ない(明示的にするため、propsとしている) const { color, message } = props; 記述量を減らすため、分割代入している しない場合、<h1>タグ内の{message}を{props.message}と記述する必要がある src/components/App.jsx import React from "react"; import ColorfulMessage from "./components/ColorfulMessage"; const App = () => { return ( <> <ColorfulMessage color="blue" message="Hello!" /> <ColorfulMessage color="pink" message="Tom" /> </> ); }; export default App; <ColorfulMessage color="blue" message="Hello!" />; コンポーネント名のタグを作成することでコンポーネントを使用することができる color="blue" message="Hello!" 動的に文字色とメッセージを変更するため、プロパティー値("blue"と"Hello!")をColorfulMessageコンポーネントに渡している porps名(color, message)は任意の文字を付けることができる ■ 親コンポーネントのタグにメーセージを記述する場合  ・ 画面にHello!(青色)とTom(ピンク色)を表示 src/components/ColorfulMessage.jsx import React from "react"; const ColorfulMessage = (props) => { console.log(props); // {color: "blue", children: "Hello!"} // {color: "pink", children: "Tom"} const { color, children } = props; const contentStyle = { color }; return <h1 style={contentStyle}>{children}</h1>; }; export default ColorfulMessage; {children} 親コンポーネントのタグで囲んでいる要素を表示することができる <ColorfulMessage color="blue">Hello!←ココ</ColorfulMessage> src/components/App.jsx import React from "react"; import ColorfulMessage from "./components/ColorfulMessage"; const App = () => { return ( <> <ColorfulMessage color="blue">Hello!</ColorfulMessage> <ColorfulMessage color="pink">Tom</ColorfulMessage> </> ); }; export default App; useState 各コンポーネントが持つ状態のことで、画面上に表示されるデータ等を保持している状態を指す。 Stateが変更されると再レンダリングされる 条件によって動的に変わる部分を定義することで、様々な状態の画面を表示することができる 親コンポーネントが再レンダリングされた場合、子コンポーネントも再レンダリングされる ■ 値を動的に更新するクリックイベントの場合  ・ ボタンをクリックする毎にnumの値が +1 され、更新後の値が画面に表示される src/components/App.jsx import React, { useState } from "react"; const App = () => { const onClickCountUp = () => { setNum(num + 1); }; const [num, setNum] = useState(0); return ( <> <button onClick={onClickCountUp}>カウントアップボタン</button> <p>{num}</p> </> ); }; export default App; import React, { useState } from "react"; useState を使用するため、分割代入で取り出している const [num, setNum] = useState(0); 構文 : const [<stateとして使用する変数名>, <値を更新するための関数名>] = useState(<初期値>); setNum(num + 1); 設定したい値を記述する ■ 要素を表示/非表示させるクリックイベントの場合  ・ ボタンをクリックすると、要素の値がfalseになることで非表示になり、再度クリックすると、要素の値がtrueになり Hello! が表示される(表示/非表示の繰り返し) import React, { useState } from "react"; const App = () => { const [showFlag, setShowFlag] = useState(true); const onClickGreeting = () => { setShowFlag(!showFlag); } return ( <> <button onClick={onClickGreeting}>on/off</button> <p>{showFlag && "Hello!"}</p> </> ); }; export default App; setShowFlag(!showFlag); 変数の先頭に!を付けることで、要素の値を切り替えている <p>{showFlag && "Hello!"}</p> showFlag が true の場合、 Hello! が表示されるように論理演算子を使用している useEffect レンダリングと副作用を切り離すことができる。 そのため、関数の実行タイミングをレンダリングされるまで遅らせることができる。 ■ カウントアップボタンをクリックし、3 の倍数時のみ、 Hello! を表示させ、且つ、on/offボタンのクリックイベントも反映させている場合 import React, { useEffect, useState } from "react"; const App = () => { const [num, setNum] = useState(0); const [showFlag, setShowFlag] = useState(false); const onClickCountUp = () => { setNum(num + 1); }; const onClickGreeting = () => { setShowFlag(!showFlag); }; useEffect(() => { if (num > 0) { if (num % 3 === 0) { showFlag || setShowFlag(true); } else { showFlag && setShowFlag(false); } } }, [num]); return ( <> <button onClick={onClickCountUp}>カウントアップボタン</button> <p>{num}</p> <button onClick={onClickGreeting}>on/off</button> <p>{showFlag && "Hello!"}</p> </> ); }; export default App; import React, { useEffect, useState } from "react"; useEffect を使用するため、分割代入で取り出している if (num > 0) { num の値が 0 以上の場合に表示させるための条件分岐 showFlag || setShowFlag(true); showFlag が false の場合のみ、setShowFlag(true) を実行 setShowFlag(true) のみの記述の場合、state が必ず呼び出されることで再レンダリングされてしまい、「無限ループが起きてしまうから気をつけて」というエラーが表示される showFlag && setShowFlag(false); showFlag が true の場合、setShowFlag(false) を実行 }, [num]); useEffect の第引数の配列に num を指定することで num の値が更新された場合のみ、実行させるように制御することができる 終わりに 触ってみた感想として、とても魅力的な言語だと感じました(語彙力とは)  これからも継続的に学習していきます。 参考教材
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

useRefでステートを管理するのはReact18でアンチパターンになるからやめよう

こんにちは。最近、Reactでのステート管理において「useStateの中にステートを置くのではなく、useRefで得たrefオブジェクトの中にステートを置いてuseState(またはuseReducer)をコンポーネントの再レンダリングを発生させるためだけに使う」というやり方を複数の記事で見かけました。このパターンは、今(React 17以前)は動くけどReact 18でアンチパターンに変貌するやり方なので、啓蒙するためにこの記事を用意しました。 ステート(コンポーネントのレンダリングに使用される値)は、useRefではなくuseState(またはuseReducer)をちゃんと使って管理するようにすれば、React 18以降も安泰です。 useRefをステート管理に使うパターンとは こういうやつです。 // 普通のやり方 const Counter1: React.VFC = () => { const [counter, setCounter] = useState(0); return ( <div> <p>Counter is {counter}</p> <p> <button onClick={() => { setCounter((c) => c + 1); }} > increment </button> </p> </div> ); }; // useRefを使うやり方 const Counter2: React.VFC = () => { const counter = useRef(0); const [, forceRerender] = useReducer((c: number) => c + 1, 0); return ( <div> <p>Counter is {counter.current}</p> <p> <button onClick={() => { counter.current++; forceRerender(); }} > increment </button> </p> </div> ); }; ここで定義されているコンポーネントはどちらも「ボタンを押すと表示されている数字が増える」というものですが、この数字はCounter1ではuseStateの中にある一方で、Counter2ではuseRefの中で管理されています。Counter2ではrefの中身(counter.current)を更新しただけではコンポーネントが再レンダリングされない(新しい状態が画面に反映されない)ので、useReducerを用いて「呼ぶと再レンダリングされる関数」(forceRerender)を作っておき、refの中身の更新後にforceRerenderを呼び出すことでuseStateと同じ挙動を実現しています。 後者のように、「ref.currentをレンダリング結果(関数コンポーネントの返り値)に使用している」ケースがこの記事でいう「useRefでステート管理」に該当します。 無意味にこのようなやり方をする人はいないと思いますが、諸々の事情があってやりたくなるケースがあるようです。 なぜReact 18でアンチパターンになるのか useRefを使うやり方は、React 18で追加されるトランジションの機能と組み合わせて使うと壊れるからです。 壊れる例をこちらに用意しました。 開くと、このようなアプリが表示されるはずです。 初期状態では「0 × 1000 = 0」と表示された部分と、incrementボタンのセットが2つ用意されています。左側の「0」部分がステートであり、上はuseStateで、下はuseRefで管理されています。上と下の実装は、ステートをどのように管理するかという点以外はまったく同じです。 トランジションはReactのSuspense機能と関連するものであり、ざっくり言うとステートの更新によって得られる新しいレンダリング結果が表示できるまでに時間がかかる場合(レンダリングがサスペンドする場合)において、新しいレンダリング結果が届くまでの間ステート更新前の画面を表示したままにしておける機能です。 今回は「× 1000」の計算に1秒かかるという設定になっています。useStateの方で「increment」ボタンを押すと表示が半透明になります(計算中を表しているつもり)。 そして、計算が完了する1秒後に「1 × 1000 = 1000」という表示になります。 コードを見ると分かりますが、ステート(counter1)をインクリメントするのは、ボタンを押すとすぐに実行しています(ただし、トランジションの中で)。それにもかかわらずそれが画面に反映されるのが1秒後まで遅らせられるというのがトランジションの効果です。トランジションを使うことで、時間がかかる処理においてもスムーズな画面遷移をユーザーに見せることができます。 一方で、useRefを使った実装の場合、「increment」ボタンを押すと「Loading...」表示に切り替わります。 これはトランジションが効いておらず、コンポーネントがサスペンドしたことによるフォールバック指定(<Suspense>のfallback propに指定したもの)が表示されているからです。 このように、useRefを用いてステート管理したものについては、トランジションの恩恵をうまく受けることができないのです。これは、useRefの中に入ったものはReactに「ステートである」と認識されていないからです。 まとめ useRefの中にステートを置いてuseStateまたはuseReducerを用いてコンポーネントを強制的に再レンダリングさせることでステート管理するというやり方は、このようにReact 18で導入されるトランジションと相性が悪いためアンチパターンとなります。 従来(React 17まで)は確かにuseStateは「useRef + 再レンダリング機能」と見なせるような機能でしたが、React 18からは「これに入っているものはステートである」と宣言しReactに伝える役割を持つようになります。トランジションは「ステートの更新」をトラックすることで動作するため、useRefの中に入っているものはReactが認識する「ステート」に含まれず、useRefではuseStateの機能を代替できなくなるのです。 じゃあトランジションなんか使わなければいいじゃんと思われるかもしれませんが、実際のところほぼ全てのステート更新は本来トランジションとして扱われるべきものです(出典)。トランジションにできないステート更新のほうが例外的なのです。よって、トランジションに対応したステート管理方法でないと、React 18以降とてもやっていけないということになります。 ですから、近く来るReact 18に備えて知識をアップデートし、useRefでステートを管理するのはアンチパターンだと覚えて帰ってくださいね。 Q&A Q. トランジションとかサスペンドとか言われても全然分からないし、サンプルコードも読めないんだけど。 A. 確かに多少前提知識が必要でしたね。しかしご安心ください。筆者がこれまでに書いた以下の記事およびZenn本を順番に読めばすべて理解できます。今すべて読む時間がないとしても、何がアンチパターンなのかということだけは理解して帰ってください。 Q. でもReduxとか他のステート管理ライブラリもuseStateを再レンダリングのためだけに使ってるじゃん! A. お詳しいですね。確かにReduxなどもuseStateなどを使わずにReactのコンポーネントツリーの外部でステート管理をしているという点で、useRefによるステート管理に近いものです(むしろ、それをうまくやってくれることこそがステート管理ライブラリの価値であると言っても過言ではないでしょう)。実際、react-reduxの現行バージョンのソースコードを見ると、forceRenderのためにuseReducerを使っているコードがあります。しかし、まさにそのためにReduxもトランジションに対応できませんでした。Reduxもアンチパターンを踏んでいたのです(React 17以前では他にやりようがなく仕方なかったのですが)。ただし、だからReact 18ではReduxが使えないというわけではなく、useSyncExternalStoreという新しい機能がReact 18で追加されるのでこれを用いてReact 18に対応しています。もしどうしてもuseRefによるステート管理が必要なら、もしかしたらuseSyncExternalStoreを調べてみると幸せになれるかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + Reduxを基本からまとめてみた【10】【Redux Toolkit ②】

Redux とは 『Redux』 とは、JavaScript製の状態管理ライブラリ。 ※ 「状態」とは、アプリケーションで扱う動的なデータという理解でよい。⇨ 例えば、ユーザー名とか、いいねの数が「状態」で、状態は時系列とともに変化する。 状態管理ライブラリが解決する問題は、コンポーネントをまたいだデータの共有で、React でも Vue.js でも、コンポーネントは、DOM と同様にツリー構造をなす。 複数のコンポーネントで同じデータを使いたい、というケースがあるとする。 深い階層のコンポーネントがあるデータを必要とする場合、下図のように、実際にはそのデータを利用しないコンポーネントも含めて props のバケツリレーが発生する。 これではアプリのコードを複雑にし、さらに途中のコンポーネントに無関係な props が定義される為、不必要な依存も産みやすい。 そこで考え出されたのが、状態管理ライブラリで、『ストア』と呼ばれるデータの入れ物を用意して、各コンポーネントが直接ストアとコミュニケーションし、これにより、上述の問題が解決される。 Redux の特徴 『Redux』の特徴は、コンポーネントからのアクセスに一定のルールが設けられている点である。 React におけるデータフローが props を通した親から子への流れに限定されているのも、コードをスパゲッティ化から守るための意図がある。 状態管理がグローバル変数のように濫用できてしまうと、コードにアンチパターンを仕込む結果になってしまう。 Redux は上記のような問題に配慮し、コードを予測可能にしてバグが出にくくするため、ルールを設けている。 さて、以下がコンポーネントと Redux ストアとのデータのやり取りを表した図です({} はオブジェクト、f(x) は関数であることを示しています)。 Reduxのモジュール State 状態を格納するオブジェクト。 sample.js { count: 0 } Reducer 受け取った Action(後述)に応じて State を変更する関数。 正確には、新たな State を返却する関数。 sample.js function reducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 } case 'ADD': return { count: state.count + action.payload } default: return state; } } Action 状態の更新を指示するオブジェクト。一般的に以下のプロパティを持ちます。 sample.js { type: 'INCREMENT' } // typeがStateに対する操作を表す { type: 'ADD', payload: 3 } // payloadは任意の引数 処理の流れ 1. コンポーネントが、Action Creator を呼び出して Action を取得する。 2. 取得した Action を Reducer に渡す。 ※これを『dispatch』という。 3. Reducer は、渡された Action に応じて State を更新する。 4. コンポーネントは State に変更があれば、関連する UI を書き換える。 設計のポイント このようなルールの重要なポイントは、コンポーネントが自由に状態(State)を書き換えられない点です。状態を更新するためには必ず Reducer に更新処理を依頼する必要があり、さらに Reducer はあらかじめ決められた Action にしか反応しない。 状態に対して定義された変更しか加えられないこのルールは、予期しない更新処理を防ぎ、コードを予測しやすくする。 また、Action Creator も不確実性の排除に一役買って、Reducer に対しては決められた属性および値を持つ Action を渡さなければならない。type の値など間違った Action を渡さないように、Action Creator を介して Action を取得する。 Redux Toolkit Redux 導入の課題 『Redux』は最小限の機能のため、導入する際は上で説明した各モジュールを自分で準備しなくてはいけない。 ※ それらのコーディングは煩雑で、初学者にはハードルになり、ある程度習得したあとも、定型文のようなセットアップコードを書くのは大変で、また、アプリの規模によってはストアを適切に分割する必要がある。 『Redux Toolkit』は、上記の問題を解決するために開発されたライブラリである。 1. Redux ToolkitとReact-Reduxのパッケージをプロジェクトに追加。 $ npm install --save @reduxjs/toolkit react-redux 2. Reduxストアの作成 src/app/store.js という名前のファイルを作成する。 Redux ToolkitからconfigureStore APIをインポートし、まずは空のReduxストアを作成し、エクスポートするところから始める。 app/store.js //JavaScript import { configureStore } from '@reduxjs/toolkit' export const store = configureStore({ reducer: {}, }) //TypeScript import { configureStore } from '@reduxjs/toolkit' export const store = configureStore({ reducer: {}, }) // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType<typeof store.getState> // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch これにより、Reduxストアが作成され、また、開発中にストアを検査できるように、 Redux DevToolsエクステンションが自動的に設定される。 3. ReduxストアをReactに提供 ストアを作成したら、src/index.jsにあるアプリケーションの周りにReact-Redux Provider を置くことで、Reactコンポーネントから利用可能にすることができる。 先ほど作成したReduxストアをインポートし、Appの周りにProviderを配置し、propとしてストアを渡す。 index.js //JavaScript //TypeScript import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './App' //追加 import { store } from './app/store' import { Provider } from 'react-redux' ReactDOM.render( //追加 <Provider store={store}> <App /> </Provider>, document.getElementById('root') ) 4.ReduxのState Sliceを作成 src/features/counter/counterSlice.js という名前のファイルを新規に追加し、そのファイルで、Redux ToolkitからcreateSlice APIをインポートする。 スライスを作成するには、スライスを識別するための文字列名、状態の初期値、状態を更新する方法を定義するための1つ以上のreducer関数が必要で、スライスを作成すると、生成されたReduxのアクションクリエイターとスライス全体のレデューサー関数をエクスポートすることができる。 Reduxでは、データのコピーを作成し、そのコピーを更新することで、すべての状態の更新をイミュータブルに記述する必要がある。しかし、Redux ToolkitのcreateSliceとcreateReducerのAPIは内部でImerを使って、正しいimmutable更新となる「mutating」更新ロジックを書くことができるようになっている。 features/counter/counterSlice.js //JavaScript import { createSlice } from '@reduxjs/toolkit' const initialState = { value: 0, } export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { // Redux Toolkit allows us to write "mutating" logic in reducers. It // doesn't actually mutate the state because it uses the Immer library, // which detects changes to a "draft state" and produces a brand new // immutable state based off those changes state.value += 1 }, decrement: (state) => { state.value -= 1 }, incrementByAmount: (state, action) => { state.value += action.payload }, }, }) // Action creators are generated for each case reducer function export const { increment, decrement, incrementByAmount } = counterSlice.actions export default counterSlice.reducer //TypeScript import { createSlice, PayloadAction } from '@reduxjs/toolkit' export interface CounterState { value: number } const initialState: CounterState = { value: 0, } export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { // Redux Toolkit allows us to write "mutating" logic in reducers. It // doesn't actually mutate the state because it uses the Immer library, // which detects changes to a "draft state" and produces a brand new // immutable state based off those changes state.value += 1 }, decrement: (state) => { state.value -= 1 }, incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload }, }, }) // Action creators are generated for each case reducer function export const { increment, decrement, incrementByAmount } = counterSlice.actions export default counterSlice.reducer 5.ストアにスライスのリデューサを追加 次に、カウンタスライスからreducer関数をインポートして、ストアに追加する必要がある。reducerパラメータ内にフィールドを定義することで、その状態に対するすべての更新を処理するためにこのスライスreducer関数を使用するようにストアに指示する。 app/store.js //JavaScript //TypeScript import { configureStore } from '@reduxjs/toolkit' import counterReducer from '../features/counter/counterSlice' export default configureStore({ reducer: { counter: counterReducer, }, }) 6.ReactコンポーネントでReduxのStateとActionを使う React-Reduxフックを使って、ReactコンポーネントがReduxストアと対話できるようになった。useSelector を使ってストアからデータを読み、useDispatch を使ってアクションをディスパッチすることができる。src/features/counter/Counter.js ファイルを作成して コンポーネントを作成し、そのコンポーネントを App.js にインポートして 内にレンダリングする。 features/counter/Counter.js //JavaScript import React from 'react' import { useSelector, useDispatch } from 'react-redux' import { decrement, increment } from './counterSlice' export function Counter() { const count = useSelector((state) => state.counter.value) const dispatch = useDispatch() return ( <div> <div> <button aria-label="Increment value" onClick={() => dispatch(increment())} > Increment </button> <span>{count}</span> <button aria-label="Decrement value" onClick={() => dispatch(decrement())} > Decrement </button> </div> </div> ) } //TypeScript import React from 'react' import { RootState } from '../../app/store' import { useSelector, useDispatch } from 'react-redux' import { decrement, increment } from './counterSlice' export function Counter() { const count = useSelector((state: RootState) => state.counter.value) const dispatch = useDispatch() return ( <div> <div> <button aria-label="Increment value" onClick={() => dispatch(increment())} > Increment </button> <span>{count}</span> <button aria-label="Decrement value" onClick={() => dispatch(decrement())} > Decrement </button> </div> </div> ) } これで、「増量」「減量」ボタンをクリックすることができる。 対応するReduxのアクションがストアにディスパッチされます。 カウンタースライスのレデューサーがアクションを見て、状態を更新します。 Counterコンポーネントは、ストアから新しい状態の値を見て、新しいデータで自分自身を再レンダリングする。 学んだこと configureStoreでReduxストアを作成する configureStoreは、名前付き引数としてreducer関数を受け取ります。 configureStoreは自動的に良いデフォルト設定でストアをセットアップする。 ReduxストアをReactアプリケーションのコンポーネントに提供する。 React-Redux の コンポーネントを の周りに配置する。 Reduxストアをとして渡す。 Reduxの "スライス "レデューサーをcreateSliceで作成する。 参考サイト HookとRedux ToolkitでReact Reduxに入門する Redux Toolkit
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nodemon【Node.jsアプリケーションを自動的に再起動ツール】

Nodemonとは… nodemonは、ディレクトリ内のファイルの変更を検出すると、自動的にnodeアプリケーションを再起動することで、node.jsベースのアプリケーションの開発を支援するツールです。 インストール方法 npmまたはyarnを利用してインストールすることができます。 開発環境のみで使用するので、ローカルインストールの方法でいいと思います。 グローバルインストール npm install -g nodemon yarn global add nodemon ローカルインストール npm install nodemon --save-dev yarn add nodemon --dev ローカルにインストールすると、コマンドラインから直接nodemonコマンドを使用できません。 package.json の scripts に設定すると、npmコマンド で起動できるようになります。 以下ではnpm devStartで利用できるように設定しました。 "scripts": { "devStart": "nodemon server.js", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, 起動してみる >npm run devStart > sample-api@0.1.0 devStart > nodemon server.js [nodemon] 2.0.15 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `react-scripts start server.js` nodemon経由でserver.jsファイルが起動されました。 手動で再起動させたい時はrsでできます。 変更があると 以下のように自動でコマンドがはしり、コンパイルしてくれます。 Compiling..... Compiled successfully! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】undefinedについてまとめみた!

undefinedとは undefiendは、「未定義」である事を示す値です。 undefinedになるケース 以下がundefinedになるケースです。 - 初期化されていない変数の値 - オブジェクトに指定されていないキーの呼び出し - 関数のreturnを書かなかった場合の戻り値 - 引数のある関数を呼び出すときに引数を与えなかった場合の値 以下がコードになります。 Javascriptを実行出来る環境で試してみてください!! // 初期化されていない変数の値 let testUndefined; console.log(testUndefined); // undefined // オブジェクトに指定されていないキーの呼び出し const test = {}; console.log(test.name); // undefined // 関数の`return`を書かなかった場合の戻り値 function testFunc() {} console.log(testFunc()); // undefined // 引数のある関数を呼び出すときに引数を与えなかった場合の値 function emptyFunc(param) { console.log(param); // undefined } // 引数になにも与えない emptyFunc(); undefinedとnullは似ているが同じではない 同じであるケース 真偽値で判断するときは、両方falseである プロパティを呼び出そうとするとエラーになる 同じでないケース 厳密等価演算子で比較するとfalseになる // falseになる console.log(undefined === null); // false // 厳密でない場合は、trueになる console.log(undefined == null); // true 最後に プログラムを書く際は、undefinedやnullはバグの原因になるので、無闇に増やさないようにしましょう!! 先日購入した以下の本を参考にさせて戴きました! ステップアップ javascript
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】ヘッダーの作成方法

はじめに  本記事は、プログラミング初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。  そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。  間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。 ヘッダーの作成方法 1.ヘッダーを作成する Header.jsx import * as React from 'react'; import { useState } from 'react'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import Toolbar from '@mui/material/Toolbar'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import Menu from '@mui/material/Menu'; import MenuIcon from '@mui/icons-material/Menu'; import Container from '@mui/material/Container'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import Tooltip from '@mui/material/Tooltip'; import MenuItem from '@mui/material/MenuItem'; import { useNavigate, Link } from 'react-router-dom'; import LogoIcon from '../images/LogoIcon.png'; import Cookies from 'js-cookie'; import { signOut } from '../lib/api/Auth' const pages = [ { label: 'TODO', link: '/todo' }, { label: 'RECORD', link: '/sign_up' } ] const settings = [ { label: 'マイページ', link: '/todo' }, { label: 'ログアウト', link: '/sign_up'} ]; export const Header = () => { const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElUser, setAnchorElUser] = React.useState(null); const [loading, setLoading] = useState(true) const [isSignedIn, setIsSignedIn] = useState(false) const [currentUser, setCurrentUser] = useState() // const { loading, isSignedIn, setIsSignedIn, currentUser } = useContext(AuthContext);¥ const navigate = useNavigate(); const handleOpenNavMenu = (event) => { setAnchorElNav(event.currentTarget); }; const handleOpenUserMenu = (event) => { setAnchorElUser(event.currentTarget); }; const handleCloseNavMenu = () => { setAnchorElNav(null); }; const handleCloseUserMenu = () => { setAnchorElUser(null); }; const handleSignOut = async (e) => { try { const res = await signOut(); if (res.data.success === 200) { Cookies.remove('_access_token'); Cookies.remove('_client'); Cookies.remove('_uid'); setIsSignedIn(false); navigate('/sign_in'); console.log('succeeded in sign out!!'); } else { console.log('failed in sign out'); } } catch (err) { console.log(err); console.log('failed in sign out') } }; return ( <AppBar position="static"> <Container maxWidth="xl"> <Toolbar disableGutters> <Typography variant="h6" noWrap component="div" sx={{ mr: 2, display: { xs: 'none', md: 'flex' } }} > <Link to="/todo" style={{ textDecoration: 'none', color: "white" }}> <img src={`${LogoIcon}`} style={{ display: 'block' }} /> </Link> </Typography> <Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}> <IconButton size="large" aria-label="account of current user" aria-controls="menu-appbar" aria-haspopup="true" onClick={handleOpenNavMenu} color="inherit" > <MenuIcon /> </IconButton> <Menu id="menu-appbar" anchorEl={anchorElNav} anchorOrigin={{ vertical: 'bottom', horizontal: 'left', }} keepMounted transformOrigin={{ vertical: 'top', horizontal: 'left', }} open={Boolean(anchorElNav)} onClose={handleCloseNavMenu} sx={{ display: { xs: 'block', md: 'none' }, }} > {pages.map((page) => ( <MenuItem key={page.label} label={page.label} component={Link} to={page.link} onClick={handleCloseNavMenu} > <Typography textAlign="center">{page.label}</Typography> </MenuItem> ))} </Menu> </Box> <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }} > あああああ </Typography> <Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex', justifyContent: 'center' }}}> {pages.map((page) => ( <Button key={page.label} label={page.label} component={Link} to={page.link} onClick={handleCloseNavMenu} sx={{ my: 2, color: 'white', display: 'block' }} > {page.label} </Button> ))} </Box> <Box sx={{ flexGrow: 0 }}> <Tooltip title="Open settings"> <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}> <Avatar alt="Remy Sharp" src="/static/images/avatar/2.jpg" /> </IconButton> </Tooltip> <Menu sx={{ mt: '45px' }} id="menu-appbar" anchorEl={anchorElUser} anchorOrigin={{ vertical: 'top', horizontal: 'right', }} keepMounted transformOrigin={{ vertical: 'top', horizontal: 'right', }} open={Boolean(anchorElUser)} onClose={handleCloseUserMenu} > <MenuItem key="my-page" label="マイページ" component={Link} to='/sign_up' onClick={handleCloseUserMenu} > <Typography textAlign="center">マイページ</Typography> </MenuItem> <MenuItem key="sign-out" label="ログアウト" onClick={handleCloseUserMenu, handleSignOut} > <Typography textAlign="center">ログアウト</Typography> </MenuItem> </Menu> </Box> </Toolbar> </Container> </AppBar> ); }; 2.ルーターの設定をする Route.jsx import React from 'react' import { BrowserRouter, Routes, Route } from 'react-router-dom' import { Header } from '../components/Header' import { Page404 } from '../components/Page404' import { PageTodo } from '../components/PageTodo' import { SignIn } from "../components/SignIn" import { SignUp } from "../components/SignUp" export const Router = () => { return ( <BrowserRouter> <Header /> <Routes> <Route path="sign_in" element={<SignIn />} /> <Route path="sign_up" element={<SignUp />} /> <Route path="todo" element={<PageTodo />} /> <Route path="*" element={<Page404 />} /> </Routes> </BrowserRouter> ) } 注. react-router-domv6で記述しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React-Redux のソースコード を読んでみる

react-redux を読んでみたので、そのメモ。 前回の redux のソースコードを読んだ記事の続きです。 コード読むのに慣れていないので、間違っている部分もあるかもしれません。あったらご指摘ください。 対象読者 redux を知っている人。 react-redux の connect connect 部分が気になったので、そこを読んでみました。 まず、connect は connect(mapStateToProps, mapDispatchToProps)(コンポーネント名) のようにして書きますが、この ()() の部分はクロージャーと呼ばれる書き方になっていて、 const appComponent = connect(mapStateToProps, mapDispatchToProps) appComponent(コンポーネント名) と同じ書き方になります。 その中身が、ここのソースコードに書いてあります。 ソースコードから、connect 部分だけを取り出すと、 src/components/connect.tsx ・・・省略 function connect< ・・・省略 型 >( mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, State>, mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>, mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>, ・・・省略 ) ・・・省略 const Context = context ・・・省略 (mapStateToProps, mapDispatchToProps の処理) const shouldHandleStateChanges = Boolean(mapStateToProps) const wrapWithConnect: AdvancedComponentDecorator< TOwnProps, WrappedComponentProps > = (WrappedComponent) => { ・・・省略 function ConnectFunction<TOwnProps>( props: InternalConnectProps & TOwnProps ) { ・・・省略 ・・・ここから 描画 const renderedWrappedComponent = useMemo(() => { return ( // @ts-ignore <WrappedComponent {...actualChildProps} ref={reactReduxForwardedRef} /> ) }, [reactReduxForwardedRef, WrappedComponent, actualChildProps]) const renderedChild = useMemo(() => { if (shouldHandleStateChanges) { return ( <ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider> ) } return renderedWrappedComponent }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) return renderedChild } const _Connect = React.memo(ConnectFunction) ・・・省略 型 const Connect = _Connect /* 省略 */ Connect.WrappedComponent = WrappedComponent ・・・省略 return hoistStatics(Connect, WrappedComponent) } return wrapWithConnect } export default connect as Connect hoistStatics は オブジェクトのコピーをしているくらい で、connect の後半の () に wrapWithConnect が入るので、connect 全体では wrapWithConnect の引数の <WrappedComponent> か wrapWithConnect を children にした <ContextToUse.Provider value=> のみを返しているのが分かります。 では、 src/components/connect.tsx 489〜506行目 ・・・省略 (mapStateToProps, mapDispatchToProps の処理) の部分を見てみましょう。 ここでは、connect()() の前半の () から mapStateToProps と mapDispatchToProps を取り出して、以下の関数に当てはめています。 src/components/connect.tsx const initMapStateToProps = match( mapStateToProps, // @ts-ignore defaultMapStateToPropsFactories, 'mapStateToProps' )! const initMapDispatchToProps = match( mapDispatchToProps, // @ts-ignore defaultMapDispatchToPropsFactories, 'mapDispatchToProps' )! const initMergeProps = match( mergeProps, // @ts-ignore defaultMergePropsFactories, 'mergeProps' )! match は、 src/components/connect.tsx function match<T>( arg: unknown, factories: ((value: unknown) => T)[], name: string ): T { for (let i = factories.length - 1; i >= 0; i--) { const result = factories[i](arg) if (result) return result } return ((dispatch: Dispatch, options: { wrappedComponentName: string }) => { throw new Error( `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${ options.wrappedComponentName }.` ) }) as any } のように、factories[i](arg) の返り値を返しています。 では、factories の中身の defaultMapStateToPropsFactories と defaultMapDispatchToPropsFactories はどうなっているんでしょうか? defaultMapStateToPropsFactories は src/connect/mapDispatchToProps.tsに、 defaultMapDispatchToPropsFactories は src/connect/mapStateToProps.ts に、export default で定義されているんですが、それぞれで共通して使われているのが src/connect/wrapMapToProps.ts にある関数 wrapMapToPropsConstant と wrapMapToPropsFunc です。 ここでは、wrapMapToPropsFunc を見てみましょう。 src/connect/wrapMapToProps.ts export function wrapMapToPropsFunc<P = AnyProps>( mapToProps: MapToProps, methodName: string ) { return function initProxySelector( dispatch: Dispatch, { displayName }: { displayName: string } ) { const proxy = function mapToPropsProxy( stateOrDispatch: StateOrDispatch, ownProps?: P ): MapToProps { return proxy.dependsOnOwnProps ? proxy.mapToProps(stateOrDispatch, ownProps) : proxy.mapToProps(stateOrDispatch, undefined) } // allow detectFactoryAndVerify to get ownProps proxy.dependsOnOwnProps = true proxy.mapToProps = function detectFactoryAndVerify( stateOrDispatch: StateOrDispatch, ownProps?: P ): MapToProps { proxy.mapToProps = mapToProps proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) let props = proxy(stateOrDispatch, ownProps) ・・・省略 return props } return proxy } } 読みづらいですが、 src/connect/wrapMapToProps.ts 92行目 proxy.mapToProps = mapToProps で、proxy の mapToProps に wrapMapToPropsFunc の引数の mapToProps を代入しているのが分かると思います。 この mapToProps の中に、下のような mapStateToProps と mapDispatchToProps が入ります。 function mapStateToProps(state) { return { data: state.hoge }; } function mapDispatchToProps(dispatch) { return { hoge: () => dispatch({ type: Actions.DISPATCH_HOGE, hoge: true, }), } }; これで、 src/components/connect.tsx const initMapStateToProps = match(・・・省略) const initMapDispatchToProps = match(・・・省略) const initMergeProps = match(・・・省略) に無事に、返り値が入りました。 これらの initMapStateToProps と initMapDispatchToProps と initMergeProps は、selectorFactoryOptions に代入され、ConnectFunction 内の childPropsSelector で src/connect/selectorFactory.ts の finalPropsSelectorFactory に渡されます。 src/components/connect.tsx const childPropsSelector = useMemo(() => { return defaultSelectorFactory(store.dispatch, selectorFactoryOptions) }, [store]) src/connect/selectorFactory.ts export default function finalPropsSelectorFactory ( dispatch: Dispatch<Action>, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ){ const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) ・・・省略 return pureFinalPropsSelectorFactory(mapStateToProps!, mapDispatchToProps, mergeProps, dispatch, options) } ここの返り値の pureFinalPropsSelectorFactory の中身は下のようになります。 src/connect/selectorFactory.ts export function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, {areStatesEqual, areOwnPropsEqual, areStatePropsEqual}) { let hasRunAtLeastOnce = false let state: State let ownProps: TOwnProps let stateProps: TStateProps let dispatchProps: TDispatchProps let mergedProps: TMergedProps function handleFirstCall(firstState: State, firstOwnProps: TOwnProps) { state = firstState ownProps = firstOwnProps // @ts-ignore stateProps = mapStateToProps!(state, ownProps) // @ts-ignore dispatchProps = mapDispatchToProps!(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) hasRunAtLeastOnce = true return mergedProps } function handleNewPropsAndNewState(){ /* mapDispatchToProps の dependsOnOwnProps 次第で 新しい dispatchProps を merge するか決めている */ ・・・省略 mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewProps() { /* mapDispatchToProps と mapStateToProps の dependsOnOwnProps 次第で、それぞれ 新しい dispatchProps と stateProps に merge するか決めている */ ・・・省略 mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewState() { /* State に変化があったかどうかで、mergeProps するか決めている */ ・・・省略 if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleSubsequentCalls(nextState: State, nextOwnProps: TOwnProps) { const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) const stateChanged = !areStatesEqual(nextState, state) state = nextState ownProps = nextOwnProps if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() return mergedProps } return function pureFinalPropsSelector( nextState: State, nextOwnProps: TOwnProps ) { return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) } } 見てみたら、hasRunAtLeastOnce が false の1回目は mapStateToProps と mapDispatchToProps を使ってできた Props を merge して mergedProps として返し、hasRunAtLeastOnce が true の2回目以降は prop と state の変化次第で 違う mergedProps を返しています。 上で出てきた childPropsSelector は、actualChildPropsSelector → actualChildProps に引き継がれ、actualChildProps から <WrappedComponent> の props に渡されます。 ここまでの流れで、connect で描画がされる部分と、connect の mapStateToProps と mapDispatchToProps が 描画された部分に props として渡される部分まで見てみました。 一旦ここまで。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む