redux-dynamic-module と
typescript-fsa と
re-ducks を
組み合わせて開発してみたお話
前提
大規模 SPA WebApp 開発
- 利用ユーザに応じて表示モードを切り替える
- CS向け / 修理向け / 製造向け など
- 認証やユーザ検索など共通部品もあり
モジュール単位での開発をしたい!
よく見る redux ディレクトリ構成 (Rails-style)
悩み
→ 関心空間の単位でディレクトリ分離したい!
- 関連するコードがあちこちに分散
- 機能分割でファイル数を増やしづらい
- 部品内での共通関数などを置く場所が作り辛い
re-ducks
redux 構成要素の
ディレクトリルール
&ネーミングルール
- 関心空間の単位でディレクトリを分ける
- redux の機能単位でファイル独立さる
- reducers, actions ...
- 公開するものと隠すものを分類する
- 外部からの参照用の index を用意する
- Selectors
- モジュール状態参照
- 状態参照関数が用意される
- Operations
- モジュール操作
- ActionCreator が用意される
⇒
rails-style
re-ducks
ディレクトリ分離できた!
Code Splitting したい
悩み
他の表示モードでしか使わないものは読み込みたくない
けど、store とか reducer / middleware って
lazy loading できるんだっけ?
redux-dynamic-modules
store / reducer / middleware などを
付け外しする機構
- MIT License
- Copyright (c) Microsoft Corporation.
- 2018.10.30 に 1.0.0
- 現在 4.0.1 (2019.6.15)
- 動作中にモジュールの load / unload 可能
- store の構造に工夫がある
import { IModule } from 'redux-dynamic-modules';
// dependencies
// application imports
import { Actions, Operations } from './operations';
import { Selectors } from './selectors';
import Reducer, { IState } from './reducers';
// ============================================================================
// Type definitions
//
export interface IModuleOwnState
{
app: IState;
}
// ============================================================================
// Dynamic Module
//
function getModule(): IModule<IModuleOwnState> {
return {
id: "ApplicationModule",
reducerMap: {
app: Reducer,
} as any,
// Actions to fire when this module is added/removed
initialActions: [Actions.initialize(null)],
// finalActions: []
// middlewares: [],
};
}
// ============================================================================
// Exports
//
export {
IState,
Selectors,
Operations,
};
export const Modules = [
// My module
getModule(),
];
module
reducer と middleware
情報を持つ object
- id
- reducerMap
- middlewares
- initialActions
- finalActions
他 module への依存関係は配列で表現
import React, {lazy} from 'react';
const CSWidget = lazy( () => import('./widgets/CS' /* webpackChunkName: "CSWidget" */) );
const FactoryWidget = lazy( () => import('./widgets/Factory' /* webpackChunkName: "FactoryWidget" */) );
const RepairWidget = lazy( () => import('./widgets/Repair' /* webpackChunkName: "RepairWidget" */) );
const Component: React.FC<IProps> = (props) => {
const { mode } = props;
return (
<div>
{mode === 'CS' && <CSWidget />}
{mode === 'Factory' && <FactoryWidget />}
{mode === 'Repair' && <RepairWidget />}
</div>
);
}
module 読込例
コンポーネントのタブを書くだけで
store も reducer も middleware も
利用可能に!
createStore
createStore
- redux-dynamic-modules が createStore を用意している
- 既存の store との互換性は無い !!
- マイグレーションが難しい!?
- 旧来の store/reducer/middleware をまとめて一つの module にしちゃえばいい!
- と、公式の人も言っている
必要なタイミングで
モジュール読み込み
できるようになった!
型、大切ですよね
悩み
dispatch すると型情報が伝播しない
reducer や middleware で値を扱うときに型情報を使うには?
typescript-fsa
Action と型を結びつける
- MIT License
- Copyright (c) 2016 Daniel Lytkin
- 2019.9.14 に 3.0.0 になった
- FSA(Flux Standard Action) 対応の Action を作成する
- Action ハンドリングの際に型情報の取得ができる
- typescript-fsa-reducers / typescript-fsa-redux-thunk etc.
import actionCreatorFactory from 'typescript-fsa';
const actionCreator = actionCreatorFactory('ApplicationModule');
export const Actions = {
initialize : actionCreator('initialize'),
fetch : actionCreator.sync<IParam, IUser[], IError> ('fetch'),
}
action creator
import { reducerWithInitialState } from "typescript-fsa-reducers";
import { Actions } from './actions';
export interface IState {
users: IUser[];
}
const initialState = () : IState => {
users: [],
}
export default reducerWithInitialState( initialState() )
.case( Actions.fetch.done, (state, {result}) => {
return {...state, users: result}
})
reducer
- Vanilla middleware 向けの typescript-fsa-* が無かった
- 作ってみた
typescript-fsa-redux-middleware
import { middleware } from "typescript-fsa-redux-middleware";
import { Operations, Actions } from './operations';
export default middleware()
.case( Operations.fetchUsers, async ({dispatch, getState}, next, action) => {
const params = action.payload;
await dispatch( Actions.fetch.started(params) );
try {
const users = await fetch('URL');
dispatch( Actions.fetch.done({params, result: users}) );
} catch(error) {
dispatch( Actions.fetch.failed({params, error}) );
}
next(action);
})
middleware
redux loop で型情報が
行きわたるようになった
まとめ
大規模 react + redux アプリ作成
でモジュール化は必須
- re-ducks で関心空間の分離
- redux-dynamic-modules でモジュール化
- typescript-fsa で型情報の伝播
関心空間の単位で
モジュール実装できる環境が整った!
typescript-fsa-redux-middleware
使ってみてね
今回紹介した技術を含めたサンプルを用意してあります
★ もお願いします!!!
おわり
redux-dynamic-module と typescript-fsa と re-ducks を組み合わせて開発してみたお話
By Yasuharu Seki
redux-dynamic-module と typescript-fsa と re-ducks を組み合わせて開発してみたお話
- 1,424