🌐

Flux + useReducer + TypeScriptの構築

4 min read 1

Reduxの公式チュートリアルを参考にFluxを実装。

完成形のプロジェクト
https://github.com/asano-yuki/react-typescript-template

1. バージョン情報

$ node -v
v12.15.0
$ yarn -v
1.22.4
$ npx create-react-app --version
3.4.1

2. 開発環境の構築

npx create-react-app [プロジェクト名] --template typescript

3. ファイル構成

srcディレクトリ配下で、必要な部分のみ抜粋。

src
├── components
  ├── AddTodo.tsx
  ├── TodoList.tsx  
├── modules
    ├── actions
        ├── actionTypes.ts
        ├── todos.ts	
    ├── contexts
        ├── todoContext.ts
    ├── reducers
        ├── todos.ts    
├── App.tsx
├── index.tsx

3. Actions

modules/actions/actionTypes.ts
export default {
  ADD_TODO    : 'ADD_TODO',
  DELETE_TODO : 'DELETE_TODO'
} as const
modules/actions/todos.ts
import types from './actionTypes'

export interface Actions {
  type: typeof types.ADD_TODO | typeof types.DELETE_TODO
  payload: {
    id: number,
    content?: string
  }
}

let nextTodoId = 0

export const addTodo = (content: string): Actions => ({
  type: types.ADD_TODO,
  payload: { 
    id: ++nextTodoId,
    content
  }
})

export const deleteTodo = (id: number): Actions => ({
  type: types.DELETE_TODO,
  payload: { id }
})

4. Reducers

modules/reducers/todos.ts
import types from '../actions/actionTypes'
import { Actions } from '../actions/todos'

export type State = Actions['payload'][]

const Todos = (state: State, action: Actions): State => {
  const { type, payload } = action
  switch (type) {
    case types.ADD_TODO:
      return [...state, payload]
    case types.DELETE_TODO:
      const id = payload.id
      return state.filter(item => item.id !== id)
    default:
      return state
  }
}

export default Todos

5. Context

Storeの実装はuseContextを使用する。
useReducerのstate、dispatchをcontextで管理することで、どのコンポーネントからでもアクセス可能とする。

modules/contexts/todoContext.ts
import { createContext } from 'react'
import { State } from '../reducers/todos'
import { Actions } from '../actions/todos'

interface ContextTypes {
  todos: State
  dispatch: (value: Actions) => void
}

export default createContext<ContextTypes>({
  todos: [],
  dispatch: () => {}
})

6. コンポーネントに実装

index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
App.tsx
import React, { useReducer } from 'react'
import TodoContext from './modules/contexts/todoContext'
import reducer from './modules/reducers/todos'
import AddTodo from './components/AddTodo'
import TodoList from './components/TodoList'

const App: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, [])
  return (
    <TodoContext.Provider value={{ todos: state, dispatch }}>
      <h1>Todo App</h1>
      <AddTodo />
      <TodoList />
    </TodoContext.Provider>
  )
}

export default App
components/AddTodo.tsx
import React, { useState, useContext } from 'react'
import TodoContext from '../modules/contexts/todoContext'
import { addTodo } from '../modules/actions/todos'

const AddTodo: React.FC = () => {
  const [input, setInput] = useState('')
  const { dispatch } = useContext(TodoContext)
  return (
    <div>
      <input type='text' value={input} onChange={e => setInput(e.target.value)} />
      <button onClick={() => dispatch(addTodo(input))}>Add</button>
    </div>
  )
}

export default AddTodo
components/TodoList.tsx
import React, { useContext } from 'react'
import TodoContext from '../modules/contexts/todoContext'
import { deleteTodo } from '../modules/actions/todos'

const TodoList: React.FC = () => {
  const { todos, dispatch } = useContext(TodoContext)
  return (
    <ul>
      {
        todos.map(({ id, content }) => (
          <li key={id}>
            {content}
            <button onClick={() => dispatch(deleteTodo(id))}>Delete</button>
          </li>
        ))
      }
    </ul>
  )
}

export default TodoList

Discussion

とても参考になりました!
何か喉に引っかかっていたものが取れたようなすっきりした気分です!(描写が貧弱ですみません。。)

ログインするとコメントできます