Interfaceのメンバ競合の問題
import * as ReduxActions from 'redux-actions'; import * as Redux from 'redux'; // 型の競合エラーがでる interface ExAction extends Redux.Action, ReduxActions.Action<string> {}
現実感の無い実験のためのこのコードは、今日時点では残念ながらコンパイラを通りません。理由はInterfaceのメンバが競合しているからです。
// reduxのindex.d.tsより export interface Action { type: any; // ここでtypeプロパティ宣言 } // @types/redux-actionsのindex.d.tsより export interface BaseAction { type: string; // ここでもtypeプロパティ宣言 } export interface Action<Payload> extends BaseAction { payload?: Payload; error?: boolean; }
どちらもtypeプロパティを持ってるけど、型がreduxのはanyで、@types/redux-actionsはstring。これが双方同じ型かどちらかのtypeプロパティ定義がなければ問題にならない。。reduxの方がanyで個別ならOKだからTypeScriptの将来バージョンでは通るようになるかもしれないけど、今の所はダメ。前記事の「The hidden power of Jest matchers」とメタで似たような問題ながら、TypeScriptのほうでは融通きかない。
調べるきっかけとなった私のコードは次のReduxのMiddlewareファクトリです。
import { Middleware, Action } from 'redux'; import { push } from 'react-router-redux'; type MoveToPayload = { title: string, vol: string, page: number }; export function moveToMiddleware(): Middleware { // redux-actions仕様(=Flux Standard Actions)をDuck Typing interface FSA<Payload> extends Action { payload?: Payload; } // reduxのActionとredux-actionsのAction<Payload>に互換性がないからこじあける return ({ dispatch }) => next => <A extends FSA<MoveToPayload>>(action: A) => { // redux-actionsのAction<Payload>だとnextに渡せない next(action); if (action.type === 'MOVE_TO' && action.payload) { // reduxのActionだとpayloadが受けられない const { payload: { title, vol, page } } = action; dispatch(push(`/book/${title}/${vol}/${page}`)); } return action; }; }
Javaと違ってTypeScriptは型があってもDuck Typingなんで外形が整ってれば同じ型とみなしてくれるから、型をあわせにいきます。型をあわせるためのinterfaceは関数スコープで宣言してみました。通るし、動くけどねえ。こういうものなのかな?まだまだTypeScriptの言語仕様に不案内なので解決方法が怪しい。