Redux 公式ドキュメント 日本語に一部翻訳


http://redux.js.org/

現在は GitHubPages 翻訳を進めています。

Redux の概要

The whole state of your app is stored in an object tree inside a single store.
The only way to change the state tree is to emit an action, an object describing what happened.
To specify how the actions transform the state tree, you write pure reducers.

あなたが作っているアプリケーションの状態 state を全て、たったひとつのオブジェクトで管理し、そしてそれを”store” という変数に入れる。(訳注:もしくは store という概念と紐付けて運用する) 状態を変更する方法は制限されており、”action”を発行する(=emit) 方法だけしかない。action は”何を起こすか”が述べられているオブジェクトである。”action”が状態に対して、どのような変更を加えるのかは、”reducer” によって定義する。reducer は純粋関数である必要がある。

(訳注:純粋関数については例えばこの記事を参照。Redux と state の関連では、私が理解する限り、関数の引数で与えられたもの以外の変動する値を関数内部で使わない(例えば引数では与えられていないグローバル変数など)、引数で与えられたものそのものを変更しない=イミュータブル、return で新たなstateを返す、というルールを守れば良い。)

That’s it!

Redux の要旨はそれで全て!

import { createStore } from 'redux'

/**
 * This is a reducer, a pure function with (state, action) => state signature. 
* 次の関数がReducerです。これは純粋関数でなければいけず、また(state, action ) の2つを与えて、state を return するというのが、特徴です。
* It describes how an action transforms the state into the next state.
* Reducer は、"action"がどのように状態satateを変更し、新しい状態 next state を作り出すかを定義しています。
 *
 * The shape of the state is up to you: it can be a primitive, an array, an object,状態の形式は自由で、普通の配列やオブジェクトでもいいですし、
 * or even an Immutable.js data structure. The only important part is that you should ほかにも Immutable.js を使ったデータ形式でも良いです。重要なのは、状態を変更する場合に、
 * not mutate the state object, but return a new object if the state changes. 状態管理用のオブジェクトを、直接変化させるのではなく、新しく新しい状態を持ったオブジェクトを作り、それを return することです。
 *
 * In this example, we use a `switch` statement and strings, but you can use a helper that この例では、"switch文"と文字列を使って分岐を行っていますが、他の方法、例えば maps 関数などを使うこともできます。
 * follows a different convention (such as function maps) if it makes sense for your
 * project.
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

// Create a Redux store holding the state of your app.次のコードは、Redux 用のstore を作り、それに自分のアプリケーションの 状態を与えています。(訳注:さらに、引数としてreducerであるcounterを与えている。reducerには初期値が与えられているので結果として、ここではstate=0が与えられている。)
// Its API is { subscribe, dispatch, getState }.このようにして作られたstoreには上記のようなAPIがあります。
let store = createStore(counter)

// You can use subscribe() to update the UI in response to state changes.状態が変更された際に、それにともなってUIを変更したい場合には、subscribe()を使用します。
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly. 通常はsubscribe()を使うよりも、React Redux のような View を binding するライブラリを使うでしょう。
// However it can also be handy to persist the current state in the localStorage. しかし、ちょっとした作業にはこちらのほうが便利な場合もあります。

store.subscribe(() =>
  console.log(store.getState())
)

// The only way to mutate the internal state is to dispatch an action. 状態を変更する方法は限定されており、"action"を"dispatch"する方法のみが用意されています。
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: 'INCREMENT' }) アクションは、直列に実行され、記録され、保存され、後でもう一度それを実行することもできます。
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

Instead of mutating the state directly, you specify the mutations you want to happen with plain objects called actions. Then you write a special function called a reducer to decide how every action transforms the entire application’s state.

状態を直接変更するのではなく、まずどのような変化を起こしたいのかを plain なオブジェクトに記述します。これを action と呼びます。次に reducer とよばれる特別な関数を書き、それぞれの action がどのように状態全体を変更するのかを規定します。

If you’re coming from Flux, there is a single important difference you need to understand. Redux doesn’t have a Dispatcher or support many stores. Instead, there is just a single store with a single root reducing function. As your app grows, instead of adding stores, you split the root reducer into smaller reducers independently operating on the different parts of the state tree. This is exactly like how there is just one root component in a React app, but it is composed out of many small components.

もし以前に Flux を使ったことがあるのであれば、Flux と Redux には一つ重要な違いがありますので、その点を理解してください。Redux は Dispathcer を持たず、また複数の store を持つこともできません。Redux は一つだけ sotre を持ち、それに対して一つだけ reducer を付加することができます。アプリケーションの規模が大きくなる場合には、store を増やすのではなく、root reducer をより小さな reducer へと分割し、それぞれの reducer が、状態ツリーのの異なる場所を担当するようにします。これはちょうど、React がルートコンポーネントをひとつだけ持ち、しかしルートコンポーネントが、沢山の小さなコンポーネントの組み合わせでできているのと、同じようなものです。

This architecture might seem like an overkill for a counter app, but the beauty of this pattern is how well it scales to large and complex apps. It also enables very powerful developer tools, because it is possible to trace every mutation to the action that caused it. You can record user sessions and reproduce them just by replaying every action.

このような構造は、恐らくちょっとしたカウンター・アプリには大げさすぎるのでしょうが、その強みは、より大きく複雑なアプリケーションになったときに発揮されます。また強力な開発者ツールも提供していますので、これによって全ての変更を追いかけることができます。ユーザーの操作を全て記録し、その全てのアクションを再度実行することで、これを再現することもできます。

http://redux.js.org/docs/introduction/CoreConcepts.html

Core Concepts

Redux itself is very simple.

Redux 自体は非常にシンプルな構造をしています。

Imagine your app’s state is described as a plain object. For example, the state of a todo app might look like this:

あなたの作ったアプリケーションの状態が、一つのシンプルなオブジェクトに記述されているとしましょう。例えば todo アプリの状態が次のような場合を想定しましょう。

{
  todos: [{
    text: 'Eat food',
    completed: true
  }, {
    text: 'Exercise',
    completed: false
  }],
  visibilityFilter: 'SHOW_COMPLETED'
}

This object is like a “model” except that there are no setters. This is so that different parts of the code can’t change the state arbitrarily, causing hard-to-reproduce bugs.

このオブジェクトはいわゆる”model”に似ていますが、少し違うのは、setter を持っていない点です。なぜこうなっているかというと、コード内の様々な場所で状態を恣意的に変更すると、再現不可能なバグが生じてしまうからです。

To change something in the state, you need to dispatch an action. An action is a plain JavaScript object (notice how we don’t introduce any magic?) that describes what happened. Here are a few example actions:

状態に変更を加える場合には、”action”を”dispatch”します。”action”はただのJSオブジェクトで、何が起きるのかが記述されています。次のコードは、”action”の例です。

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

Enforcing that every change is described as an action lets us have a clear understanding of what’s going on in the app. If something changed, we know why it changed. Actions are like breadcrumbs of what has happened. Finally, to tie state and actions together, we write a function called a reducer. Again, nothing magical about it—it’s just a function that takes state and action as arguments, and returns the next state of the app. It would be hard to write such a function for a big app, so we write smaller functions managing parts of the state:

全ての変更に関する内容が”action”として定義されているので、私たちはアプリケーションの中で何が起きているのかを正確に把握することができます。何かが変更されときにはいつでも、どうしてそうなったのかを知ることができます。Action は何が起きたのかということに関するパンくずリストのようなものです。最後に、状態とactionを結びつけるために、reducer と呼ばれる関数を書きます。特殊なものではありません。単に状態とアクションを引数として受取り、そして次の状態を return するファンクションです。アプリケーションが大きくなった場合には、このような関数を書くのは難しくなってくるので、そのような場合には、状態の一部分だけを担当する小さなファンクションを書くこともできます。

function visibilityFilter(state = 'SHOW_ALL', action) {
  if (action.type === 'SET_VISIBILITY_FILTER') {
    return action.filter
  } else {
    return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([{ text: action.text, completed: false }])
    case 'TOGGLE_TODO':
      return state.map(
        (todo, index) =>
          action.index === index
            ? { text: todo.text, completed: !todo.completed }
            : todo
      )
    default:
      return state
  }
}

And we write another reducer that manages the complete state of our app by calling those two reducers for the corresponding state keys:

そして、状態全体を担当する reducer を書いて、この中で先程の2つのreducer を実行します。この2つのreducer それぞれは、対応する state の key の部分で実行されます。

function todoApp(state = {}, action) {
  return {
    todos: todos(state.todos, action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  }
}

This is basically the whole idea of Redux. Note that we haven’t used any Redux APIs. It comes with a few utilities to facilitate this pattern, but the main idea is that you describe how your state is updated over time in response to action objects, and 90% of the code you write is just plain JavaScript, with no use of Redux itself, its APIs, or any magic.

ここまでが Redux の基本的な概念です。まだ Redux API は一つも使っていないことにお気づきでしょうか。Redux API はいくつかパターン管理を楽にしてくれる諸機能を提供してくれますが、Redux の主要な概念は、Action オブジェクトに搬送して、状態がどのように変更されるかを自分で書く、というものであって、コードの90%は単なるJSで記述されます。そういう意味ではReduxも、そのAPIも必要ありません。

http://redux.js.org/docs/introduction/ThreePrinciples.html

3つの原則

Redux can be described in three fundamental principles:

Redux は3つの基本原則に則って記述されます。

一つだけの真実の源泉

The state of your whole application is stored in an object tree within a single store.

アプリケーション全体の状態を、単一のオブジェクト内に保持し、それを単一の storeと結びつける。

This makes it easy to create universal apps, as the state from your server can be serialized and hydrated into the client with no extra coding effort. A single state tree also makes it easier to debug or inspect an application; it also enables you to persist your app’s state in development, for a faster development cycle. Some functionality which has been traditionally difficult to implement – Undo/Redo, for example – can suddenly become trivial to implement, if all of your state is stored in a single tree.

そうなっていれば、一般的なアプリケーションを作る際に楽です。サーバーから取得した状態は、serialize (訳注:よくわからない) され、余計なコーディングの面倒さなしに、クライアント側に渡すことができます。さらにデバグも簡単だし、アプリケーションの状態を調べるのも簡単です。それによって開発時に状態を検証することが容易になるので、開発サイクルをより早くすることができます。また、一般的に実装するのが難しいと考えられてきた機能、例えば unde/redo 機能も、状態が単一の場所で管理されていれば、簡単に実装できます。

console.log(store.getState())

/* Prints
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
*/

