概要
コンポーネント指向について、分かったような気になって実際に作ってみたら色々ごちゃごちゃしてしまったのだが、改めて考えを整理したら少し上手くいくようになった。
上手くいってなかった時と、上手くいった今の、考え方のdiffを稚拙ながら整理したので以下に紹介する。
react-reduxを使って、よくある一覧画面を作ることを想定しながら、紹介する
前提
- webpackなどを使ったjs・jsxのビルドやトランスパイルができている前提
考え方1・コンポーネントが最初
何はともあれ、コンポーネントから作る。
画面の中で使うであろうコンポーネントをまず列挙する
多分こんな感じ
各コンポーネントを実装する。どんなpropsがあればいいかを考えながら実装する。
既存の世にあるコンポーネントも適宜使う(ここでは most poularである Material-UI を使う。その場合も適宜ラップしたりする。)
多分こんな感じ。括弧内は必要そうなpropsと処理
- Atoms
- TextField (Enter押した時のハンドラ、placeholder、など)
- IconButton (アイコンの種類、クリックした時のハンドラ、など)
- Button (クリックした時のハンドラ、など)
- Molecules
- SearchBox (ボタンクリックやEnterを押した時のハンドラ/ハンドラのトリガ)
- Organisms
- BranchCard (店舗ID、店舗名、店舗住所、など)
- BranchList (店舗情報リスト)
- Pagination (現在の表示数、合計数、次があるか、前があるか、など/前、次を押した時のハンドラ)
- Loading (特になし)
- Templates
- ListPageTemplate (ヘッダ部分のコンポーネント、リスト部分のコンポーネント、ページナビ部分のコンポーネント、ローディング表示かどうか)
- Pages
- BranchesListPage (ajaxで取得した店舗リスト、データ取得中かどうか、現在の検索条件/マウント時にデータ取得、ページ移動した時にデータ取得・URL変更、ListPageTemplateに各コンポーネントを渡してrender、など)
AtomsやMoleculesなどは特に、汎用性が高くなるように作る。例えば AtomsやMoleculesなどの部品で margin
を定義してしまうと、他で使う時にまた調整が必要になってしまったりするので、AtomsやMoleculesの部品のmargin
などは親側のコンポーネントで設定するのが定跡。
atomic designについて
以下が分かりやすい
SlideShare
UI 開発をアジャイルに行うための Atomic Design
書籍
Atomic Design ~堅牢で使いやすいUIを効率良く設計する
考え方2・コンポーネントが必要とするpropsを元にReducerを実装する
コンポーネントを考えてからreducerを実装する。reducerを先に考えてコンポーネントをそれに合わせるように実装するのではない。
先に列挙したコンポーネントのなかで、reducerが必要なものはBranchListPage
。他は親からpropsを渡す。
例えばこんな感じのreducerになる
import { LOCATION_CHANGE } from 'react-router-redux';
import {
REQUEST_BRANCHES,
RECEIVE_BRANCHES,
REQUEST_BRANCHES_FAILURE,
} from '~/constants/actionTypes';
const initialState = {
loading: false, // ロード中かどうか。Loadingを描画するかどうかの判定で使う
search: '', // 検索ワード。表示したり、TextFieldにあらかじめ表示しておくなら使う
total: 0, // データの合計数。表示するので使う
page: 0, // 現在のページ。表示したり、次や前へを押せるか、また押した時に、どうリクエストを送るのかに使う
rowsPerPage: 5, // 1ページあたりの件数。表示したり、次や前へを押せるか、また押した時に、どうリクエストを送るのかに使う
offset: 0, // 現在何件目からを表示しているのか。表示したり現在のページの計算に使う
items: [], // 表示するデータ。表示するのに使う
};
export default function branchesList(state = initialState, action) {
switch (action.type) {
case LOCATION_CHANGE: {
// URLによってpropsが変わるならここに処理を書く。ここでは省略
}
case REQUEST_BRANCHES:
return {
...state,
loading: true,
};
case RECEIVE_BRANCHES: {
const {
orders: items,
offset,
total,
limit,
search,
} = action.data;
const page = Math.floor(offset / state.rowsPerPage, 10);
return {
...state,
loading: false,
search,
items,
offset,
total,
page,
};
}
case REQUEST_BRANCHES_FAILURE:
return {
...state,
loading: false,
};
default:
return state;
}
}
あとはactionsやstyleを作って終わり。
考え方3:何か変更があった時も、コンポーネントを優先して考える
何か変更があった時も、コンポーネントを優先して考える。reducerやactionありきではなく、コンポーネントありきである。
例えば、この一覧画面で、別の項目を増やしたいだの、1ページあたりに表示できる件数を変更できるUIが欲しいだの、検索条件をもっと増やしたい、ソートしたい、ログインユーザをヘッダに出したい、などなど、どんな変更でも、それがコンポーネントにどう影響するかを考える。
コンポーネントが表示する内容がどうなるか、コンポーネントが表示する内容を決めるためにpropsが増えるとか、そのためには新たなactionが必要、新たなreducerの実装が必要、など、そういう順番で考える。
storybookも最初から作った方が、コンポーネントが綺麗に作れているかが分かるのでいい。
storybook
まとめ
とにかくコンポーネントが先、そのほかのことは後。というふうに、考えればスッキリした設計になる。