故障排除
这里是分享常见问题及其解决方案的地方。
示例使用 React,但如果你使用其他框架,仍然会觉得有用。
触发(dispatch)一个 action 后没有任何反应
有时你尝试 dispatch 一个 action,但视图没有更新。这是为什么?可能有几个原因。
不要修改 reducer 的参数
修改 Redux 传给你的 state 或 action 是很诱人的,但千万不要这么做!
Redux 假设你永远不会修改传给 reducer 的对象。每一次,都必须返回新的状态对象。 即使你不使用类似 Immer 这样库,也需要完全避免变异(mutation)。
不可变性使得 react-redux 能够高效订阅状态的细粒度更新。同时也支持诸如 redux-devtools 的时间旅行等极佳的开发体验功能。
例如,下面的 reducer 是错误的,因为它修改了 state:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
// 错误!这会修改 state
state.push({
text: action.text,
completed: false
})
return state
case 'COMPLETE_TODO':
// 错误!这会修改 state[action.index]
state[action.index].completed = true
return state
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
}
}
代码稍微多一点,但这正是使 Redux 可预测且高效的关键。
如果你想写更少的代码,可以用类似 React.addons.update 的辅助工具,通过简洁的语法写不可变变换:
// 之前:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
// 之后:
return update(state, {
[action.index]: {
completed: {
$set: true
}
}
})
最后,要更新对象,你需要用类似 Underscore 里的 _.extend,或更好的 Object.assign polyfill。
确保正确使用 Object.assign。例如,别直接返回 Object.assign(state, newData),而要返回 Object.assign({}, state, newData),这样不会覆盖之前的 state。
你也可以使用对象扩展运算符(object spread operator)提案,语法更简洁:
// 之前:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
// 之后:
return state.map((todo, index) => {
if (index === action.index) {
return { ...todo, completed: true }
}
return todo
})
注意,实验语言特性可能会改变。
同时注意需要深拷贝的嵌套状态对象。_.extend 和 Object.assign 只会做浅拷贝。参考 更新嵌套对象,了解如何处理嵌套状态。
别忘了调用 dispatch(action)
如果你定义了 action creator,调用它不会自动 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() {
// 不会生效!
addTodo('Fix the issue')
}
render() {
return <button onClick={() => this.handleClick()}>Add</button>
}
}
它不生效,因为你的 action creator 只是一个返回 action 的函数。真正 dispatch 是你的责任。我们无法在定义时把 action creator 绑定到特定的 store,因为服务器渲染需要为每个请求单独的 Redux store。
解决方案是调用 store 实例的 dispatch() 方法:
handleClick() {
// 有效!但你需要获取 store
store.dispatch(addTodo('Fix the issue'))
}
如果你处在组件层级很深的位置,手动传递 store 非常麻烦。
这也是为什么 react-redux 提供了 connect 高阶组件,它除了帮你订阅 Redux store,还会把 dispatch 注入到你的组件 props 里。
修正后的代码像这样:
AddTodo.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addTodo } from './TodoActions'
class AddTodo extends Component {
handleClick() {
// 有效!
this.props.dispatch(addTodo('Fix the issue'))
}
render() {
return <button onClick={() => this.handleClick()}>Add</button>
}
}
// 除了 state,`connect` 会把 `dispatch` 放入组件的 props。
export default connect()(AddTodo)
你也可以将 dispatch 手动传递给其他组件。
确保 mapStateToProps 函数正确
有时你正确地 dispatch 了 action 并且 reducer 也起作用了,但对应的状态没有正确映射到组件 props。
其他问题
可以去 #redux Reactiflux Discord 频道问问,或者 创建 issue。
如果你解决了问题,也欢迎编辑此文档,帮助下一个遇到同样问题的人。