状態は、読み取り専用

The only way to change the state is to emit an action, an object describing what happened.

状態を変更する手段は限定されており、action を発行することによってのみ可能です。action とは、何が起きるのかが書かれているオブジェクトです。

This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state. Because all changes are centralized and happen one by one in a strict order, there are no subtle race conditions to watch out for. As actions are just plain objects, they can be logged, serialized, stored, and later replayed for debugging or testing purposes.

これによって、view や network callbacks(訳注:なんのことかよくわからず)が直接状態を書き換えることを、防ぐことができます。そうではなくて、状態を変更する「意図」を表明します。全ての変更は、一箇所で中央管理され、1つづつ厳密な順番で発生させられるので、注意が必要な不明瞭な競合状態が発生しません。action 自体は単なるオブジェクトなので、記録され、serialized され、保持され、そして後からそれを再現してデバグをしたり、テストをすることができます。

store.dispatch({
  type: 'COMPLETE_TODO',
  index: 1
})

store.dispatch({
  type: 'SET_VISIBILITY_FILTER',
  filter: 'SHOW_COMPLETED'
})

変更は、純粋関数で行われる

To specify how the state tree is transformed by actions, you write pure reducers.

状態がactionによってどのように変化させられるかを記述する場合には、純粋関数によるruducerを用いること。

