5
1

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.

VALUAdvent Calendar 2019

Day 6

【Redux】Redux を数ヶ月間使って感じたツライの実態とその改善策

Last updated at Posted at 2019-12-06

VALU Advent Calendar 2019 6 日目の投稿です!! 🎉🎉

こんにちは! VALU サービスの Web アプリケーションにおいて,クライアントサイド開発を担当している藤本です. ぼくの得意な言語は,日本語 ( Japanese ) なのでがんばって書いていこうと思いますー!

本記事のゴール

✔︎ VALU サービス開発内部の Redux の実態が伝わった
✔︎ VALU における Redux の使い方が伝わった
✔︎ ぼくの努力が伝わった

はじめに

はじめに VALU における Redux の使い方ついて簡単に説明しようと思います.
なお,Redux とは何か?とか, Redux をなぜ導入したのか?という記事構成に必要そうな前提部分は本記事では,割愛させて頂きます.

Redux の基本構成 と 分割方法

VALU における Redux の基本構成と分割方法を説明します.

♻️ Redux

- Component (View 層)
  - Container Component
  - Presentational Component
  -> 1 ページ 1 Container の構成
- Actions
  -> Actions は機能ごとに分けることが多い
- Reducers
  -> Reducers も機能ごとに分けることが多い
- Store
  -> Reducers に対応する形で初期状態を定義

📂 ファイル構成はこんな感じ

├── containers/
│   ├── user/
│       └── common-user.js
│   └── event/
│       └── index.js
├── component/
│   └── pages/
│       └── event/
│           └── index.js
│   ├── temlates/...
├── actions/
│   ├── user.js
│   └── event.js
├── reducers/
│   ├── user.js
│   ├── event.js
│   └── index.js
└── store/
│   ├── user.js
│   ├── event.js
│   └── index.js

ざっくりした役割分担

そして VALU における Redux フローの役割分担についてざっくりと説明します.

  • Component (View 層)
    • Container Component
      • Redux の出発地点と中継地点を担当
    • Presentational Component
      • 基本的に「見た目」の描画だけを担当
      • 必要に応じてイベントの発火を担当 (ロジックは持たない)
      • pages/ で props を受けとって必要に応じてバケツリレーする
  • Actions
    • redux-actionscreateActionを使って ActionCreator を作成する
    • POST を含む全ての API 通信を担当
  • Reducers
    • Actions から dispatch されてきた action オブジェクトを,「良しなに」 Store に置かれている State の状態を更新する 
      • ...????!? 良しなに????!!🤔
  • Store
    • Reducers から要求された状態の差分を更新して Container に新しい State として送信する

つらみ

そう...Reducer に開発効率を妨げる "つらみ" が隠されていました...
VALU のフロントエンド開発では,Redux それぞれに明確な役割を持っているように一見みえます.しかし,現実は Reducers の実装は実装者の雰囲気で実装されている痕跡が残っています.

💫つらかったこと 2 選

- 👿 API からのレスポンスの情報を丸ごと Reducer を通して送信していた
- 😈 API からのレスポンスを名前から意図を把握することが困難な状況になっていた

👿API からのレスポンスの情報を丸ごと Reducer を通して送信していた

まずは簡単な例からみていきましょう....

例えば以下 👇 のような型をもつ API からのレスポンスがあるとします.

🗂EventType.js

EventType.js
type Event = {
  id: number, // イベントID
  title: string, // イベントタイトル
  image_url?: string, // イベントのTop画像
  address?: string, // イベントの開催地
  geotag?: string, // イベントの位置情報
  min_age: number, // 年齢制限
  start_at: string, // イベントの開始時刻
  end_at: string, // イベントの終了時刻
  detail?: string // イベントの内容詳細
};

上記のレスポンスを Actions で,event オブジェクトとして受け取り Reducers を通して Store の状態を更新しにいきます.(Actions で API 通信している処理は本記事では割愛します.)

🗂reducers/event.js

reducers/event.js
import * as EventActions from '../actions/event';

export default (state = {}, action) => {
  switch (action.type) {
    // Eventのレスポンスデータを更新する
    case String(EventActions.fetch.fulfilled):
      return {
        ...state,
        event: ...action.payload.event
      };
    default:
      return state;
  }
};

🗂store/event.js

store/event.js
export default {
  // eventオブジェクトの中身を丸ごと更新
  event: {}
};

あくまで雰囲気ですがこれに近い実装が,実際にありました.
いま現在は型を定義しているので,event オブジェクトの中身を確認できるようになりました.
しかし,型がなかった時代もありまして,そのころは,event の中に何がいるのか確認しづらい状態でした.つまり,例えばconsole.log()をして中身をみにいかなければならないという謎の手間 ( つらみ ) が発生しておりました...

👿API からのレスポンスを名前から意図を把握することが困難な状況になっていた

1 点目の話との繋がるのですが,API からのレスポンスを Redux を使ってそのまま伝搬していたため,命名に秩序がありませんでした.
上述した通り VALU プロジェクトのレスポンスはスネークケースで返ってくるのですが,例えばそれをキャメルケース等に変換する場所は決まってませんでした.なのでスネークケースの箇所とキャメルケースの箇所が入り乱れる事態が発生していました.メソッドはキャメルケースで書いたりしていたので...なかなかに混沌とすることに...

また,レスポンスの持つ名前の意図を汲み取ることが難しく,最悪 API ドキュメントを参照しないといけないという状況でした.毎回この作業が発生するのはつらい...

改善策 🚀

これらの問題を解決するためには Reducer に明確に役割を持たせることが重要だと考えました.(当然ですが...)
改善策として Reducer を以下のように書き換えることを提案します!

🗂reducers/event.js

reducers/event.js
import * as EventActions from '../actions/event';

export default (state = {}, action) => {
  switch (action.type) {
    // Eventのレスポンスデータを更新する
    case String(EventActions.fetch.fulfilled):
      return {
        ...state,
        eventId: action.payload.event.id,
        eventTitle: action.payload.event.title,
        eventImageUrl: action.payload.event.image_url,
        eventAddress: action.payload.event.address,
        eventMinAge: action.payload.event.min_age,
        eventStartAt: action.payload.event.start_at,
        eventEndAt: action.payload.event.end_at,
        eventContentDetail: action.payload.detail
      };
    default:
      return state;
  }
};

以上のことから Reducer で行う役割を下記のように定義します.

- ✨Reducer では View 層で必要な情報だけを更新すること
  - また,スプレッド構文等を極力使わず地道に書くこと
- ✨Reducersでスネークケースをキャメルケースに変換すること
  - 余力があれば,命名を意識すること

これだけで Reducers にあったつらさはだいぶ解決されると思っております.まだ実施途中ですが...頑張っていこうと日々奮闘中であります..!

最後に

ぼくは,Redux のことすきです.なので,これからもたくさん書いていきます...!

5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?