TypeScript
VSCode
redux

TypeScript + Reduxはもうぼちぼちサードライブラリに頼らなくてもある程度はいい感じに補完してくれる

typescript-fsaなど、TypeScriptとReduxを利用する上でサードパーティライブラリが勧められる事があったが、現状のTypeScript 2.9、3.0-rcで普通に書いてみたところ、素reduxでもVSCodeでわりとサクサク補完されるようだ。

import {
  createStore,
  Reducer,
  Action,
  combineReducers,
  ActionCreatorsMapObject,
  ReducersMapObject,
  ActionCreator
} from "redux"

//// State全体の定義
export type AppState = {
  counter: number
}

//// ActionとActionCreatorの定義
// type CounterAction = Action<"INCREMENT" | "DECREMENT"> でも可
type CounterActionType = "INCREMENT" | "DECREMENT"
type CounterAction = Action<CounterActionType>

// これだけだるいが利用側のために二重定義してる。後述
export interface CounterActionCreators
  extends ActionCreatorsMapObject<CounterAction> {
  increment: ActionCreator<CounterAction>
  decrement: ActionCreator<CounterAction>
}
export const counterActions: CounterActionCreators = {
  increment: () => {
    return { type: "INCREMENT" }
  },
  decrement: () => {
    return { type: "DECREMENT" }
  }
}

//// Reducer。genericsらへんはちょっと怠けてる
const counterReducer: Reducer = (state = 0, action: CounterAction) => {
  switch (action.type) {
    case "INCREMENT": // このへんVSCode補完効いて最高の気分
      return state + 1
    case "DECREMENT":
      return state - 1
  }
  return state
}

export const generateStore = () => {
  const reducerMap: ReducersMapObject<AppState> = {
    counter: counterReducer
  }
  return createStore(combineReducers(reducerMap))
}

で、利用側

// Counter sample
import React, { Component } from "react"
import { connect } from "react-redux"
import { bindActionCreators, Dispatch } from "redux"
import {
  counterActions,
  AppState,
  CounterActionCreators
} from "../store"

// State -> Propsに変換する例。
// 変換不要なら type StateProps = AppState でいいだろう
type StateProps = {
  cnt: number
}
// 子のPropsはStatePropsとCounterActionCreatorsを持つ
type ChildProps = StateProps & CounterActionCreators

class CounterInner extends Component<ChildProps> {
  render() {
    return (
      <div>
        <div>{this.props.cnt}</div>
        <button onClick={this.props.increment}>+</button>
        <button onClick={this.props.decrement}>-</button>
      </div>
    )
  }
}

const connectCounter = connect(
  (state: AppState): StateProps => ({
    cnt: state.counter
  }),
  (dispatch: Dispatch) => bindActionCreators(counterActions, dispatch)
)
export const Counter = connectCounter(CounterInner)

この流れで一点だけヒジョーにイケてないのが export interface CounterActionCreators extends ActionCreatorsMapObject<CounterAction>の部分。

ActionCreatorsとして定義されているプロパティが例えばkeyof typeof counterActionsなどでincrement | decrementなど推論がとれてくれればこんなものは不要になるのだが、現状上記のようなものだとstring | numberとなってしまうため、致し方なくCounterActionCreatorsを定義している。

ここらへんもっといい方法あったら知りたい