Reducers are just pure functions that take the previous state and an action, and return the next state. Remember to return new state objects, instead of mutating the previous state. You can start with a single reducer, and as your app grows, split it off into smaller reducers that manage specific parts of the state tree. Because reducers are just functions, you can control the order in which they are called, pass additional data, or even make reusable reducers for common tasks such as pagination.

Reducer は単なる純粋関数で、現在の状態とactionを引数として受け取って、次の状態をreturnするものです。この際に、状態を直接変更するのではなく、新しい状態用のオブジェクトをreturnするようにしてください。最初は一つのreducerで始めれば良いのですが、アプリケーションが大きくなってきた場合には、reducer をより小さなreducerへと分割して、状態の特定の場所だけを担当させるようにします。reducerは単なる関数ですから、実行される順番をコントロールすることができ、また追加の情報を与えたり、さらにいえばページネーション用のものなど、一般的によくあるタスクに対して、繰り返し利用可能reducerを作ることもできます。

function visibilityFilter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

import { combineReducers, createStore } from 'redux'
const reducer = combineReducers({ visibilityFilter, todos })
const store = createStore(reducer)

That’s it! Now you know what Redux is all about.

以上です!Redux の全貌がわかりましたね!

Basics

Don’t be fooled by all the fancy talk about reducers, middleware, store enhancers—Redux is incredibly simple. If you’ve ever built a Flux application, you will feel right at home. If you’re new to Flux, it’s easy too!

