Reactアプリケーションの環境構築とReduxによる状態管理ベストプラクティス

開発環境のセットアップとスタイリング

Reactプロジェクトの雛形作成には create-react-app を活用し、スタイリングには styled-components を採用してコンポーネント指向なCSS設計を行います。

まずはパッケージを導入します。

yarn add styled-components

続いて、ブラウザ間のスタイル差異を吸収するため、createGlobalStyle を用いてリセットCSSとグローバルスタイルを定義します。

import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
  html, body, div, span, applet, object, iframe,
  h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  a, abbr, acronym, address, big, cite, code,
  del, dfn, em, img, ins, kbd, q, s, samp,
  small, strike, strong, sub, sup, tt, var,
  b, u, i, center,
  dl, dt, dd, ol, ul, li,
  fieldset, form, label, legend,
  table, caption, tbody, tfoot, thead, tr, th, td,
  article, aside, canvas, details, embed,
  figure, figcaption, footer, header, hgroup,
  menu, nav, output, ruby, section, summary,
  time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    vertical-align: baseline;
  }
  /* その他のグローバル設定 */
  body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    line-height: 1;
  }
`;

作成したスタイルコンポーネントは、アプリケーションのルートコンポーネント(App.jsなど)の子要素として配置することで全体に適用されます。アイコンフォントやアニメーションライブラリ(react-transition-group)も同様に導入し、UIの表現力を高めます。

Reduxによる状態管理の設計

アプリケーションの状態管理を一元化するため、reduxreact-redux を導入します。Storeの設計では、combineReducers を活用してReducerを機能単位で分割し、保守性を高めます。

まずはStoreを作成し、開発中のデバッグを容易にするために Redux DevTools Extension を設定します。

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunk))
);

export default store;

Reducerは純関数として定義し、状態の不変性を保つために immutable.js を併用します。これにより、予期せぬ状態の変更を防ぎ、パフォーマンスの向上も期待できます。

プロジェクト全体でImmutableオブジェクトを扱いやすくするため、redux-immutable を導入し、State全体をImmutableなデータ構造として管理します。

// reducers/index.js
import { combineReducers } from 'redux-immutable';
import { searchReducer } from '../common/header/store';

export default combineReducers({
  header: searchReducer
});

各Reducerの実装では、fromJS を使用して初期状態を定義し、state.setstate.setIn などを用いて新しい状態を返します。

import { fromJS } from 'immutable';
import * as constants from './actionTypes';

const initialState = fromJS({
  isFocused: false,
});

export default (state = initialState, action) => {
  switch (action.type) {
    case constants.SEARCH_FOCUS:
      return state.set('isFocused', true);
    case constants.SEARCH_BLUR:
      return state.set('isFocused', false);
    default:
      return state;
  }
};

コンポーネント側では connect 関数を用いてStoreと接続します。Stateの取得時は state.get('header') のようにImmutableオブジェクトとして扱う必要があります。

import { connect } from 'react-redux';

const mapState = (state) => ({
  isFocused: state.getIn(['header', 'isFocused'])
});

const mapDispatch = (dispatch) => ({
  handleFocus() {
    dispatch(actionCreators.searchFocus());
  }
});

export default connect(mapState, mapDispatch)(Header);

非同期処理とデータ通信

APIリクエストなどの非同期処理は、コンポーネントのライフサイクルメソッド内に直接記述せず、redux-thunk を導入してAction内で処理するアーキテクチャを採用します。

通信には axios を使用します。開発環境におけるCORS問題を回避するため、package.json にプロキシ設定を追加してバックエンドサーバーへのアクセスを簡略化します。

"proxy": "http://localhost:8080"

バックエンドAPIのモックサーバーとしては、Node.jsベースの Koa フレームワークを使用して迅速に構築可能です。

ルーティング設定

シングルページアプリケーションとしてのページ遷移を実現するため、react-router-dom を導入します。

import { BrowserRouter, Route, Redirect } from 'react-router-dom';

// ルーティング設定例
<BrowserRouter>
  <div>
    <Header />
    <Route path="/" exact component={Home} />
    <Route path="/detail/:id" exact component={Detail} />
    <Redirect from="/old-path" to="/" />
  </div>
</BrowserRouter>

コンポーネント内でのページ遷移は、history オブジェクトを通じて行います。

// クラスコンポーネントの場合
this.props.history.push('/');

// 現在のパス情報の取得
const currentPath = this.props.location.pathname;

タグ: React Redux styled-components Immutable.js react-router-dom

9月9日 01:46 投稿