質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.80%

TypeScriptでRedux.connectのラップ関数を作りたいのです

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,115

m0a

score 696

export default connect(
  (state: ReduxState) => ({ state }),
  (dispatch: Dispatch<ReduxAction>) => ({ dispatch }),
  ({state}, {dispatch}, onwProps) => ({
    actions: new ActionDispatcher(dispatch, state),
    value: state.counter,
  })
)(Counter);

上記conncetを毎回書くのが面倒なので上手くラップする関数が作りたいんです。

まず、非同期処理を行うためにActionDispacherを作りました。非同期処理を行えるクラスです。
参考資料:React + Redux + TypeScriptの最小構成

うごくソースコードはこちらです。

counterコンポーネントを以下の構成で作っています

  • 今回見ていただきたいActionDispacher

src/counter/Container.tsx

import { Counter } from './Counter';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { decrementAmount, incrementAmount } from './module';
import { ReduxAction, ReduxState } from '../store';

export class ActionDispatcher {
  constructor(private dispatch: (action: ReduxAction) => void, rootState: ReduxState) { }

  public increment(amount: number) {
    this.dispatch(incrementAmount(amount));
  }

  public decrement(amount: number) {
    this.dispatch(decrementAmount(amount));
  }
}

/* 少し複雑 */
export default connect(
  (state: ReduxState) => ({ state }),
  (dispatch: Dispatch<ReduxAction>) => ({ dispatch }),
  ({state}, {dispatch}, onwProps) => ({
    actions: new ActionDispatcher(dispatch, state),
    value: state.counter,
  })
)(Counter);
  • UI

src/counter/Counter.tsx

import * as React from 'react';
import { CounterState } from './module';
import { ActionDispatcher } from './Container';

interface Props {
  value: CounterState;
  actions: ActionDispatcher;
}

export class Counter extends React.Component<Props> {

  render() {
    return (
      <div>
        <p>score: {this.props.value.num}</p>
        <button onClick={() => this.props.actions.increment(3)}>Increment 3</button>
        <button onClick={() => this.props.actions.decrement(2)}>Decrement 2</button>
      </div>
    );
  }
}
  • reducer

counter/module.tsx

// ActionCreator
import { Action } from 'redux';

enum ActionNames {
  INC = 'counter/increment',
  DEC = 'counter/decrement',
}

interface IncrementAction extends Action {
  type: ActionNames.INC;
  plusAmount: number;
}
export const incrementAmount = (amount: number): IncrementAction => ({
  type: ActionNames.INC,
  plusAmount: amount
});

interface DecrementAction extends Action {
  type: ActionNames.DEC;
  minusAmount: number;
}

export const decrementAmount = (amount: number): DecrementAction => ({
  type: ActionNames.DEC,
  minusAmount: amount
});

// reducer
export interface CounterState {
  num: number;
}

export type CounterActions = IncrementAction | DecrementAction;

const initialState: CounterState = { num: 0 };

export default function reducer(state: CounterState = initialState, action: CounterActions): CounterState {
  switch (action.type) {
    case ActionNames.INC:
      return { num: state.num + action.plusAmount };
    case ActionNames.DEC:
      return { num: state.num - action.minusAmount };
    default:
      return state;
  }
}

上記は動く構成です。

ここで注目していただきたいのはsrc/counter/Container.tsxです。

export default connect(
  (state: ReduxState) => ({ state }),
  (dispatch: Dispatch<ReduxAction>) => ({ dispatch }),
  ({state}, {dispatch}, onwProps) => ({
    actions: new ActionDispatcher(dispatch, state),
    value: state.counter,
  })
)(Counter);

connectの呼び出しが複雑なので汎用のラップ関数を作ろうと思いました。 しかし上手く行きません。

とりあえず書いてみました。

import { Counter } from './Counter';
import * as ReactRedux from 'react-redux';
import { Dispatch } from 'redux';
import { decrementAmount, incrementAmount } from './module';
import { ReduxAction, ReduxState } from '../store';

export class ActionDispatcherBase {
  constructor(protected dispatch: (action: ReduxAction) => void, rootState: ReduxState) { }
}

export class ActionDispatcher extends ActionDispatcherBase {

  public increment(amount: number) {
    this.dispatch(incrementAmount(amount));
  }

  public decrement(amount: number) {
    this.dispatch(decrementAmount(amount));
  }
}

interface MyMergeProps<T> {
  (state: ReduxState, dispatch: Dispatch<ReduxAction>, onwProps: T): T;
}

function Myconnect<T>(actionDispatcher: typeof ActionDispatcherBase, mergeProps: MyMergeProps<T>) {
  return ReactRedux.connect(
    (state: ReduxState) => ({ state }),
    (dispatch: Dispatch<ReduxAction>) => ({ dispatch }),
    ({ state }, { dispatch }, onwProps: T) => {
      const pp = mergeProps(state, dispatch, onwProps);
      return (
        Object.assign({}, {
          actions: new actionDispatcher(dispatch, state)
        }, pp)
      );
    });
}

// export default ReactRedux.connect(
//   (state: ReduxState) => ({ state }),
//   (dispatch: Dispatch<ReduxAction>) => ({ dispatch }),
//   ({ state }, { dispatch }, onwProps) => ({
//     actions: new ActionDispatcher(dispatch, state),
//     value: state.counter,
//   })
// )(Counter);
export default Myconnect(
  ActionDispatcher, (state, dispatch, ownprops) => ({
  value: state.counter,
}))(Counter);

最終的に 

export default Myconnect(
  ActionDispatcher, (state, dispatch, ownprops) => ({
  value: state.counter,
}))(Counter);


のような呼び出してRedux Stateとの連携ができるコンポーネントが作れるのが理想なのですが上手く行きません。
どう記述すればいいかご教授下さい

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

+7

自己解決しました。
最終的に以下のように書くことができるようになりました

export default Myconnect<Props>(
  ActionDispatcher, (state, dispatch, ownProps) => ({
  ...ownProps,
  value: state.counter,
}))(Counter);

`

変更点:

import { Counter, Props } from './Counter';
import * as ReactRedux from 'react-redux';
import { Dispatch } from 'redux';
import { decrementAmount, incrementAmount } from './module';
import { ReduxAction, ReduxState } from '../store';

export class ActionDispatcherBase {
  constructor(protected dispatch: (action: ReduxAction) => void, rootState: ReduxState) { }
}

export class ActionDispatcher extends ActionDispatcherBase {

  public increment(amount: number) {
    this.dispatch(incrementAmount(amount));
  }

  public decrement(amount: number) {
    this.dispatch(decrementAmount(amount));
  }
}

interface MyMergeProps<T extends {actions: ActionDispatcherBase}> {
  (state: ReduxState, dispatch: Dispatch<ReduxAction>, ownProps: T): T;
}
function Myconnect<T extends {actions: ActionDispatcherBase}>(
    actionDispatcher: typeof ActionDispatcherBase, 
    mergeProps: MyMergeProps<T>): ReactRedux.InferableComponentEnhancer<T>  {
  return ReactRedux.connect(
    (state: ReduxState) => ({ state }),
    (dispatch: Dispatch<ReduxAction>) => ({ dispatch }),
    ({ state }, { dispatch }, ownProps: T) => {
      const pp = mergeProps(state, dispatch, ownProps);
      return (
        Object.assign({}, pp, { actions: new actionDispatcher(dispatch, state)})
      );
    });
}

export default Myconnect<Props>(
  ActionDispatcher, (state, dispatch, ownProps) => ({
  ...ownProps,
  value: state.counter,
}))(Counter);

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る