よくわからない話に惑わされないでください。reducer, middleware, store enhancer … Redux は驚くほどシンプルなのです。Flux アプリケーションを作ったことがあるのであれば、自分の家にいるような快適さを感じるはずです。もちろんそうでない人にとっても、とっても簡単です!

In this guide, we’ll walk through the process of creating a simple Todo app.

このガイドは、シンプルなtodoアプリケーションを作るプロセスを紹介していきます。

Actions

First, let’s define some actions.

まずは”action”を定義しましょう。

Actions are payloads of information that send data from your application to your store. They are the onlysource of information for the store. You send them to the store using store.dispatch().

Action は情報を持った貨物のようなもので、アプリケーションから store へと運ばれます。Action は情報だけをもっています。store.dispatch()を使ってその情報をstoreへ運びます。

Here’s an example action which represents adding a new todo item:

次のサンプルは、新しい todo アイテムを追加する action です。

const ADD_TODO = 'ADD_TODO'
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants. Once your app is large enough, you may want to move them into a separate module.

Action は単なるJSオブジェクトです。Action は必ず type プロパティを持たなくてはいけません。これによって実行されるアクションがどれなのかを示します。Typeは一般的に文字列を含んだ定数によって定義されます。アプリケーションが大きくなってきた場合には、これらを分割してモジュールとして管理したくなるでしょう。

import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
Note on Boilerplate

You don’t have to define action type constants in a separate file, or even to define them at all. For a small project, it might be easier to just use string literals for action types. However, there are some benefits to explicitly declaring constants in larger codebases. Read Reducing Boilerplate for more practical tips on keeping your codebase clean.

Action タイプを定数で定義し別々のファイルに分ける必要がない、もしくはそもそも定数で定義する必要もない場合もあるでしょう。とりわけ小さなプロジェクトにおいては、単に文字列でaction タイプを指定するだけでいいかもしれません。しかし、コードが大きくなってきた場合には、action typeを定数で宣言しておく利点があります。より実践的な内容に関しては、次の記事を読んで下さい。

Other than type, the structure of an action object is really up to you. If you’re interested, check out Flux Standard Action for recommendations on how actions could be constructed.

typeの他にも、action オブジェクトには、どのような構造をも持たせることができます。興味があれば次の記事を読んで、action のオススメの構造について学んでください。

