はじめに
【React/Redux/TypeScript】実践的なフォームの扱いを完全に理解しよう!を購入したのでこちらで学習の記録を残していこうと思います。
✅ゴール
・React+TSの理解を深める
✅環境
Windows
✅参考
💻【React/Redux/TypeScript】実践的なフォームの扱いを完全に理解しよう!
💻React, Redux 初心者が、Hooks 時代の React, Redux, React-Redux に触れてみて感じたこと
useSelectorやuseDispatchなどについて記載されている。
制作において
✅環境
frontディレクトリでyarn startでプロジェクト起動。
✅ディレクトリ構成
actions.tsでアクションを定義。reducer.tsで初期値の作成とアクションを読み込み。
✅ライブラリなど
①typescript-fsa
ReduxをTypeScriptで記述するのは大変なので、Redux with TypeScriptの開発ではデファクトとなっているtypescript-fsaを利用。
👇以下使用例
actionCreatorFactory関数 参考
profile/actions.ts
const actionCreator = actionCreatorFactory();
~~~~
const profileActions = {
setProfile: actionCreator<Partial<Profile>>("SET_PROFILE"),
actionCreatorにはジェネリクス(型引数)が使われている。setProfileという action のpayload(reducer に渡す値)の型をこれで定義することができる。
actionCreator.async() 参考
※非同期処理の型宣言に対応
profile/actions.ts
searchAddress: actionCreator.async<{}, Partial<Address>, {}>("SEARCH_ADDRESS")
actionCreator.async()を使うことで、非同期処理用のstart、done、failの 3 つの action を作成することが可能。
generics の3つの型引数はstart、done、failに対応しており、そのときにどんな型の payload を渡すのか定義できる。
reducerWithInitialState関数 参考
profile/reducer.ts
const init: Profile = { name: "", description: "", birthday: "", gender: "" }; const profileReducer = reducerWithInitialState(init).case( profileActions.setProfile, (state, payload) => ({ ...state, ...payload }) );
最初にstateの初期値を定義してreducerWithInitialState関数に引数として渡す。.case()チェーンでアクションの処理を記述。第1引数にアクション、第2引数にコールバック関数(第1引数にstateそのもの、第2引数にアクションから渡ってきたpayload)。
🙄作っていてこのライブラリ様様な気がした。別途要学習。
②Redux hooks
connect()の使い方が厄介なのでreact-reduxのhooks APIを活用する。
③redux-thunk
非同期処理に活用。
src/store/index.ts
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
🙄Reduxライブラリからインポートしている点が印象的。
applyMiddlewareはredux-thunkという外部ライブラリをreduxに登録するためのもの。
composeはRedux Dev Toolとmiddlewareをまとめてstoreに登録するもの。
redux-thunk では、dispatch 関数を引数にとる関数を返す関数(高階関数)を非同期 action として扱う。
src/store/profile/effects.ts 参考
export const searchAddressFromPostalcode = (code: string) => async ( dispach: Dispatch ) => { // ... };
dispatch を引数にとる関数を返す高階関数。
※コード割愛部分
fetchを用いて非同期のリクエストを実行。awaitを使うことで手続き的に Promise を解決。
📝印象的だった点
①interfaceとtypeの使い分けは曖昧だということ
※typeの場合
export type Profile = { name: string; description: string; birthday: string; };
※interfaceの場合
export interface Profile { name: string; description: string; birthday: string; }
🙄typeだと=が必要なのがわかりますね。
②Redux with hooks
Reduxで専用のhooksが用意されているのは初めて知りました(useSelector)。
基本的にconnectはもう使わないとのこと。
import React from "react"; import { useSelector } from "react-redux"; export const CounterComponent = () => { const counter = useSelector(state => state.counter); return <div>{counter}</div>; };
これでstoreから状態を参照できる。
ディスパッチも以下のような形で参照できる。
const dispatch = useDispatch();
③APIの活用
PostalcodeJP API:住所補完
※2章終えた時点で自動補完のAPIが実装される。

✅備考
・ReactではTSのClass型の記法について覚える必要はあまりない
サーバー側の環境構築
このプロジェクトは直下にfront,serverディレクトリを置いています。
教材では扱っていませんでしたが、serverディレクトリ側を少し分析してみたいと思います。
index.ts
import express from "express"; import fetch from "node-fetch"; require("dotenv").config(); const app = express(); app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE"); res.header( "Access-Control-Allow-Headers", "Content-Type, Authorization, access_token" ); // intercept OPTIONS method if ("OPTIONS" === req.method) { res.send(200); } else { next(); } }); app.get("/hc", (_req, res) => { res.send("ok"); }); app.get("/colleges", (req, res) => { (async () => { try { const url = `http://webservice.recruit.co.jp/shingaku/school/v1/?key=${process.env.API_KEY}&format=json&name=${req.query.name}`; const result = await fetch(encodeURI(url)).then(res => res.json()); res.json(result); } catch (e) { res.status(500).send(e); } })(); }); app.listen(18001);
1文目でExpressを使っていることがわかります。
✅CORS対応
また、ヘッダーに以下の情報を付与してますね。
app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE"); res.header( "Access-Control-Allow-Headers", "Content-Type, Authorization, access_token" );
これでCORSへの対処をされている感じですね。
指定されたオリジンからのリクエストを行うコードでレスポンスが共有できるかどうかを示す。
*
資格情報がないリクエストでは、リテラル値 “*” をワイルドカードとして指定することができます。この値はブラウザーに、すべてのオリジンからのリクエストコードにリソースへのアクセスを許可するように指示します。資格情報がある時にワイルドカードを使用すると、エラーを返します。
<origin>
オリジンを指定します。1つのオリジンだけを指定することができます。
プリフライトリクエストのレスポンスの中で、リソースにアクセスするときに利用できる1つまたは複数のメソッドを指定
・Access-Control-Expose-Headers
レスポンスの一部としてどのヘッダーを公開するかを、その名前を列挙して示す
✅ルーティング
app.get("/hc", (_req, res) => { res.send("ok"); }); app.get("/colleges", (req, res) => { (async () => { try {~~~~
上記で/hcと/collegesが来た際に処理される内容が記述されています。
🙄Laravelでいうコントローラーみたい
✅サーバー側のポート番号
app.listen(18001);
localhost:18001で起動することがわかります。
起動
npm run start
確認
http://localhost:18001/collegesへアクセスするとJSONファイルが取得できていることがわかります。

🙄意外にexpress側の記述はシンプルなことがわかりました。
✅React側の記述
front/src/store/effects.ts
import { Dispatch } from "redux"; import collegesActions from "./actions"; export const searchColleges = (name: string) => async (dispach: Dispatch) => { const url = `http://localhost:18001/colleges?name=${name}`; const result = await fetch(url).then(res => res.json()); dispach( collegesActions.searchCollege.done({ result: result.results.school, params: {} }) ); };
こっちもとてもシンプル。
Proxyの設定とかせずに18001ポートを直接参照できています。
記事まとめ
・Re-ducksパターン:React + Redux のディレクトリ構成ベストプラクティス
・【初学】初めてのAPI(fetchとかpromiseとか良くわからない)
fetchでデータを取ると、返り値としてPromiseを返す。
fetch() の最も簡単な使い方は1つの引数で取得したいリソースへのパスのみをとり、レスポンス(Responseオブジェクト)を含むpromiseを返す。
※JSONではないため、json()メソッドを併用する。
Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現。
非同期の状態としてreject(失敗)とresolved(成功)があって、
成功時は.then以降の処理を実行させ、失敗時は.catch以降の処理を実行できる模様。
おわりに
フォームを作るだけでもかなりボリューミーな内容。
ReduxのuseDispatchとuseSelectorは別のアプリでも使いながら勉強したいと思った。
6/6追記:
一旦完成。
別途TypeScript部分のコードを深掘りして、しっかりと理解していきたい。
備忘録
①2-4終えた時点での自動補完がされない。
handlePostalcodeChangeが読み込まれなかったのが原因。
完成リポジトリを参考に修正したら解決。
コメントを残す