トラブルシューティング
これは、一般的な問題とその解決策を共有するための場所です。例ではReactを使用していますが、他のものを使用している場合でも役に立つはずです。
アクションをディスパッチしても何も起こらない
アクションをディスパッチしようとしても、ビューが更新されない場合があります。なぜこのようなことが起こるのでしょうか? これにはいくつかの理由が考えられます。
Reducerの引数を変更しない
Reduxによって渡された`state`または`action`を変更したくなるかもしれません。しかし、これはしないでください!
Reduxは、Reducerで渡されたオブジェクトを決して変更しないと前提としています。**毎回、新しい状態オブジェクトを返さなければなりません。** Immerのようなライブラリを使用していない場合でも、変更を完全に回避する必要があります。
不変性のおかげで、react-reduxは状態のきめ細かい更新を効率的に購読できます。また、redux-devtoolsを使ったタイムトラベルなど、優れた開発者体験機能も実現できます。
たとえば、状態を変更するため、次のようなReducerは間違っています。
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
// Wrong! This mutates state
state.push({
text: action.text,
completed: false
})
return state
case 'COMPLETE_TODO':
// Wrong! This mutates state[action.index].
state[action.index].completed = true
return state
default:
return state
}
}
次のように書き直す必要があります。
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
// Return a new array
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
// Return a new array
return state.map((todo, index) => {
if (index === action.index) {
// Copy the object before mutating
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
コードは増えますが、Reduxを予測可能で効率的にするものです。コードを減らしたい場合は、`React.addons.update`のようなヘルパーを使用して、簡潔な構文で不変の変換を書くことができます。
// Before:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
// After
return update(state, {
[action.index]: {
completed: {
$set: true
}
}
})
最後に、オブジェクトを更新するには、Underscoreの`_.extend`のようなもの、あるいは、`Object.assign`ポリフィルが必要です。
`Object.assign`を正しく使用していることを確認してください。たとえば、Reducerから`Object.assign(state, newData)`のようなものを返す代わりに、`Object.assign({}, state, newData)`を返します。こうすることで、以前の`state`を上書きしません。
より簡潔な構文のために、オブジェクトスプレッド演算子提案を使用することもできます。
// Before:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
// After:
return state.map((todo, index) => {
if (index === action.index) {
return { ...todo, completed: true }
}
return todo
})
実験的な言語機能は変更される可能性があることに注意してください。
また、深くコピーする必要があるネストされた状態オブジェクトにも注意してください。 `_.extend`と`Object.assign`はどちらも状態の浅いコピーを作成します。ネストされた状態オブジェクトの処理方法については、ネストされたオブジェクトの更新を参照してください。
`dispatch(action)`を呼び出すことを忘れないでください
アクションクリエーターを定義しても、それを呼び出すだけではアクションは自動的にディスパッチされ*ません*。たとえば、このコードは何もしません。
`TodoActions.js`
export function addTodo(text) {
return { type: 'ADD_TODO', text }
}
`AddTodo.js`
import React, { Component } from 'react'
import { addTodo } from './TodoActions'
class AddTodo extends Component {
handleClick() {
// Won't work!
addTodo('Fix the issue')
}
render() {
return <button onClick={() => this.handleClick()}>Add</button>
}
}
アクションクリエーターはアクションを*返す*だけの関数であるため、機能しません。実際にディスパッチするのはあなた次第です。サーバーでレンダリングするアプリケーションはリクエストごとに個別のReduxストアを必要とするため、定義中にアクションクリエーターを特定のストアインスタンスにバインドすることはできません。
ストアインスタンスの`dispatch()`メソッドを呼び出すことで修正できます。
handleClick() {
// Works! (but you need to grab store somehow)
store.dispatch(addTodo('Fix the issue'))
}
コンポーネント階層の深い場所にいる場合、ストアを手動で渡すのは面倒です。そのため、react-reduxでは、Reduxストアにサブスクライブするだけでなく、コンポーネントのプロパティに`dispatch`を挿入する`connect` 高階コンポーネントを使用できます。
修正されたコードは次のようになります。
`AddTodo.js`
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addTodo } from './TodoActions'
class AddTodo extends Component {
handleClick() {
// Works!
this.props.dispatch(addTodo('Fix the issue'))
}
render() {
return <button onClick={() => this.handleClick()}>Add</button>
}
}
// In addition to the state, `connect` puts `dispatch` in our props.
export default connect()(AddTodo)
必要であれば、`dispatch`を他のコンポーネントに手動で渡すことができます。
mapStateToPropsが正しいことを確認する
アクションを正しくディスパッチし、Reducerを適用しても、対応する状態がプロパティに正しく変換されない可能性があります。
その他うまくいかない場合
Reactiflux Discordチャンネルの**#redux**で質問するか、issueを作成してください。
解決策を見つけたら、同じ問題を抱えている次の人への配慮として、このドキュメントを編集してください。