We’ll add one more action type to describe a user ticking off a todo as completed. We refer to a particular todo by index because we store them in an array. In a real app, it is wiser to generate a unique ID every time something new is created.

今回はもう一つactionを追加しましょう。todo リストが完了した際に実行されるアクションです。特定のtodoアイテムをindexによって参照します。todoアイテムはstoreの中に配列として持つことになるので、これを参照するためです。実際のアプリケーションにおいては、新しいアイテムが作られる度に、ユニークなIDを生成するほうが賢いやり方でしょう。

{
  type: TOGGLE_TODO,
  index: 5
}

It’s a good idea to pass as little data in each action as possible. For example, it’s better to pass indexthan the whole todo object.

なるべく少ない情報をactionには与えるほうがいいでしょう。例えばtodoオブジェクト全体を渡すよりも、indexだけを渡す方が良い、といったようなことです。

Finally, we’ll add one more action type for changing the currently visible todos.

最後に、もう一つアクションタイプを追加します。これは現在のタスクの表示非表示を切り替えるためのものです。

{
  type: SET_VISIBILITY_FILTER,
  filter: SHOW_COMPLETED
}

Action Creators

Action creators are exactly that—functions that create actions. It’s easy to conflate the terms “action” and “action creator,” so do your best to use the proper term.

Action creator はアクションを作る関数です。action と action creator は混同しやすいので、適切な言葉をしっかり使えるように注意してください。

In Redux action creators simply return an action:

Redux の action creator は単純に、action を return します。

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

This makes them portable and easy to test.

こうすることで、取り回しがよくなり、かつ、テストも容易になります。

In traditional Flux, action creators often trigger a dispatch when invoked, like so:

伝統的なFluxにおいては、アクションクリエイターは呼び出された場合に、たいていの場合、dispatchをもトリガーします。

function addTodoWithDispatch(text) {
  const action = {
    type: ADD_TODO,
    text
  }
  dispatch(action)
}

In Redux this is not the case.
Instead, to actually initiate a dispatch, pass the result to the dispatch() function:

しかし、Redux においてはそうではありません。その代わりに、dispatchを実際に初期化し、その結果をdispatch()関数へと渡します。

dispatch(addTodo(text))
dispatch(completeTodo(index))

Alternatively, you can create a bound action creator that automatically dispatches:

他の方法としてbound action creator を作る方法もあります。これは自動的にdispatchを呼び出すものです。

const boundAddTodo = text => dispatch(addTodo(text))
const boundCompleteTodo = index => dispatch(completeTodo(index))

Now you’ll be able to call them directly:

こうすることで、これらのdispatchを直接実行することができます。(訳注:addTodoというactionクリエイターとdispatachを関連付けた関数、boudAddTodoを作って、これを呼び出せば、アクションクリエイターもdispatchも同時に実行されるので、毎回それぞれを書かなくて良いので便利。)

boundAddTodo(text)
boundCompleteTodo(index)

The dispatch() function can be accessed directly from the store as store.dispatch(), but more likely you’ll access it using a helper like react-redux‘s connect(). You can use bindActionCreators() to automatically bind many action creators to a dispatch() function.

dispatch()関数は、storeから直接、store.dispatch()という形でアクセすることができます。ですが、より使われるのは、react-reduxのconnect()というヘルパー関数を用いて、アクセスする方法です。さらにbindActionCreators()をつかってたくさんのactionクリエイターをdispatch()関数に紐付けることもできます。

Action creators can also be asynchronous and have side-effects. You can read about async actions in the advanced tutorial to learn how to handle AJAX responses and compose action creators into async control flow. Don’t skip ahead to async actions until you’ve completed the basics tutorial, as it covers other important concepts that are prerequisite for the advanced tutorial and async actions.

Actionクリエイターは非同期で、かつ副次的効果をもたらしてしまいます。 async actions を読んで、AJAXの返答や、action creatorを同期的なフローの中に組み込む方法を学習してください。async action に進むのは、reduxの基本的なチュートリアルを終えてからにしてください。基本的なチュートリアルには、より高度なチュートリアルと、それからasync action の理解に必要な、重要な前提知識が含まれているからです。

