3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

日本一わかりやすいReact-Redux入門#4~#7 学習備忘録

Last updated at Posted at 2020-07-05

はじめに

この記事は、Youtubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux入門』の学習備忘録です。

前回講座で学んだ React に続き、大規模アプリ開発の必須のライブラリである Redux についても勉強をしていきます。

前回の記事はこちら

#4...Actionsを書いてstateの変更を依頼しよう

  • Actionsは、Fluxフローにおいて窓口の役割を果たす。
  • アプリから受け取った「stateの変更依頼」をReducersに渡す
  • Actionsはプレーンなオブジェクトを返す
src/Reducks/users/action.js
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で行う。
src/reducks/store/initialState.js
const initialState = {
  users: {
    isSignedIn: false,
    uid: "",
    username: ""
  }
};

export default initialState
src/users/reducers.js
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の値を変更される。
src/reducks/store/store.js
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を編集する。

src/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を用いて確認する。

App.js
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()の結果を見に行く。

image.png

initialStateとして定義しているuserオブジェクトの初期値が、Reactアプリに渡っているのが分かります!

次に、このstateの値を更新してみます。src/reducks/users/actions.jsで定義したsignInAction()が発火するボタンをApp.js内に追記します。

src/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()を渡しています。このボタンをクリックすると、

image.png

stateの値が更新されているのが分かります!

#7...URLに応じたコンポーネントを表示しよう

connected-react-routerというReduxライブラリを使用する。これは、Reduxのstoreを利用してルーティングを管理するというもの。

store.jsindex.jsを編集する。

src/reducks/store/store/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を管理する。
src/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';
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という特殊なコンポーネントファイルを作成して行う。

src/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にした上で、中身をがらっと変える。

src/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はエントリーポイントの役割を果たす。

src/templates/Login.jsx
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()を実行すると、引数のパスにページを遷移させることができる。現時点では、ダミーとしてルートパスを指定。
src/templates/Home.jsx
import React from 'react';

const Home = () => {
  return (
    <h2>Home</h2>
  );
};

export default Home
src/templates/index.js
export {default as Login} from './Login'
export {default as Home} from './Home'

ブラウザで確認すると、

path = "/" path = "/login"
image.png image.png

上手く表示されています!また、ログインボタンを押すことで、Home画面へ遷移できるようになっています。

おわり

今回はここまで。

第三回講座で概念を学んだFluxフローについて、実際に手を動かすことでなんとなーく分かってきた気がしてきました。

開発フレームワークにおける「どこに何を書くべきかが決まっている」という状態は、初期の学習コストは高くなりがちな反面、慣れることさえできれば、開発だけでなく保守・改修も効率的に行うことができる、というメリットがあると思います。Reduxもその類かと思われるので、頑張って習得していきたいです。

次回記事は今後更新予定。

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?