はじめに
この記事は、Youtubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux入門』の学習備忘録です。
前回講座で学んだ React に続き、大規模アプリ開発の必須のライブラリである Redux についても勉強をしていきます。
前回の記事はこちら。
#4...Actionsを書いてstateの変更を依頼しよう
- Actionsは、Fluxフローにおいて窓口の役割を果たす。
- アプリから受け取った「stateの変更依頼」をReducersに渡す
- Actionsはプレーンなオブジェクトを返す
export const SIGN_IN = "SIGN_IN";
export const signInAction = {userState} => {
return {
type: "SIGN_IN",
payload: {
isSignedIn: true,
uid: userState.uid,
username: userState,username
}
}
};
export const SIGN_OUT = "SIGN_OUT";
export const signOutAction = () => {
return {
type: "SIGN_OUT",
payload: {
isSignedIn: false,
uid: "",
username: ""
}
}
}
5...Reducersの作り方とスプレッド構文の使い方
- ReducersはActionsからデータを受け取り、Storeのstateをどう変更するか決める。
- Reducersは、Storeの”現在の状態”と”初期の状態”の情報を保有する。
- 初期状態の定義は、
initialState.js
で行う。
const initialState = {
users: {
isSignedIn: false,
uid: "",
username: ""
}
};
export default initialState
import * as Actions from './actions'
import initialState from '../store/initialState'
export const UsersReducer = (state = initialState.users, action) => {
switch (action.type) {
case Actions.SIGN_IN:
return {
...state,
...action.payload
}
default:
return state
}
}
...
はスプレッド構文というjavascriptの文法で、オブジェクトの展開を表す。すなわち、return文は下記と同じ意味になる・
return {
isSignedIn: false,
uid: "",
username: ""
isSignedIn: true,
uid: userState.uid,
username: userState,username
}
同じkeyのものが繰り返された時は、後半にあるものが上書きして残る。
#6...Redux(Store)とReactを接続してstateを変更しよう
- Storeはstateを保存する役割。Reducersにより、Store内部のstateの値を変更される。
import {
createStore as reduxCreateStore,
combineReducers,
} from 'redux';
import {UsersReducer} from '../users/reducers';
export default function createStore() {
return reduxCreateStore(
combineReducers({
users: UsersReducer,
})
);
}
-
combineReducers()
は、複数のReducerを束ねる関数。今はUsersReducer
しかないが、Reducerが増えた場合、ここの記述が複数行になる。
storeをReactアプリと接続するため、index.js
を編集する。
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './reducks/store/store';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
export const store = createStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
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://bit.ly/CRA-PWA
serviceWorker.unregister();
-
export const store = createStore();
で、Storeを生成。 -
<Provider>
タグで<App />
をラッピングすることで、StoreをReactアプリに渡す。
本当にstoreとReactアプリが接続されたのかを、Redux Hooks
を用いて確認する。
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {useDispatch. useSelector} from "react-redux";
function App() {
const dispatch = useDispatch()
const selector = useSelector((state) => state)
console.log(selector.users)
return (
...
localhost:3000をGoogle Chromeで開き、検証ツールからconsole.log()
の結果を見に行く。
initialState
として定義しているuserオブジェクトの初期値が、Reactアプリに渡っているのが分かります!
次に、このstateの値を更新してみます。src/reducks/users/actions.js
で定義したsignInAction()
が発火するボタンをApp.js
内に追記します。
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {useDispatch, useSelector} from "react-redux";
import {signInAction} from "./reducks/users/actions"
function App() {
const dispatch = useDispatch()
const selector = useSelector((state) => state)
console.log(selector.users)
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<button onClick={() => dispatch(signInAction({uid: "0001", username: "torahack"})) } >
Sign In
</button>
</header>
</div>
);
}
export default App;
<button>
タグのonClick
イベントに、先ほどのsignInAction()
を渡しています。このボタンをクリックすると、
stateの値が更新されているのが分かります!
#7...URLに応じたコンポーネントを表示しよう
connected-react-routerというReduxライブラリを使用する。これは、Reduxのstoreを利用してルーティングを管理するというもの。
store.js
とindex.js
を編集する。
import {
createStore as reduxCreateStore,
combineReducers,
applyMiddleware
} from 'redux';
import {connentReactRouter, routerMiddleware} from "connected-react-router";
import {UsersReducer} from '../users/reducers';
export default function createStore(history) {
return reduxCreateStore(
combineReducers({
router: connentReactRouter(histroy),
users: UsersReducer,
}),
applyMiddleware(
routerMiddleware(history)
)
);
}
-
createStore()
が新たにhistory
という引数を受け取っている。これは、サイト遷移の履歴が入っている -
router
というstate名で、このhistory
を管理する。
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './reducks/store/store';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as History from 'history';
const histroy = History.createBrowserHistory();
export const store = createStore(history);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
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://bit.ly/CRA-PWA
serviceWorker.unregister();
-
<ConnectedRouter>
でさらに<App />
をラッピング。
具体的なルーティングの定義を実装する、実装は、Router.jsx
という特殊なコンポーネントファイルを作成して行う。
import React from 'react';
import {Route, Switch} from "react-router";
import {Login, Home} from "templates";
const Router = () => {
return (
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="(/)?" component={Home} />
</Switch>
);
};
export default Router
-
<Route>
タグ内で、パスとコンポーネントの対応を記述する。 -
exact path=
が完全一致、path=
が部分一致。静的なルーティングに対しては前者を使い、動的なルーティングに対しては後者を使う("/posts/:id"のようなケース)
この時点では、templatesファイルは未作成。これを作る前に、App.js
を変更する。拡張子をApp.jsx
にした上で、中身をがらっと変える。
import React from 'react'
import Router from './Router'
const App = () => {
<main>
<Router />
</main>
};
export default App;
<main>
タグでRouterコンポーネントを呼び出している。この<Router />
の中身が、パスごとに切り替わるイメージ
続いて、templatesファイルを作る。templatesファイルは、静的ページにつき1個ずつ存在し、そのページ(ルーティング)において親コンポーネントの役割を果たす。
作成するファイルは以下の通り。
- src/templates/Login.jsx
- src/templates/Home.jsx
- src/templates/index.js
index.js
はエントリーポイントの役割を果たす。
import React from 'react';
import {useDispatch} from "react-redux";
import {push} from "connected-react-router";
const Login = () => {
const dispatch = useDispatch();
return (
<div>
<h2>ログイン</h2>
<button onClick={() => dispatch(push('/'))}>
ログインする
</button>
</div>
);
};
export default Login
-
useDispatch()
で store に紐付いた dispatch が取得できる。 -
push()
を実行すると、引数のパスにページを遷移させることができる。現時点では、ダミーとしてルートパスを指定。
import React from 'react';
const Home = () => {
return (
<h2>Home</h2>
);
};
export default Home
export {default as Login} from './Login'
export {default as Home} from './Home'
ブラウザで確認すると、
path = "/" | path = "/login" |
---|---|
上手く表示されています!また、ログインボタンを押すことで、Home画面へ遷移できるようになっています。
おわり
今回はここまで。
第三回講座で概念を学んだFluxフローについて、実際に手を動かすことでなんとなーく分かってきた気がしてきました。
開発フレームワークにおける「どこに何を書くべきかが決まっている」という状態は、初期の学習コストは高くなりがちな反面、慣れることさえできれば、開発だけでなく保守・改修も効率的に行うことができる、というメリットがあると思います。Reduxもその類かと思われるので、頑張って習得していきたいです。
次回記事は今後更新予定。