Source Code

actions.js

/*
 * action types
 */

export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * other constants
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action creators
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

Next Steps

Now let’s define some reducers to specify how the state updates when you dispatch these actions!

次はreducerを定義していきましょう。これによって、actionがディスパッチされた際に、どのようにstateをアップデートするかを定義します。

Reducers

Actions describe the fact that something happened, but don’t specify how the application’s state changes in response. This is the job of reducers.

Actionは何かが発生した、という事実を記述していました。しかしそれに呼応して、どのようにアプリケーションの状態が変化するのか、ということは全く定義していません。それは「Reducer」の仕事です。

Designing the State Shape

In Redux, all the application state is stored as a single object. It’s a good idea to think of its shape before writing any code. What’s the minimal representation of your app’s state as an object?

Reduxにおいては、アプリケーションの全ての状態は、ひとつのオブジェクトとして保持されます。コードを書く前に、状態がどのような形になるのか考えてみることにしましょう。あなたのアプリケーションの状態を表すオブジェクトは、一番簡潔に表現するとしたら、どのようなものになるでしょうか?

For our todo app, we want to store two different things:

いまから作ろうとしているtodo appにおいては、二つの異なるものを保持しないといけませんね。

  • The currently selected visibility filter; 現在選択されているフィルター
  • The actual list of todos. todoの書かれたリスト

You’ll often find that you need to store some data, as well as some UI state, in the state tree. This is fine, but try to keep the data separate from the UI state.

 

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
Note on Relationships

In a more complex app, you’re going to want different entities to reference each other. We suggest that you keep your state as normalized as possible, without any nesting. Keep every entity in an object stored with an ID as a key, and use IDs to reference it from other entities, or lists. Think of the app’s state as a database. This approach is described in normalizr’s documentation in detail. For example, keeping todosById: { id -> todo } and todos: array<id> inside the state would be a better idea in a real app, but we’re keeping the example simple.

Handling Actions

Now that we’ve decided what our state object looks like, we’re ready to write a reducer for it. The reducer is a pure function that takes the previous state and an action, and returns the next state.

(previousState, action) => newState

It’s called a reducer because it’s the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue). It’s very important that the reducer stays pure. Things you should never do inside a reducer:

  • Mutate its arguments;
  • Perform side effects like API calls and routing transitions;
  • Call non-pure functions, e.g. Date.now() or Math.random().

We’ll explore how to perform side effects in the advanced walkthrough. For now, just remember that the reducer must be pure. Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.

With this out of the way, let’s start writing our reducer by gradually teaching it to understand the actionswe defined earlier.

We’ll start by specifying the initial state. Redux will call our reducer with an undefined state for the first time. This is our chance to return the initial state of our app:

import { VisibilityFilters } from './actions'

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
}

function todoApp(state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }

  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

One neat trick is to use the ES6 default arguments syntax to write this in a more compact way:

