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

React Hooksまとめ

はじめに それぞれのhooksの構文をはじめ、useStateのlazy-initial-stateやfunctional-updates、useEffectの実行タイミングなど個人的に忘れてしまいがちな事をまとめてみました。 useState useState(初期値) => [state, state更新用の関数] state更新用の関数を実行しstateが更新されると再レンダリングされる import React, { useState } from "react"; export const Example: React.FC = () => { // stateの初期値を引数で受け取り、stateとstate更新用の関数を返す const [count, setCount] = useState(0); const handleClick = () => { /** * state更新用の関数を実行し、新しいStateに更新している。 * 新しいstateに更新されると、このコンポーネントは再レンダリングされる */ setCount(count + 1); }; return ( <div> <p>You clicked {count} times</p> <button onClick={handleClick}>Click me</button> </div> ); }; 1つのコンポーネント内で2つ以上のuseStateを使用することが可能 初期値には関数も渡すことができる(lazy-initial-state) 例えば初期値の算出に高い処理コストが必要な場合に、lazy-initial-stateを用いない場合はレンダリングのたびにその処理が実行される。しかしlazy-initial-stateを用いれば初回のみ実行される。 import React, { useState } from "react"; function calculateSomethingExpensive(state: number) { console.log("hoge"); // ここでコストが高い処理がなされる return state; } export const Example: React.FC = () => { // Bad: レンダリングのたびにcalculateSomethingExpensiveが実行されてしまう // const [count, setCount] = useState(calculateSomethingExpensive(0)); // Good: 初回レンダリング時にのみcalculateSomethingExpensiveが実行される const [count, setCount] = useState(() => calculateSomethingExpensive(0)); const handleClick = () => { setCount(count + 1); }; return ( <div> <p>You clicked {count} times</p> <button onClick={handleClick}>Click me</button> </div> ); }; state更新用の関数には、(現在のstate) => 次のstateのような形式の関数を渡すことができる(functional-updates) 古い値を参照しないようにできる import React, { useState } from "react"; export const Example: React.FC = () => { const [count, setCount] = useState(0); const handleClick = () => { // Bad: タブルクリックされるとYou clicked 1 timesと表示される setTimeout(() => setCount(count + 1), 1000); }; const functionalUpdates = () => { // Good: タブルクリックされるとYou clicked 2 timesと表示される setTimeout(() => setCount((nowCount) => nowCount + 1), 1000); }; return ( <div> <p>You clicked {count} times</p> <button onClick={handleClick}>Click me</button> <button onClick={functionalUpdates}>functional-updates</button> </div> ); }; クラスコンポーネントのstateとuseStateの違い useStateのstateはオブジェクトでなくてもよい クラスコンポーネントのthis.setStateと似ているが違う useStateの場合はオブジェクトの部分更新ができず更新関数に渡された値が次のstateになる functional-updatesを用いてマージしてあげるのがよさそう import React, { useState } from "react"; interface User { id?: number; name: string; } export const Example: React.FC = () => { const [user, setUser] = useState<User>({ id: 1, name: "hoge" }); const handleClick = () => { // Bad: userは{ id: 1, name: "fuga" }とならず{ name: "fuga" }になる setUser({ name: "fuga" }); }; const functionalUpdates = () => { // Good: userは{ id: 1, name: "fuga" }になる setUser((nowState) => { return { ...nowState, ...{ name: "fuga" } }; }); }; return ( <div> <p>ID: {user.id}</p> <p>Name: {user.name}</p> <button onClick={handleClick}>Change Fuga</button> <button onClick={functionalUpdates}>functional-updates</button> </div> ); }; useEffect useEffect(fn, 依存する配列) 第二引数をどうするかによって動作が変わる useEffect(fn) 初回の画面へのDOM反映後に実行される stateの更新などによって新しいDOMが画面へ反映されるたびに実行される useEffect(fn, []) 初回の画面へのDOM反映後のみに実行される useEffect(fn, [deps]) 初回の画面へのDOM反映後に実行される depsが変更されるたびに実行される stateの変更に伴う処理や副作用を伴う処理を関心ごとに分離できるように クラスコンポーネントの場合、ライフサイクルメソッド内でまったく関係のない複数の処理がなされている。しかも関連ある処理が別ライフサイクルメソッドに跨ったりしている 1 つのコンポーネント内で 2 つ以上のuseEffectを使用することが可能 初回の画面へのDOM反映後に初めて実行される componentDidMountは(DOMは構築済みなのでrefでElementにアクセスはできるが)画面へのDOM反映前に実行される。なのでcomponentDidMount内で重い処理をするとその分、画面へのDOM反映が遅れる 第一引数の関数が関数を返せば、その返された関数はクリーンアップ後に実行される 実行タイミング ①コンポーネントのアンマウント時 ②第一引数の副作用が再実行される前に このクリーンアップ関数は、画面へのDOM反映前に実行される(但し、コンポーネントのアンマウントの際は、DOM構築前に実行される) ここで重い処理をするとその分、画面へのDOM反映が遅れる React 17からはこの実行タイミングが変更される どちらのケースでも画面へのDOM反映後に実行されるように useLayoutEffect useLayoutEffect(fn, 依存する配列) 構文はuseEffectと同じ 初回の画面へのDOM反映前に実行される DOMは構築済みなのでref経由でElementにアクセスはできる useEffectでDOMの上書きをすると(useEffectは画面へのDOM反映後に実行されるので)一瞬上書き前の状態が見えてしまうので、そのようなケースを避けたい場合に使用する ここで重い処理をするとその分、画面へのDOM反映が遅れる useCallback useMemo(fn, 依存する配列) => メモ化されたfn メモ化されたコールバックを返す 第二引数をどうするかによって動作が変わる 第二引数の配列を省略した場合は、初回レンダリング時と再レンダリングのたびにメモ化されたコールバックを返す 第二引数の配列に[]を指定した場合は、初回レンダリング時のみメモ化されたコールバックを返す 第二引数の配列に依存する値を指定した場合は、初回レンダリング時とその配列の値が変更されるたびにメモ化されたコールバックを返す useMemo useMemo(() => 値, 依存する配列) => メモ化された値 メモ化された値を返す 公式Docs1を見る限り、なんでもかんでもuseMemoを使用する。というよりかは、パフォーマンス最適化が必要であれば使用すると言うスタンスがよさそう useEffectと同様で第二引数に配列を渡すことができる。配列を指定した場合は、配列内の値が変更された場合のみ新しい値を返すようになる 第二引数をどうするかによって動作が変わる 第二引数の配列を省略した場合は、初回レンダリング時と再レンダリングのたびにメモ化された値を返す 第二引数の配列に[]を指定した場合は、初回レンダリング時のみにメモ化された値を返す 第二引数の配列に依存する値を指定した場合は、初回レンダリング時とその配列の値が変更されるたびにメモ化された値を返す useCallback(fn, deps) は useMemo(() => fn, deps) と等価 useContext 指定したcontextを取得できる HOCとかで書いてた部分が簡素にかけるようになる useRef useRef(初期値) => ミュータブルなRefオブジェクト refはイベントハンドラや副作用内でだけ書き換えるように(レンダーの最中にrefを書き換えることはなるべくしないように) ユースケース①: レンダリングには直接影響しない状態の保持 クラスコンポーネントのインスタンス変数と似たよう使い方 こちらの例ではuseRefを使ってcomponentDidMount相当なことを行えるようにしています。 ボタンをクリックするとコンソール上ではuseEffectのみ表示されます。(hogeは表示されない) import React, { useEffect, useRef, useState } from "react"; interface User { id: number; name: string; } export const Example: React.FC = () => { const [user, setUser] = useState<User | null>(null); const mount = useRef(false); useEffect(() => { console.log("useEffect"); if (!mount.current) { // 初回のみuseEffectが実行されるように console.log("hoge"); setUser({ id: 1, name: "hoge" }); mount.current = true; } }, [user]); const handleChangeName = () => { setUser((nowUser) => { return { ...nowUser, ...{ name: "fuga" } }; }); }; if (!user) { return null; } return ( <div> <p>ID: {user.id}</p> <p>Name: {user.name}</p> <button onClick={handleChangeName}>change name</button> </div> ); }; ユースケース②: DOMエレメントの取得 import React, { useEffect, useRef, useState } from "react"; interface User { id: number; name: string; } export const Example: React.FC = () => { const [user, setUser] = useState<User>({ id: 1, name: "hoge" }); const pRef = useRef<HTMLParagraphElement>(); useEffect(() => { console.log(pRef.current?.textContent); // => ID: 1 }, []); return ( <div> <p ref={pRef}>ID: {user.id}</p> <p>Name: {user.name}</p> </div> ); }; useReducer useReducer((state, action) => newState, 初期値, lazy-initial-state用の関数) => [state, dispatch] useStateと同じようにstateの管理ができる useReducerの第三引数は省略可 useStateとの使い分け 明確な使い分けの基準はなさそうなのでチームで話合ったほうがよさそう 公式的には以下の場合はuseReducerをおすすめしている ①複数の値にまたがる複雑なstateロジックがある場合(例えばあるstateを変更する際に別のstateを参照したり利用したりする場合などはuseReducerで1つのstateとして扱うなど) ②前のstateに基づいて次のstateを決める必要がある場合 useImperativeHandle useImperativeHandle(親から渡されるref, () => カスタマイズされたインスタンス値, 依存する配列) 子コンポーネントをref経由で取得する際にインスタンス値をカスタマイズできる forwardRefと組み合わせて使う 極力使わないように2 import React, { forwardRef, useImperativeHandle, useRef, useState } from "react"; interface Handler { value: string; } const Child = forwardRef<Handler>((_, ref) => { const [value, setValue] = useState(""); useImperativeHandle( ref, () => { return { value, }; }, [value] ); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setValue(e.target.value); }; return <input value={value} onChange={handleChange} />; }); Child.displayName = "Child"; export const Example: React.FC = () => { const inputRef = useRef<Handler>(); const handleClick = () => { // クリックされるたびにChildコンポーネントのその時のinput valueが表示される console.log(inputRef.current?.value); }; return ( <> <Child ref={inputRef} /> <button onClick={handleClick}>get Input Value</button> </> ); }; Custom Hooks コンポーネントのstateとstateを用いた処理などを関数として切り出すことができる ステートフルなロジックの再利用が容易になる render-propsやHOCでも可能だがコンポーネントの階層が深くなってしまいつらいし手軽でない 参考 https://ja.reactjs.org/docs/hooks-reference.html https://ja.reactjs.org/docs/hooks-faq.html https://kentcdodds.com/blog/use-state-lazy-initialization-and-function-updates https://ja.reactjs.org/docs/hooks-reference.html#usememo ↩ https://ja.reactjs.org/docs/hooks-reference.html#useimperativehandle ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Firebase】ReactプロジェクトでFirebaseを扱う

概要 Reactのプロジェクトで、Firebaseを扱う方法をまとめます。 ※ 処理をいくつかピックアップして記載していますが、ドキュメントが充実しているので、そちらを参照したほうがいいかもしれないです。 この記事は、以下のコースの受講に伴って、備忘録として書いています。詳しく知りたい方は、是非受講してみてください。 環境 Windows10 VSCode npm セットアップ Firebaseプロジェクトの作成 Firebaseのコンソールから、プロジェクトを追加します。 アプリの種類を選択します。Webサービスを作成する場合は </> を選択します。 ⚙ - [プロジェクトを設定]で、SDKの設定と構成を確認しておきます。 インストール VSCodeでアプリケーションのプロジェクトを開き、ターミナルで以下を実行します Reactプロジェクトの作成 npx create-react-app . --template typescript firebaseのインストール npm i firebase 設定ファイルの作成 プロジェクト直下に、環境変数ファイル.envを作成します。 .env REACT_APP_FIREBASE_APIKEY="apiKey" REACT_APP_FIREBASE_DOMAIN="authDomain" REACT_APP_FIREBASE_DATABASE="https://PROJECT_ID.firebaseio.com" REACT_APP_FIREBASE_PROJECT_ID="projectId" REACT_APP_FIREBASE_STORAGE_BUCKET="storageBucket" REACT_APP_FIREBASE_SENDER_ID="messagingSenderId" REACT_APP_FIREBASE_APP_ID="appId" Reactプロジェクトの場合、REACT_APP_としていすることで、自動的に環境変数として扱われます。 値は、FirebaseコンソールのSDKの設定と構成で確認したものを入力します。 REACT_APP_FIREBASE_DATABASE はコンソールに表示されていないので、アドレスの PROJECT_ID を置き換えた値を入力します。 src配下に、環境変数を読み込んで、使用するインスタンスを返すファイルを作成します。 firebase.ts import firebase from 'firebase/app'; import 'firebase/auth'; import 'firebase/firestore'; import 'firebase/storage'; const firebaseConfig = { apiKey: process.env.REACT_APP_FIREBASE_APIKEY, authDomain: process.env.REACT_APP_FIREBASE_DOMAIN, databaseURL: process.env.REACT_APP_FIREBASE_DATABASE, projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID, appId: process.env.REACT_APP_FIREBASE_APP_ID }; const firebaseApp = firebase.initializeApp(firebaseConfig); export const db = firebaseApp.firestore(); export const auth = firebaseApp.auth(); export const storage = firebaseApp.storage(); export const provider = new firebase.auth.GoogleAuthProvider(); ここでは、データベース(db)、認証(auth)、ストレージ(storage)、googleアカウント認証(provider)のインスタンスをexportしています。 認証 認証の設定 Firebaseコンソールの [Authentication] で、使用する認証方法を有効にします。 ログイン・ログアウトの処理 ログイン const signInEmail = async () => { await auth.signInWithEmailAndPassword(email, password); }; ドキュメント:ログイン 新規登録してログイン(レジスター) const signUpEmail = async () => { await auth.createUserWithEmailAndPassword(email, password); }; ドキュメント:レジスター Googleアカウントでログイン const signInGoogle = async () => { // ポップアップでgoogleのサインインを表示する await auth.signInWithPopup(provider).catch(err => alert(err.message)); }; ドキュメント:Googleログイン ログアウト auth.signOut() ドキュメント:ログアウト ユーザーに関する処理(あれこれ) こちらの ドキュメント では、以下の操作をする場合の説明があります。 ユーザーを作成する(上記のレジスター) 現在ログインしているユーザーを取得する ユーザーのプロフィールを取得する ユーザーのプロバイダ別のプロフィール情報を取得する ユーザーのプロフィールを更新する ユーザーのメールアドレスを設定する ユーザーに確認メールを送信する ユーザーのパスワードを設定する パスワードの再設定メールを送信する ユーザーを削除する ユーザーを再認証する ストレージ ストレージの設定 Firebaseコンソールの [Storage] から、デフォルトストレージを作成します。 アップロード・保存したファイルのパスの取得 アップロード // testフォルダに画像を保存する(フォルダがない場合は自動で作成される) await storage.ref(`test/${fileName}`).put(inputImage); こちらの例では、ストレージのtestフォルダを参照し(ない場合は作成し)、fileNameという名前でinputImageをアップロードします。 fileNameのファイルがすでにストレージに存在する場合は上書きされるので、一意な名前をつける必要があります。(Tips参照) URL取得 url = await storage.ref('test').child(fileName).getDownloadURL(); Tips 疑似的な一意な文字列を作成する let S = [...Array(26)].map((_, i) => String.fromCharCode(i + 97)).join(''); // a-z S += S.toUpperCase(); // A-Z S += '0123456789'; // 0-9 const N = 16; // 文字数 const randomChar = Array.from(crypto.getRandomValues(new Uint32Array(N))) .map(n => S[n % S.length]) .join(''); この例では、a-z A-Z 0-9の文字列に対し、ランダムに16個を連結した文字列を作成します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】create-react-appをPWA化するのに必要な最低限の知識

現場でWebアプリ(jQuery)とモバイルアプリ(React Native)を並行して開発している身として、Webアプリをモバイル化できるというPWAに以前からとても憧れていました。 最近Reactまわりを学習し直していることもあり、この機会にcreate-react-appでPWAを作成するケースについて学んでみることにしました。 PWA(Progressive Web Apps)とは PWA(Progressive Web Apps)とは、ネイティブアプリと同じ勝手で使うことができるWebアプリのことをいいます。 有名どころだと、Uber, Twitter, InstagramのWebアプリで使用されており、以下のサイトでPWAのその他の例もいろいろ確認することができます。 Webアプリなのに以下のことができるということがわかれば、PWAがいかにすごい技術だということがイメージできるかと思います。 ホーム画面にアイコンとして追加できる オフラインで開くことができる スマホのカメラやマップとの連携、プッシュ通知機能を使える ブラウザによって使える機能は変わってくるのですが、以下のサイトで自分のブラウザがどの機能に対応しているかを確認することができます。 例えば、Chromeについては以下のようになっています。 大体の機能に対応していますが、SMS/MMSなどの一部の機能には対応していません。 PWAを構成する三要素 WebアプリをPWAと呼ぶためには以下の三要素が必要となります。 それぞれの要素を順番に説明します。 HTTPS Manifest Service Worker HTTPS 位置情報やService WorkerなどのPWAに関連する機能のほとんどは、アプリがHTTPSを使用して読み込まれた場合にのみ利用できます。 HTTPSについて詳しくない方は、クライアント(Webアプリ)とサーバ間で暗号化されたデータを安全にやりとりする仕組みだと覚えておけば大丈夫です。 さらに詳しく知りたい場合は、以前私が書いた記事を参考にしていただければ幸いです。 Manifest Manifestは、アプリがユーザにどのように表示されるかを制御するJSONファイルです。 アプリの名前、概要、アイコン、起動時の画面の向きなど、アプリの動作以外に関する詳細が記述されています。 manifest.json { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": "./index.html", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } 以下は、Twitterにおけるdev-toolのApplicationタブの表示です。 Manifestに記述した内容が反映されていることを確認することができます。 Service Workers Service Workerは、Webアプリとサーバのネットワークを仲介するプロキシのような役割をもっており、Webアプリのオフライン対応やバックグラウンドでの同期処理、プッシュ通知などを提供する基盤となります。 Webアプリ(ブラウザ)がサーバからデータを取得する際に、初めて取得するものであればService Workerがそのデータをキャッシュに保存します。 二度目以降の通信ではキャッシュのデータを利用するので、オフライン(サーバとつながっていない)でもWebアプリを使い続けることができます。 Service WorkerのようなプロキシはWebアプリとサーバ間の通信に割り込むため、この機能が悪用されてサーバに攻撃が加えられてしまう恐れがあります。 そのようなリスクを避けるため、前述のHTTPSによる暗号化通信が必須となります。 また、Manifestと同様に、Service Workerの設定もdev-toolのApplicationタブより確認することができます。 Cache Storageよりキャッシュの中身も確認することができます。 create-react-appプロジェクトでPWAを新規作成 create-react-appにはworkbox-webpack-pluginというものが含まれているので、他のツールをインストールすることなく簡単にService Workerをつくることができます。 また、最初からPWAアプリをつくる目的であれば、service-worker.jsとその他のモックを作ってくれるテンプレートを使用することができます。 JavaScriptの場合 npx create-react-app [プロジェクト名] --template cra-template-pwa TypeScriptの場合 npx create-react-app [プロジェクト名] --template cra-template-pwa-typescript プロジェクトを作成すると以下のようなフォルダ構成となります。 manifest.jsonやservice-worker.ts(.js)が作成されています。 index.tsxの中身をみると以下のようになっています。 serviceWorkerRegistration.unregister();をserviceWorkerRegistration.register();に置き換えるだけでPWA化完了です。 index.tsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorkerRegistration from './serviceWorkerRegistration'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://cra.link/PWA serviceWorkerRegistration.unregister(); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); 既存create-react-appプロジェクトのPWA化 既存のプロジェクトをPWA化したい場合は以下のステップを踏めば大丈夫です。 必要なファイルをコピーするためにnpx create-react-app [プロジェクト名] --template cra-template-pwaで適当なプロジェクトを作成 service-worker.jsとserviceWorkerRegistration.jsをコピーして既存プロジェクトのsrc`直下に配置 package.jsonのworkbox-で始まるdependenciesをコピーして既存プロジェクトのpackage.jsonにペースト npmかyarnでパッケージを再インストール おわりに どうやら5月はフロントエンド強化月間らしい... 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[簡単]モダン構成なSPAで作るチュートリアル⑥(編集・削除機能)

SPAで作るタスク管理アプリのチュートリアル - 編集・削除機能 今回は第④弾でReactで一覧テーブル作成する部分をやっていくで ①環境構築(Docker/Laravel/React.js/Material-UI) ②React側でルーティング設定 ③API取得してReactで一覧テーブル作成 ④API取得してReactで一覧表示 ⑤新規登録機能 ⑥編集・削除機能 今回は編集機能と削除機能をやっていくで 編集機能 こんな感じで一覧の編集ボタンを押すと該当のするタスクの編集が行える画面に遷移できるようにするで バックエンド側の実装 シンプルなCRUDだからバックエンドはそんなにややこしくないな。 ルーティングは下記のように記載 api.php Route::group(['middleware' => 'api'], function(){ Route::get('posts', 'App\Http\Controllers\Api\PostController@index'); Route::post('post/create', 'App\Http\Controllers\Api\PostController@create'); Route::post('edit', 'App\Http\Controllers\Api\PostController@edit'); //追記 Route::post('update', 'App\Http\Controllers\Api\PostController@update'); //追記 }); コントローラー PostController.php   // 編集画面に遷移するためのアクション public function edit(Request $request) { $post = Post::find($request->id); return $post; } //データを更新するためのアクション public function update(Request $request) { $post = Post::find($request->id); $post->name = $request->name; $post->content = $request->content; $post->save(); $posts = Post::all(); return $posts; } バックエンドはここまで 編集画面用のコンポーネント作成 入力フォームを新規作成機能を作った際のコンポーネントを再利用する /pages/PostEdit.jsを下記のように作成 PostEdit.js import React, { useState, useEffect } from 'react'; import { Card } from '@material-ui/core'; import { makeStyles, createStyles } from '@material-ui/core/styles'; import PostFrom from '../components/PostFrom'; const useStyles = makeStyles((theme) => createStyles({ card: { margin: theme.spacing(5), padding: theme.spacing(3), }, })); function PostEdit(props) { const classes = useStyles(); const params = props.match.params; const [editData, setEditData] = useState({name:'', content:''}); useEffect(() => { getEditData(); }, []) function getEditData(){ axios .post('/api/edit', { id: params.id }) .then(res => { setEditData(res.data); }) .catch(() => { console.log('通信に失敗しました'); }); } function updatePost(){ if(editData == ''){ return; } //入力値を投げる axios .post('/api/update', { id: params.id, name: editData.name, content: editData.content }) .then((res) => { setEditData(res.data); }) .catch(error => { console.log(error); }); } function inputChange(e){ const key = e.target.name; const value = e.target.value; editData[key] = value; let data = Object.assign({}, editData); setEditData(data); } return ( <div className="container"> <div className="row justify-content-center"> <div className="col-md-8"> <div className="card"> <h1>タスク編集</h1> <Card className={classes.card}> <PostFrom data={editData} inputChange={inputChange} btnFunc={updatePost} /> </Card> </div> </div> </div> </div> ); } export default PostEdit; ルーティングに記載 route.js import PostEdit from './pages/PostEdit'; //追記 function App() { return ( <div> <Switch> <Route path='/example' exact component={Example} /> <Route path='/' exact component={Home} /> <Route path='/post/edit/:id' exact component={PostEdit} /> //追記 Homeの一覧画面で編集ボタンをクリックすると対象のidを使ってリクエスト投げるように設定 Home.js //rows.pushの中の編集ボタンに keyとhrefを追加の編集ボタン rows.push({ name: post.name, content: post.content, editBtn: <Button color="secondary" variant="contained" key={post.id} href={`/post/edit/${post.id}`}>編集</Button>, //追加 deleteBtn: <Button color="primary" variant="contained">完了</Button>, }) 編集ボタンをクリックすると、対象のデータの編集画面に遷移できればOKや そのままデータを変更できることも確認しておくで 確認出来たらコミットしておこう # git add . # git commit -m "投稿編集機能の実装" 削除機能 完了を押したらデータが削除されるようにするで バックエンド ルーティング api.php Route::group(['middleware' => 'api'], function(){ Route::get('posts', 'App\Http\Controllers\Api\PostController@index'); Route::post('post/create', 'App\Http\Controllers\Api\PostController@create'); Route::post('edit', 'App\Http\Controllers\Api\PostController@edit'); Route::post('update', 'App\Http\Controllers\Api\PostController@update'); Route::post('delete', 'App\Http\Controllers\Api\PostController@delete'); }); //追記 コントローラー PostController.php public function delete(Request $request) { $post = Post::find($request->id); $post->delete(); $posts = Post::all(); return $posts; } apiを叩くdeletePostの定義 Home.js // createPostの下に記載 const deletePost = async (post) => { await axios .post('/api/delete', { id: post.id }) .then((res) => { this.setState({ posts: res.posts }); }) .catch(error => { console.log(error); }); } 完了ボタンのOnClickを追記 Home.js rows.push({ name: post.name, content: post.content, editBtn: <Button color="secondary" variant="contained" key={post.id} href={`/post/edit/${post.id}`}>編集</Button>, deleteBtn: <Button color="primary" variant="contained" href="/" onClick={() => deletePost(post)}>完了</Button>,//追記 }) ビルド $ make npm-dev 一覧表で完了ボタンを押し、対象のデータが削除されることを確認 コミットしておく $ git add . $ git commit -m "削除機能の実装" 完了
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactNavigationのTabNavigatorにネストされたStackNavigatorのタブを非表示にする方法

TabNavigatorにネストされたStackNavigatorのタブを非表示にする方法 公式ドキュメント的には、 TabNavigatorとStackNavigatorのネストを入れ替えて、StackNavigatorの中にTabNavigatorを入れる方法が紹介されているが、これが結構辛かったりする。 https://reactnavigation.org/docs/hiding-tabbar-in-screens/ (TabNavigatorより外側に、StackNavigatorを移動すればよいだけなので意外と簡単だが。。。) <TabNavigator> <StackNavigator> <Tabを非表示にしたいスクリーン /> <Tabは表示してもいいスクリーン1 /> </StackNavigator> <Tabは表示してもいいスクリーン2 /> </TabNavigator> ↓ <StackNavigator> /* ここに移動 */ <TabNavigator> <Tabは表示してもいいスクリーン1 /> <Tabは表示してもいいスクリーン2 /> </TabNavigator> <Tabを非表示にしたいスクリーン /> /* ここに移動 */ </StackNavigator> 他の方法はないか 他に楽な方法はないのかとググった中で、以下の方法が簡単で良さそうだった。 <Tab.Navigator tabBarOptions={stackOptions} > <Tab.Screen name={"market"} component={MarketNavigator} options={navigation => ({ // tabBarIcon: , tabBarVisible: navigation.route.state.index > 0 ? false : true })} /> </Tab.Navigator> 最後に 時間がなくてササッと書いているので、間違いがあったらすみません。。。 間違いを見つけた場合は教えて下さい! もっと良い方法をご存じの方も教えていただけると幸いです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

redux と react-redux の内部動作を解説 ~小さな “Reduxもどき” を自作してみる~

はじめに react-redux と redux と @reduxjs/toolkit のソースコードの主要部分を読んだので、redux と react-redux の内部動作について自分なりに解説してみようと思います。 また、redux を使わなくても少しコードを追加するだけで react-redux を動かせると思ったので試してみました。 注意・前提 本稿は hooks API を前提にしており、connect API のことは考慮しておりません。1 前提とするバージョンは以下の通りです。 - react-redux: 7.2.3 - redux: 4.0.4 対象読者 「Redux の基本的な動作(react-redux と関係する部分のみ解説)」まで redux と react-redux(hooks API) は使ったことがあるけど、redux と react-redux の内部的な動作は分からないという方 「“Reduxもどき” を作成して動かしてみる」以降 Redux Toolkit Quick Start のソースコードを読んだことのある方 react-redux の Provier が store(createStore の戻り値)に求めているプロパティ【必須3メソッド】 Provider.js の PropTypes を見てみると、store インスタンスのプロパティとして subscribe、dispatch、getState (以下、「必須3メソッド」と呼称) が必要であることが分かります。「必須3メソッド」についての概要は後述します。 Provider.js(一部抜粋) if (process.env.NODE_ENV !== 'production') { Provider.propTypes = { store: PropTypes.shape({ subscribe: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired, getState: PropTypes.func.isRequired, }), context: PropTypes.object, children: PropTypes.any, } } Redux の基本的な動作(react-redux と関係する部分のみ解説) State (Store内部) の変化の様子 redux では reducer((B, A) => B) を使って store 内で保持している state (以下 currentState と記載)を更新します。 単に reducer と言った時には、A、B は任意の型ですが、redux では B は currentState、A は Action です。 currentState が reducer と Action によって initialState -> State1 -> State2 と変遷する様子を図示します。 2 必須3メソッド(getState、subscribe、dispatch)の概要 ※ 分かりやすさ優先のため、説明で用いる変数名は実際の変数名とは異なる場合があります。すべてのパターンを網羅した完全な説明ではなく、あくまでも概要です。 getState store の内部変数 currentState を取得します。 Redux Toolkit Quick Start においては、currentState は { counter: { value: number } } です。 subscribe store の 内部変数3listeners4 に notify関数(後述)を“登録”(※)します。 最初に実行される useSelector hook にて実行されます。5 ※ redux の listeners は Array なので、“登録”というのは、listeners.push(notify関数) を意味します。 なお、戻り値は subscribe で登録した notify関数 を listenersから 除去する unsubscribe関数 です。 notify関数 について redux には Observer パターン が用いられていますが、redux-react にも Observer パターン が用いられていて6、redux-react は内部に lisners をもっています。 redux-react 内の lisners には useSelector hook にて checkForUpdates関数が登録されます5 (checkForUpdates関数については後述)。react-redux 内の lisners(checkForUpdates関数の配列7) を全て実行する関数が notify関数 です。 dispatch useDispatch hook で取得する、dispatch です。 大きく分けて以下の2つのことを行います。 1. currentState = reducer(currentState, action) 4 2. store 内部変数 lisners4 に登録された全ての関数を実行(※) (3. 引数の action をそのまま return) ※ redux(store)内のlisteners には、基本的には notify関数 の1つしか登録されないため、実際に実行される関数は notify関数 だけです。(ただし、react-redux 以外のコードが subscribe を使用している場合はこの限りではありません。) (useSelector 内の)checkForUpdates 関数 の概要 store.getState() で currentState を取得 (store はレンダー時に Provider(Contex)から取得) useSelector 第一引数の selector を用いて currentState から 対象データを取得し、latestSelectedState.current に保存 前回保存対象データ(※1)と 最新対象データ(2.) を比較 (デフォルト:Strict equality (===) で比較 / 第二引数 equalityFn が渡されていれば、それを利用して比較) (3.) の比較結果が false なら(対象データが前回と異なるデータなら) forceRender(※2)で強制的にコンポーネントを再レンダーする ※1 前回実行時、(2.)にて latestSelectedState.current に保存したデータ ※2 forceRender: const [, forceRender] = useReducer((s) => s + 1, 0) 必須3メソッドまとめ redux (store) react-redux lisners に登録される関数 notify(react-redux 内の listeners(checkForUpdatesたち)を全て実行する関数) checkForUpdates lisners に登録される関数の数 1つ(Provider につき1つ) useSelector 等の個数 lisners に登録された関数が実行されるタイミング dispatch 実行時 dispatch 実行時 dispatchの実行 -> listenrs(redux)の実行 -> notify関数の実行 -> lisners(react-redux)の実行 -> 全てのcheckForUpdatesの実行 という流れになります。 つまり、useSelector 等で登録された checkForUpdates 関数たちが dispatch で全部実行されるということになります。 dispatch を実行すると、useSelector の数だけ5、「selectorによるデータ取得及びデータの比較」 が実行されます。 “Reduxもどき” を作成して動かしてみる 「Redux の基本的な動作(react-redux と関係する部分のみ解説)」を踏まえて Redux を模倣した 簡単なソースコードを書いてみます。 State (Store内部) の更新方針 “Reduxもどき” では、簡略化のため、(prevState: S) => S という形の関数(以下、modifier と呼称)で store の内部変数 currentState を更新します。 currentState を変更させる関数として、 (B, A) => B8 (reducer) ではなく、 (A) => (B) => B 9 (modifierCreator) という形の関数を使います。 A は任意の型であり、Action({type: "name", payload: something}のようなオブジェクト)である必要はありません。 B は currentState です。 以下は modifier の例です。 const [count, setCount] = React.useState(0); const add = (a) => (b) => a + b // modifierCreator: (A) => (B) => B const inclement = add(1) // ? modifier: (B) => B (ここでは、(number) => number) // modifier の使い方: setCount(inclement) currentState が modifier によって initialState -> State1 -> State2 と変遷する様子を図示します。 10 必須3メソッド(getState、subscribe、dispatch)の動作方針 getState redux と同じです。(store の内部変数3 currentState を取得します。) subscribe notify関数 をlisteners配列 に push するのではなく、listener変数 に代入します。 listeners に登録されるものが基本的には notify関数 だけであることを示すためです。 戻り値は notify関数 を listener変数 から 破棄する unsubscribe関数 です。 dispatch 引数として Action ではなく、modifier ((B)=>Bの関数)を渡します。 大きく分けて2つのことを行います。 1. currentState = modifier(currentState) 2. listener変数 に代入された notify関数 を実行 必須3メソッドにおける主な相違点まとめ redux との 一番の違いは dispatch における currentState の変更方法が reducer((B, A) => B) から modifier((B) => B) になっている部分です。 redux reduxもどき getState (currentStateの取得) (相違点なし) subscribe listeners配列 に notify関数 を push listener変数 に notify関数 を 代入 dispatch currentState = reducer(currentState, action) currentState = modifier(currentState) ソースコードの変更 以上を踏まえて redux の createStore を自作してみます。 src/app/store.js の変更(createStore の自作) 変更前のソース(Github) src/app/store.js import { configureStore } from '@reduxjs/toolkit'; // redux の `createStore` を内部で利用 import counterReducer from '../features/counter/counterSlice'; export default configureStore({ reducer: { counter: counterReducer, }, }); 変更後のソース(Github) src/app/store.js import initialState from '../features/counter/counterSlice'; // counterSlice は後述 // 前述の「必須3メソッド(getState、subscribe、dispatch)の動作方針」通りに実装 const createStore = initialValue => { let currentState = initialValue; // state を初期化 let listener; // notify関数 の代入先内部変数 const store = { getState: () => currentState, subscribe: notify => { listener = notify; // notify関数 を代入 return function unsubscribe() { listener = undefined; // notify関数 を破棄 }; }, dispatch: modifier => { currentState = modifier(currentState); // state を更新 listener && listener(); // notify関数 を実行 }, }; return store; }; export default createStore(initialState); src/features/counter/counterSlice.js の変更 変更前のソース(Github) src/features/counter/counterSlice.js(コメントは省略) import { createSlice } from '@reduxjs/toolkit'; export const slice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: state => { state.value += 1; }, decrement: state => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = slice.actions; export const incrementAsync = amount => dispatch => { setTimeout(() => { dispatch(incrementByAmount(amount)); }, 1000); }; export const selectCount = state => state.counter.value; export default slice.reducer; 変更後のソース(Github) src/features/counter/counterSlice.js // import { createSlice } from '@reduxjs/toolkit'; // - import produce from 'immer'; // + // immer は redux toolkit 内部で利用されているライブラリ export const entier = { // initialState: B = { counter: { value: number } } slice ではなく、state 全体 initialState: { counter: { value: 0 }, }, // modifierCreator: A => B => B modifierCreators: { // A => B => B (immer なしの場合 / A は不要) increment: _ => state => ({ ...state, counter: { ...state.counter, value: state.counter.value + 1, }, }), // A => B => B (immer ありの場合 / A は不要) decrement: _ => produce(state => { state.counter.value -= 1; }), // A => B => B ( A は number ) incrementByAmount: amount => produce(state => { state.counter.value += amount; }), }, }; // dispatch には action creator ではなく、modifier creator の戻り値を渡す // export const { increment, decrement, incrementByAmount } = slice.actions; // - export const { increment, decrement, incrementByAmount } = entier.modifiers; // + // incrementAsync 変更なし // selectCount 変更なし // export default slice.reducer; // - export default entier.initialState; // + src/features/counter/Counter.js の変更 @reduxjs/toolkit の configureStore が返す store の dispatch は redux-thunk を含む middleware が合成された dispatch です(※)。今回実装した dispatch にはそのような合成を行っていないので、redux-thunk の合成なしで動くような書き方に変更します。 src/features/counter/Counter.js - onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))} + onClick={() => incrementAsync(Number(incrementAmount) || 0)(dispatch)} ※ この説明では知ってる人にしか分からないと思いますが、本稿では Async を扱うロジックの詳細な説明は割愛させていただきます。 デモ および オリジナルとの差分 以上3つのファイルの修正で redux なしで react-redux が動くようになりました。 デモはこちら(CodeSandbox)を参照ください。 オリジナル との 差分はこちら を参照ください。 おまけ: ramda ライブラリ を利用した場合 JavaScript プログラマのための関数型ライブラリである ramda を使うと 変更後の src/features/counter/counterSlice.js をスッキリ記述することができます。 src/features/counter/counterSlice.js import * as R from 'ramda'; const lens = R.lensPath(['counter', 'value']); export const entier = { // initialState: B = { counter: { value: number } } initialState: { counter: { value: 0 }, }, // modifierCreator: A => B => B modifierCreators: { increment: _ => R.over(lens, R.inc), decrement: _ => R.over(lens, R.dec), incrementByAmount: amount => R.over(lens, R.add(amount)), }, }; export const { increment, decrement, incrementByAmount } = entier.modifierCreators; // incrementAsync 変更なし export const selectCount = R.view(lens); export default entier.initialState; 変更後ソースコード(immer 利用バージョン)と 変更後ソースコード(ramda 利用バージョン)との差分はこちら を参照ください。 デモはこちら(CodeSandbox)を参照ください。 ramda のススメ ramda のドキュメント は検索性に優れ、全ての関数に説明と例が記載されており、しかも「Run it here」ですぐに動作を試せるという至れり尽くせりなドキュメントです。 ramda の全ての関数は破壊的な変更を行わないので、React の state の更新にもってこいです。 例えば evolve を使えば 下記の increment1 のような書き方をせずに react の state を更新できます。 import * as R from 'ramda'; const increment1 = state => ({ ...state, counter: { ...state.counter, value: state.counter.value + 1, }, }); const increment2 = state => R.evolve({ counter: { value: value => value + 1 } }, state); // 通常利用ver const increment3 = R.evolve({ counter: { value: value => value + 1 } }); // カリー化利用ver const increment4 = R.evolve({ counter: { value: R.inc } }); // ramda フル活用ver // console.log(incrementX({ counter: { value: 0 } }) -> { counter: { value: 1 }} ramda の 関数は全てカリー化されていますが、通常の関数と同様に(カリー化された関数であることを意識せずに)使えるので、カリー化を知らない人も利用できます。 公式サイト(?TIP)では「React-Redux hooks APIをデフォルトとして使用すること」が勧められているので hooks API の理解を優先しました。 ↩ mermaidを利用 graph TD S0[B: initialState]-->R1((reducer: B, A => B)) A1[A: Action1] -- type:x1 payload: y1 -->R1 R1 --> S1[B: State1] S1-->R2((reducer: B, A => B)) A2[A: Action2] -- type:x2 payload: y2 -->R2 R2 --> S2[B: State2] ↩ 内部変数は「クロージャ内に存在するアクセスを制限された変数」という意味合いで用いています。 ↩ 実際の変数名とは異なります。ここでは理解しやすさを優先しています。 ↩ 「はじめに > 注意」でお断りを入れた通り、connect API は説明対象外にしています。 ↩ 参考: The Observer Pattern In JavaScript as Implemented By Redux 及び An Obsession with Design Patterns: Redux。react-redux が Observer パターン を使っていると明言している記事は見つかりませんでしたが、Observer パターンに近い実装であることは間違いないと思います。 ↩ 正確には linked list。 ↩ TypeScript表記: type Reducer<A, B> = (b: B, a: A) => B; ↩ TypeScript表記: type CreateEndomorphism<A, B> = (a: A) => (b: B) => B; ↩ mermaidを利用 graph TD S0[B: initialState]-->R1((modifier : B => B)) R1 --> S1[B: State1] S1-->R2((modifier : B => B)) R2 --> S2[B: State2] ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React入門メモ(1)

目的 朝活整理として復習メモの作成レベル 前提知識 node.jsやnpmの環境は作成済みとする。 React とは SPA(Single-Page Application) を実現する JavaScript フレームワークの一つです。 Angular, Vue.js とよく比較されます。 Facebook 社によって開発され、Facebook の Web サイトでも利用されています。 CDN を用いた例 cdn_sample.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>React Test</title> </head> <body> <div id="root"></div> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js"></script> <script type="text/babel"> ReactDOM.render( <h1>Hello world!</h1>, document.getElementById('root') ); </script> </body> </html> 上記では開発者用のライブラリを使用していますが、プロダクトモードの場合は下記を使用する。 <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> インストールとアプリケーション作成と実行 Node.js 環境で React をインストールするには、npm を用いる。 $ npm install -g create-react-app #または以下の場合は、package.jsonのある所に作成される # $ npm install create-react-app コマンドでアプリケーションを作成します。 $ create-react-app my-app $ cd my-app public/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html> src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); serviceWorker.unregister(); src/App.js import React from 'react'; import './App.css'; function App() { return <h1>Hello, world!</h1>; } npm start を実行すると、開発用の簡易サーバが起動します。ブラウザから http://サーバアドレス:3000/ にアクセスして "Hello, world!" が表示される。 参考サイト https://www.tohoho-web.com/ex/react.html https://reactjs.org/ Reactを使うメリットとデメリット
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む