Edited at

TypeScirptを使ってReactとreduxをconnectしようとしたらはまった

ランサーズ Advent Calendar 2018」9日目の記事になります。

ランサーズでは最近Reactのフロントエンドの開発にTypeScriptを導入しました。

導入当初の自分のスペックは以下の通りでした。


  • React/Reduxで過去にプロダクト開発経験アリ

  • Flowはすこし使ったことがある

  • TypeScriptはまったく触ったことがない

過去にFlowを使ったことがあったのでTypeScriptに乗り換えても何とかなるだろうと思っていたのですが、

ReduxとReactのconnectでベストプラクティスが見つからずに苦労したのでまとめます。


TL;DR


User.tsx

import * as React from 'react';

import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';

class UserComponent extends React.Component<Props> {
render() {
const { user, title } = this.props;
return (
<div>
<div>{title}</div>
<div>{user.name}</div>
</div>
);
}
}

interface User {
name: string;
}

interface ownProps {
title: string;
}

interface StateProps {
user: User;
}

interface DispatchProps {
actions: {
getUserStart: Function;
};
}

function mapStateToProps(state: { user: User }): StateProps {
return {
user: state.user,
};
}

function mapDispatchToProps(dispatch: Dispatch): DispatchProps {
return {
actions: bindActionCreators({ getUserStart }, dispatch),
};
}

type Props = ownProps & StateProps & DispatchProps;

export default connect<StateProps, DispatchProps>(
mapStateToProps,
mapDispatchToProps
)(UserComponent);



解説


コンポーネントにpropsの型を渡す

class UserComponent extends React.Component<Props> {

コンポーネントを定義するときはReact.Componentのジェネリクス<>にpropsの型Propsを渡します。

ここで渡す型はattributesとして渡すpropsやreduxから渡すpropsなどすべてを含めたものになります。


Propsを定義する

次にownProps,StateProps,DispatchPropsの3つの型をつくります。

それぞれの型は以下のpropsに対応しています。


  • ownProps → attributesとして渡すprops

  • StateProps → mapStateToPropsで注入するprops

  • DispatchProps → mapDispatchToPropsで注入するprops

これらの型を&でマージします。

マージしてできた型Propsは前節で説明したようにReact.Componentのジェネリクスに渡します。

type Props = ownProps & StateProps & DispatchProps;


connectする

最後にreduxとコンポーネントをconnectします。

connectのジェネリクスにStatePropsDispatchPropsを渡します。

export default connect<StateProps, DispatchProps>(

mapStateToProps,
mapDispatchToProps
)(UserComponent);

作成したコンポーネントは下のように使うことができます。

ownPropsで定義したpropsはattributesとして値を渡すことができます。


App.tsx

import * as React from 'react';

import User from './UserComponent';

const App = () => (
<div>
<User title={'title'} />
</div>
)



まとめ

TypeScriptを導入しはじめのときは、どうやって型を定義してジェネリクスに渡せばよいのかわからずにはまってしまうことが多かったです。

特にTypeScriptでライブラリを使うときはドキュメントもなく、自分で型定義ファイルを読み進めて実装する必要があるので最初は大変です。

しかし、型に慣れてきた今ではTypeScriptの恩恵を存分に受けており、開発スピードも向上していると感じています。

JavaScriptで開発をされている方は一度トライしてみるとよいと思います。


追記

最近更に簡潔に型定義する方法を見つけました。

ReturnTypeとtypeofを組み合わせることでmapStateToPropsとmapDispatchToPropsの型定義をせずにconnectできます。最高。


User.tsx

import * as React from 'react';

import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';

class UserComponent extends React.Component<Props> {
render() {
const { user, title } = this.props;
return (
<div>
<div>{title}</div>
<div>{user.name}</div>
</div>
);
}
}

interface User {
name: string;
}

interface ownProps {
title: string;
}

function mapStateToProps(state: { user: User }) {
return {
user: state.user,
};
}

function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators({ getUserStart }, dispatch),
};
}

type Props = ownProps & ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;

export default connect(
mapStateToProps,
mapDispatchToProps
)(UserComponent);