function todoApp(state = initialState, action) {
  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

Now let’s handle SET_VISIBILITY_FILTER. All it needs to do is to change visibilityFilter on the state. Easy:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

Note that:

  1. We don’t mutate the state. We create a copy with Object.assign()Object.assign(state, { visibilityFilter: action.filter }) is also wrong: it will mutate the first argument. You mustsupply an empty object as the first parameter. You can also enable the object spread operator proposal to write { ...state, ...newState } instead.
  2. We return the previous state in the default case. It’s important to return the previous state for any unknown action.
Note on Object.assign

Object.assign() is a part of ES6, but is not implemented by most browsers yet. You’ll need to either use a polyfill, a Babel plugin, or a helper from another library like _.assign().

Note on switch and Boilerplate

The switch statement is not the real boilerplate. The real boilerplate of Flux is conceptual: the need to emit an update, the need to register the Store with a Dispatcher, the need for the Store to be an object (and the complications that arise when you want a universal app). Redux solves these problems by using pure reducers instead of event emitters.

It’s unfortunate that many still choose a framework based on whether it uses switch statements in the documentation. If you don’t like switch, you can use a custom createReducer function that accepts a handler map, as shown in “reducing boilerplate”.

Handling More Actions

We have two more actions to handle! Just like we did with SET_VISIBILITY_FILTER, we’ll import the ADD_TODO and TOGGLE_TODO actions and then extend our reducer to handle ADD_TODO.

import { VisibilityFilters, ADD_TODO, TOGGLE_TODO } from './actions'

...

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

Just like before, we never write directly to state or its fields, and instead we return new objects. The new todos is equal to the old todos concatenated with a single new item at the end. The fresh todo was constructed using the data from the action.

Finally, the implementation of the TOGGLE_TODO handler shouldn’t come as a complete surprise:

case TOGGLE_TODO:
  return Object.assign({}, state, {
    todos: state.todos.map((todo, index) => {
      if (index === action.index) {
        return Object.assign({}, todo, {
          completed: !todo.completed
        })
      }
      return todo
    })
  })

Because we want to update a specific item in the array without resorting to mutations, we have to create a new array with the same items except the item at the index. If you find yourself often writing such operations, it’s a good idea to use a helper like immutability-helperupdeep, or even a library like Immutable that has native support for deep updates. Just remember to never assign to anything inside the state unless you clone it first.

Splitting Reducers

Here is our code so far. It is rather verbose:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}

Is there a way to make it easier to comprehend? It seems like todos and visibilityFilter are updated completely independently. Sometimes state fields depend on one another and more consideration is required, but in our case we can easily split updating todos into a separate function:

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: todos(state.todos, action)
      })
    default:
      return state
  }
}

Note that todos also accepts state—but it’s an array! Now todoApp just gives it the slice of the state to manage, and todos knows how to update just that slice. This is called reducer composition, and it’s the fundamental pattern of building Redux apps.

Let’s explore reducer composition more. Can we also extract a reducer managing just visibilityFilter? We can.

Below our imports, let’s use ES6 Object Destructuring to declare SHOW_ALL:

const { SHOW_ALL } = VisibilityFilters

Then:

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

Now we can rewrite the main reducer as a function that calls the reducers managing parts of the state, and combines them into a single object. It also doesn’t need to know the complete initial state anymore. It’s enough that the child reducers return their initial state when given undefined at first.

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

Note that each of these reducers is managing its own part of the global state. The stateparameter is different for every reducer, and corresponds to the part of the state it manages.

This is already looking good! When the app is larger, we can split the reducers into separate files and keep them completely independent and managing different data domains.

Finally, Redux provides a utility called combineReducers() that does the same boilerplate logic that the todoApp above currently does. With its help, we can rewrite todoApp like this:

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

Note that this is equivalent to:

export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

You could also give them different keys, or call functions differently. These two ways to write a combined reducer are equivalent:

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

All combineReducers() does is generate a function that calls your reducers with the slices of state selected according to their keys, and combining their results into a single object again. It’s not magic.And like other reducers, combineReducers() does not create a new object if all of the reducers provided to it do not change state.

Note for ES6 Savvy Users

Because combineReducers expects an object, we can put all top-level reducers into a separate file, export each reducer function, and use import * as reducers to get them as an object with their names as the keys:

import { combineReducers } from 'redux'
import * as reducers from './reducers'

const todoApp = combineReducers(reducers)

Because import * is still new syntax, we don’t use it anymore in the documentation to avoid confusion, but you may encounter it in some community examples.

Source Code

reducers.js

import { combineReducers } from 'redux'
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

Next Steps

Next, we’ll explore how to create a Redux store that holds the state and takes care of calling your reducer when you dispatch an action.

1 thought on “Redux 公式ドキュメント 日本語に一部翻訳”

Leave a Reply

Your email address will not be published. Required fields are marked *