TypeScriptでrecomposeを使う

## はじめに
普段はReact.SFCで書いてるけど、ステート追加したり、ライフサイクル追加したりしたいことがあります。
recomposeつかえばいいのですが、型定義が結構深くて調べるのが大変でした。調べる度に追記していきます。

## Samples
実際に使ったやつとか組み合わせとかをのせます。

### lifecycle
一番簡単なやつです。`componentDidMount`で統計情報送ったりしたいとき便利です。

```tsx:LifeCycleComponent.tsx
import * as React from 'react';
import {
    lifecycle,
    ReactLifeCycleFunctions,
} from 'recompose';

type Props = {
    text: string;
};

const component: React.SFC<Props> = (props: Props) => {
    return <div>{props.text}</div>;
};

const lifeCycleFunctions: ReactLifeCycleFunctions<Props, {}> = {
    componentWillMount() { console.log('component will mount'); }, // thisにもアクセス可
    componentDidMount() { console.log('component did mount'); },
};

export default lifecycle<Props, {}>(
    lifeCycleFunctions
)(component);
```

### withStateHandlers
これを使うとStateを追加できます。

```tsx:WithStateHandler.tsx
import * as React from 'react';
import {
    mapper,
    StateHandler,
    StateHandlerMap,
    StateUpdaters,
    withStateHandlers,
} from 'recompose';

type Outter = { // こいつがモジュールの外側から注入するProps
    initialCount: number;
};

type State = { // こいつが添加するState
    counter: number;
};

interface Updaters extends StateHandlerMap<State> {
    increment: StateHandler<State>; // Stateを受け取って、'Stateの部分集合を返す関数'を返す。
    decrement: StateHandler<State>;
}

type Props      // こいつには
    = Outter    // 一番外側から渡されるプロパティと
    & State     // Stateの中身と
    & Updaters; // Stateの部分集合を返す関数たちが入ってる

const component: React.SFC<Props> = (props: Props) => {
    return (
        <div>
            <span>{props.counter}</span>
            <button onClick={(e) => {
                e.preventDefault();
                props.increment(2);
            }}>
                increment
            </button>
            <button onClick={(e) => {
                e.preventDefault();
                props.decrement(1);
            }}>
                decrement
            </button>
        </div>
    );
};

const createProps: mapper<Outter, State> // 外側からのプロパティを受け取って、初期Stateを返す
    = (props: Outter): State => ({ counter: props.initialCount });

const stateUpdaters: StateUpdaters<Outter, State, Updaters> = {
    increment: (prev: State, props: Outter): StateHandler<State> => ( // propsは省略可
        (value: number): Partial<State> => ({ // 引数受け取ってもOK
            counter: prev.counter + value,
        })
    ),
    decrement: (prev: State, props: Outter): StateHandler<State> => (
        (value: number): Partial<State> => ({
            counter: prev.counter - value,
        })
    ),
};

export default withStateHandlers<State, Updaters, Outter>( // initialCountをPropertyとするStatefulなコンポーネント
    createProps,
    stateUpdaters,
)(component);
```

### withStateHandlers + lifecycle
`componentWillMount`とかで`setState`をしたいケースがある。(例ではしてないけど。)

```tsx:WithStateHandlerWithLifecycle.tsx
import * as React from 'react';
import {
    compose,
    lifecycle,
    mapper,
    ReactLifeCycleFunctions,
    StateHandler,
    StateHandlerMap,
    StateUpdaters,
    withStateHandlers,
} from 'recompose';

type Outter = {
    initialCount: number;
};

type State = {
    counter: number;
};

interface Updaters extends StateHandlerMap<State> {
    increment: StateHandler<State>;
    decrement: StateHandler<State>;
}

type Props
    = Outter
    & State
    & Updaters;

const component: React.SFC<Props> = (props: Props) => {
    return (
        <div>
            <span>{props.counter}</span>
            <button onClick={(e) => {
                e.preventDefault();
                props.increment(2);
            }}>
                increment
            </button>
            <button onClick={(e) => {
                e.preventDefault();
                props.decrement(1);
            }}>
                decrement
            </button>
        </div>
    );
};

const createProps: mapper<Outter, State>
    = (props: Outter): State => ({ counter: props.initialCount });

const stateUpdaters: StateUpdaters<Outter, State, Updaters> = {
    increment: (prev: State): StateHandler<State> => (
        (value: number): Partial<State> => ({
            counter: prev.counter + value,
        })
    ),
    decrement: (prev: State): StateHandler<State> => (
        (value: number): Partial<State> => ({
            counter: prev.counter - value,
        })
    ),
};

const lifeCycleFunctions: ReactLifeCycleFunctions<Props, {}> = {
    componentWillMount() { console.log('component will mount'); },
    componentDidMount() { console.log('component did mount'); },
};

export default compose<Props, Outter>( // composeを使ってまとめる
    withStateHandlers<State, Updaters, Outter>(createProps, stateUpdaters),
    lifecycle<Props, {}>(lifeCycleFunctions), // lifecycleまできたときにはStateは隠れてるので第二ジェネリック引数は{}でOK
)(component);
```

### withStateHandlers + withHandlers + lifecycle
withStateHandlersだとハンドラに非同期処理をもたせられない…という時用の組み合わせ。別途非同期処理ハンドラをもたせて、stateHandlerを呼び出すような形。
そして、コンポーネントのマウント時に状態をセットする。

```tsx:WithStateHandlerWithAsyncHandlersAndLifecycle.tsx
import * as React from 'react';
import {
    compose,
    withStateHandlers,
    withHandlers,
    StateHandler,
    StateHandlerMap,
    lifecycle,
} from 'recompose';

type Outter = {}; // ここから外側のプロパティを注入できる。

type Body = { // 非同期処理の結果の肩
    [key: string]: string;
};

type State = {
    data: Body;
};

interface StateUpdaters extends StateHandlerMap<State> {
    recieveData: StateHandler<State>; // ここでは普通に状態の更新のインターフェースを用意する
}

type Handlers = {
    fetchData: () => Promise<void>; // StateHandlerとは別に非同期関数を用意する
};

type Props = Outter & State & StateUpdaters & Handlers;

const component: React.SFC<Props> = (props: Props) => {
    const entries = Object.entries(props.data);
    return (
        <dl>
        {
            entries.map((e, i) => ([
                <dt key={i}>{e[0]}</dt>,
                <dd key={i + entries.length}>{e[1]}</dd>,
            ]))
        }
        </dl>
    );
};

export default compose<Props, Outter>(
    withStateHandlers<State, StateUpdaters, Outter>(
        { data: {} },
        {
            recieveData: (_: State) => (body: Body) => ({ data: body }),
        },
    ),
    withHandlers<Props, Handlers>({
        fetchData: props => async () => {
            // ここの実装がミソ。
            try {
                const resp = await fetch('https://api.github.com');
                const body = await resp.json();
                props.recieveData(body); // StateHandlerのインターフェースを呼び出しちゃう。
            } catch (e) {
                console.log(e.message);
            }
        },
    }),
    lifecycle<Props, {}>({
        componentDidMount() {
            this.props.fetchData();
        },
    }),
)(component);
```

## まとめ
型ありがたい。型定義調べると使い方がわかる。