車輪の二度漬け

railsを触っていましたが、最近はreactに興味あります。

react + redux-thunk + typescriptでよくあるカウンターのサンプル

redux-thunkをreactとtypescriptで使ってみたかったので、よくある簡単なカウンターを作ってみた。
ただ、typescriptに慣れてなかったりそもそもreduxに触ったばっかりって感じで、表示のガワ部分を作ったあとのactionやreducerあたりは全部presentationalなコンポーネントの中に書いている。
いきなりファイルを分けすぎるとかえって把握しづらかったため。

index.tsx

import React from "react"
import ReactDOM from "react-dom"
import "./index.css"
import * as serviceWorker from "./serviceWorker"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import { createStore, applyMiddleware } from "redux"
import Buttons, { reducer } from "./Buttons"

let store = createStore(reducer, applyMiddleware(thunk))

ReactDOM.render(
  <Provider store={store}>
    <Buttons />
  </Provider>,
  document.getElementById("root")
)
serviceWorker.unregister()

Buttons.tsx(よくあるApp.tsxと同じ)

import * as React from "react"
import { connect } from "react-redux"
import { Dispatch, Reducer, ActionCreator } from "redux"
import AddButton from "./AddButton"
import CounterPanel from "./CounterPanel"
import DecrementButton from "./DecrementButton"
import IncrementButton from "./IncrementButton"
import AsyncIncrementButton from "./AsyncIncrementButton"
import { ThunkAction, ThunkDispatch } from "redux-thunk"

interface CounterState {
  count: number
}

enum CounterActionType {
  ADD = "COUNT/ADD",
  INCREMENT = "COUNT/INCREMENT",
  DECREMENT = "COUNT/DECREMENT"
}

interface CounterAction {
  type: CounterActionType
  amount?: number
}

interface ButtonsProps {
  count?: number
  add?: (amount: number) => void
  increment?: () => void
  decrement?: () => void
  asyncIncrement?: () => void
}

const Buttons: React.FC<ButtonsProps> = ({
  count = 0,
  add = () => {},
  increment = () => {},
  decrement = () => {},
  asyncIncrement = () => {}
}) => {
  return (
    <div>
      <CounterPanel count={count} />
      <IncrementButton onClick={increment} />
      <DecrementButton onClick={decrement} />
      <AddButton onClick={() => add(10)} />
      <AsyncIncrementButton onClick={asyncIncrement} />
    </div>
  )
}

// action
const add = (amount: number): CounterAction => ({
  amount,
  type: CounterActionType.ADD
})

const increment = (): CounterAction => ({
  type: CounterActionType.INCREMENT
})

const decrement = (): CounterAction => ({
  type: CounterActionType.DECREMENT
})

const asyncIncrement: ActionCreator<
  ThunkAction<void, CounterActionType, null, CounterAction>
> = () => {
  return (dispatch: Dispatch) => {
    setTimeout(() => {
      dispatch({ type: CounterActionType.INCREMENT })
    }, 3000)
  }
}

// reducer
export const reducer: Reducer<CounterState, CounterAction> = (
  state: CounterState = { count: 0 },
  action: CounterAction
): CounterState => {
  switch (action.type) {
    case CounterActionType.ADD:
      return {
        ...state,
        count: state.count + (action.amount || 0)
      }
    case CounterActionType.INCREMENT:
      return {
        ...state,
        count: state.count + 1
      }
    case CounterActionType.DECREMENT:
      return {
        ...state,
        count: state.count - 1
      }
    default:
      const _: never = action.type
      return state
  }
}

interface StateProps {
  count: number
}

interface DispatchProps {
  add: (amount: number) => void
  increment: () => void
  decrement: () => void
}

const mapStateToProps = (state: CounterState): StateProps => ({
  count: state.count
})

const mapDispatchToProps = (
  dispatch: ThunkDispatch<any, any, CounterAction>
) => ({
  add: (amount: number) => dispatch(add(amount)),
  increment: () => dispatch(increment()),
  decrement: () => dispatch(decrement()),
  asyncIncrement: () => dispatch(asyncIncrement())
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Buttons)

typescriptの型指定はまだ全然慣れない。
前の会社ではC++とかJavaとか触ってたけど、今の会社でrubyを触り始めて型についてすべて忘れてしまった。

このサンプルを作ってる中で気になったんだけど、reduxってどの程度hooksで置き換えられるんだろう。
100%は無理にしても割と置き換えられるんじゃないかなあって気がしている。

最近は

booth.pm


この本が良かった。
萌え系って苦手だから表紙でちょっと避けてたけど、中見たら萌え要素全く無かったし、内容もかなりわかりやすく書かれていたしですごくよかった。
そろそろサンプル以上のものを作るようにしたい。