開発環境のセットアップとスタイリング
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による状態管理の設計
アプリケーションの状態管理を一元化するため、redux と react-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.set や state.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;