4
5

More than 5 years have passed since last update.

Reduxドキュメント「2.2 Reducers」翻訳

Last updated at Posted at 2016-07-13

2.2 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 a reducer.

Actionは何かあったという事実を記述するが、応答の中のどのようにアプリケーションの状態が変化したかは明確にしない。

Designing the State Shape

状態の形状をデザインする

In Redux, all 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アプリのために、我々は2つの異なるものを保持したい。

The currently selected visibility filter;
The actual list of todos.
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.

現在選択されたvisibility filterとtodoの実際のリストです。その状態のツリーの中にあなたはUIの状態と同じく、いくつかのデータを保持する必要があることが分かります。これは問題ありませんが、UIの状態からデータを分離しておくようにしてください。

{
  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 inside the state would be a better idea in a real app, but we’re keeping the example simple.

関係についての注意
より複雑なアプリでは、異なるエンティティが互いに参照するようになるでしょう。ネストせずにできるだけ状態を通常に保つことを我々は提案します。キーとしてIDと一緒に格納されているオブジェクト内の全てのエンティティを維持し、他のエンティティまたは、リストからそれを参照するためのIDを使用しています。データベースとしてアプリの状態を考えてください。このアプローチは詳細にnormalizrのマニュアルに記載されています。例えば、状態の内部でtodoById:{id -> todo}とtodos:arrayを維持することは実際のアプリケーションではよりよいアイディアでしょうが、私たちは例を簡単に保っています。

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.

状態オブジェクトがどのように見えるかを決定した今となっては、状態オブジェクトのためのreducerを書く準備ができました。reducerは前の状態とアクションをとい次の状態を返す純粋な関数です。

(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:

「Array.prototype.reduce(reducer, ?initialValue)」に渡すであろう関数の型なのでreducerと呼ばれます。reducerが純粋関数であることは非常に重要です。あなたがreducerの内部でやるべきではないことは:

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

  • 引数を変化させる。
  • APIの呼び出しやルーティングの遷移のような副作用を実行する。
  • 純粋関数ではないものを呼ぶ。例えば、Date.now()や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.

私たちは高度なチュートリアルで副作用を実行する方法を見ていきます。今のところ、reducerが純粋関数でなければいけないことだけを覚えておいてください。同じ引数を与えると次の状態を計算してそれを返すべきです。驚かないでください。副作用はありません。APIを呼びません。変化しません。ただ計算します。

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

我々が以前に定義したactionを理解するためにそれを徐々に教えることによってreducerを書き始めましょう。

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:

私たちは初期状態を指定するところから始めます。Reduxは始めはundefinedの状態でreducerを呼びます。これは私たちのアプリの初期状態を返す機会です。

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:

秘訣はより簡潔な方法でこれを書くためにES6のデフォルト引数構文を使うことです。

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:

すぐにSET_VISIBILITY_FILTERをハンドリングしましょう。それは状態に応じてvisibilityFilterを全て変更する必要があります。

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 must supply 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.

  1. 私たちは状態を変化させません。私たちはObject.assign()でコピーを作成します。Object.assign(state, { visibilityFilter: action.filter })は間違いです。それは最初の引数を変化させます。あなたは最初のパラメータとして空のオブジェクトを提供するべきです。
    あなたは代わりに{ ...state, ...newState }で展開するオブジェクト展開演算子を書くこともできます。

  2. 私たちはデフォルトの場合以前の状態を返します。どのような未知の行動のために以前の状態を返すことが重要です。

Note on Object.assign

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().

Object.assign()はES6のメソッドですが、まだほとんどのブラウザで実装されていません。あなたは、polyfillやBabelプラグイン、または_.assign()のような別のライブラリからのヘルパのいずれかが必要です。

Note on switch and Boilerplate

switchと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.

switchステートメントは本当のboilerplateではありません。Fluxの本当のboilerplateは概念的なものです。updateを発行する必要、dispatcherとStoreを登録する必要、そしてStoreはオブジェクト(あなたが一般的なアプリケーションを望むとき発生する複雑化)である必要があります。Reduxはイベント発生装置の代わりに純粋関数であるreducerを使用することによってそれらの問題を解決します。

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”.

多くがドキュメンテーションによってswitch分を使うかどうかに基づくフレームワークをまだ選択するのは不幸です。もしあなたがswitch分が嫌いなら、「boilerplateを減らす」ことで示すように、handler mapを受け入れるカスタマイズされたcreateReducerを使用することができます。

Handling More Actions

より多くのアクションのハンドリング

We have two more actions to handle! Let’s extend our reducer to handle ADD_TODO.

2つ以上のハンドリングするためのアクションがあります。さあ、ADD_TODOをハンドリングするためにreducerを拡張しましょう。

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.

ちょうど以前のように、私たちは直接的に状態やフィールドを書きません、そして代わりに新しいオブジェクトを返します。新しいTODOは最後にひとつの新しいアイテムと連結した古いTODOと同等です。新しいTODOはactionからのデータを使って組み立てられます。

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

最後に、TOGGLE_TODハンドラの実装は驚くべきものではありません。

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 react-addons-update, updeep, 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.

私たちは変化させるために再ソートすることなく配列の中の特定のアイテムを更新したいので、インデックスでアイテムを除いた同じアイテムで新しい配列を作る必要があります。もし、あなたがそのような操作を頻繁に書いていることに気づいたら、react-addons-update,updeepのようなヘルパや、ディープアップデートのためのネイティブサポートを持つイミュータブルなライブラリを利用するのはよい考えです。あなたがそれを最初にクローンしない限り、状態の内部には決して何もアサインされないことだけ覚えておいてください。

Splitting Reducers

reducerを分割する

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:

より簡単に理解する方法はありますか?todosとvisibilityFilterは完全に独立して更新されると思われます。ときどき状態のフィールドは状態とは別のものに依存し、より深い考慮が要求されましたが、このケースでは関数を分けることでtodosの更新をより簡単に分割することができます。

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.

todosは状態(だが、それは配列!)を受け入れることに注意してください。今todoAppはtodosに管理のために状態のスライスを与え、todosはそのスライスをアップデートする方法を知ります。これはreducer compositionと呼ばれ、Reduxアプリケーション構築の基本的なパターンです。

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

より深くreducer compositionを見てみましょう。visibilityFilterを管理するreducerを抽出することができますか?私たちはできます。

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.

今私たちは状態の一部を管理しているreducerを呼び、そしてそれらをシングルオブジェクトの中で組み合わせる関数としてメインのreducerを書き直すことができます。これ以上、完成した初期状態をしる必要もありません。最初にundefinedが与えられたときに、子供のreducerがそれらの初期状態を返すので十分です。

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 state parameter is different for every reducer, and corresponds to the part of the state it manages.

それぞれのreducerはグローバルな状態の一部として自身を管理しています。状態パラメータはreducerごとに異なって、それが管理する状態の一部と一致します。

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:

最後に、Reduxは今やったtodoAppと同じboilerplateロジックのcombineReducers()と呼ばれるユーティリティを提供します。その助けで、私たちはこのようにtodoAppを書き直すことができます。

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

Note that this is completely 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 completely equivalent:

あなたはそれらに異なるキーを与え、異なった関数を呼ぶこともできます。組み合わせたreducerを書くための2つの方法は完全にこれと同等です。

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.

全てのcombineReducers()はキーに従って選択された状態のスライスであなたのreducerを呼び、再びシングルオブジェクトのなかで組み合わせる関数を生成します。これは魔法ではありません。

Note for ES6 Savvy Users

ES6のしっかりしたユーザのための注意

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:

combineReducersはオブジェクトを期待するので、私たちは全てのトップレベルのreducerを分割したファイルに入れ、それぞれのreducerの関数をexportし、キーとしてそれらの名前のオブジェクトとしてreducerを取得するために「import * as reducers」を利用することができる。

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.

「import * 」はまだ新しい文法なので、混乱を避けるために私たちはドキュメントでこれ以上利用しませんが、いくつかのコミュニティのサンプルでその書き方に出会うかもしれません。

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

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5