排错

这里会列出常见的问题和对应的解决方案。
虽然使用 React 做示例,但是即使你使用了其它库,这些问题和解决方案仍然对你有所帮助。

dispatch action 后什么也没有发生

有时,你 dispatch action 后,view 却没有更新。这是为什么呢?可能有下面几种原因。

永远不要直接修改 reducer 的参数

如果你想修改 Redux 给你传入的 stateaction,请住手!

Redux 假定你永远不会修改 reducer 里传入的对象。任何时候,你都应该返回一个新的 state 对象。即使你没有使用 Immutable 这样的库,也要保证做到不修改对象。

不变性(Immutability)可以让 react-redux 高效的监听 state 的细粗度更新。它也让 redux-devtools 能提供“时间旅行”这类强大特性。

例如,下面的 reducer 就是错误的,因为它改变了 state:

  1. function todos(state = [], action) {
  2. switch (action.type) {
  3. case 'ADD_TODO':
  4. // 错误!这会改变 state.actions。
  5. state.push({
  6. text: action.text,
  7. completed: false
  8. })
  9. return state
  10. case 'COMPLETE_TODO':
  11. // 错误!这会改变 state[action.index]。
  12. state[action.index].completed = true
  13. return state
  14. default:
  15. return state
  16. }
  17. }

应该重写成这样:

  1. function todos(state = [], action) {
  2. switch (action.type) {
  3. case 'ADD_TODO':
  4. // 返回新数组
  5. return [
  6. ...state,
  7. {
  8. text: action.text,
  9. completed: false
  10. }
  11. ]
  12. case 'COMPLETE_TODO':
  13. // 返回新数组
  14. return state.map((todo, index) => {
  15. if (index === action.index) {
  16. // 修改之前复制数组
  17. return Object.assign({}, todo, {
  18. completed: true
  19. })
  20. }
  21. return todo
  22. })
  23. default:
  24. return state
  25. }
  26. }

虽然需要写更多代码,但是让 Redux 变得可具有可预测性和高效。如果你想减少代码量,你可以用一些辅助方法类似
React.addons.update 来让这样的不可变转换操作变得更简单:

  1. // 修改前
  2. return state.map((todo, index) => {
  3. if (index === action.index) {
  4. return Object.assign({}, todo, {
  5. completed: true
  6. })
  7. }
  8. return todo
  9. })
  10. // 修改后
  11. return update(state, {
  12. [action.index]: {
  13. completed: {
  14. $set: true
  15. }
  16. }
  17. })

最后,如果需要更新 object,你需要使用 Underscore 提供的 _.extend 方法,或者更好的,使用 Object.assign 的 polyfill

要注意 Object.assign 的使用方法。例如,在 reducer 里不要这样使用 Object.assign(state, newData),应该用 Object.assign({}, state, newData)。这样它才不会覆盖以前的 state

你也可以通过 对象操作符 所述的使用更多简洁的语法:

  1. // 修改前:
  2. return state.map((todo, index) => {
  3. if (index === action.index) {
  4. return Object.assign({}, todo, {
  5. completed: true
  6. })
  7. }
  8. return todo
  9. })
  10. // 修改后:
  11. return state.map((todo, index) => {
  12. if (index === action.index) {
  13. return { ...todo, completed: true }
  14. }
  15. return todo
  16. })

注意还在实验阶段的特性会经常改变。

同时要注意那些需要复制的深层嵌套的 state 对象。而 _.extendObject.assign 只能提供浅层的 state 复制。在 更新嵌套的对象 章节中会教会你如何处理嵌套的 state 对象。

不要忘记调用 dispatch(action)

如果你定义了一个 action 创建函数,调用它并会自动 dispatch 这个 action。比如,下面的代码什么也不会做:

TodoActions.js

  1. export function addTodo(text) {
  2. return { type: 'ADD_TODO', text }
  3. }

AddTodo.js

  1. import React, { Component } from 'react'
  2. import { addTodo } from './TodoActions'
  3. class AddTodo extends Component {
  4. handleClick() {
  5. // 不起作用!
  6. addTodo('Fix the issue')
  7. }
  8. render() {
  9. return (
  10. <button onClick={() => this.handleClick()}>
  11. Add
  12. </button>
  13. )
  14. }
  15. }

它不起作用是因为你的 action 创建函数只是一个返回 action 的函数而已。你需要手动 dispatch 它。我们不能在定义时把 action 创建函数绑定到指定的 Store 上,因为应用在服务端渲染时需要为每个请求都对应一个独立的 Redux store。

解法是调用 store 实例上的 dispatch() 方法。

  1. handleClick() {
  2. // 生效!(但你需要先以某种方式拿到 store)
  3. store.dispatch(addTodo('Fix the issue'))
  4. }

如果组件的层级非常深,把 store 一层层传下去很麻烦。因此 react-redux 提供了 connect 这个 @dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750">高阶组件,它除了可以帮你监听 Redux store,还会把 dispatch 注入到组件的 props 中。

修复后的代码是这样的:

AddTodo.js

  1. import React, { Component } from 'react'
  2. import { connect } from 'react-redux'
  3. import { addTodo } from './TodoActions'
  4. class AddTodo extends Component {
  5. handleClick() {
  6. // 生效!
  7. this.props.dispatch(addTodo('Fix the issue'))
  8. }
  9. render() {
  10. return (
  11. <button onClick={() => this.handleClick()}>
  12. Add
  13. </button>
  14. )
  15. }
  16. }
  17. // 除了 state,`connect` 还把 `dispatch` 放到 props 里。
  18. export default connect()(AddTodo)

如果你想的话也可以把 dispatch 手动传给其它组件。

 确保 mapStateToProps 是正确的

你可能正确地 diaptching 一个 action 并且将它提到了 reducer 中,但是对应的 state 却没有通过 props 正确地传输。

其它问题

在 Discord Reactiflux 里的 redux 频道里提问,或者提交一个 issue
如果问题终于解决了,请把解法写到文档里,以便别人遇到同样问题时参考。