解决 Redux 报错问题:错误地尝试更改 Redux 状态

当我们在使用 Redux 进行状态管理时,有时会遇到 Redux 报错问题:错误地尝试更改 Redux 状态。这个错误看起来比较晦涩,不太容易理解,本文将围绕这个主题,详细解决这个报错问题并提供示例代码。

什么是“错误地尝试更改 Redux 状态”?

这个错误提示通常在开发 Redux 应用时出现,它的含义是:某个组件尝试修改 Redux 中的状态,而不是使用 Redux 的 action&reducer 机制来进行状态改变,从而导致了这个错误。

为什么会出现这个错误?

Redux 的核心思想是:所有的 State 都保存在 Store 对象中,而任何的 State 修改操作,必须通过 dispatch Action 的方式来进行。这样做的目的是为了保证状态的可预测性,避免了各个组件之间直接修改 State 导致的意料之外的问题。

我们知道,Redux 中的 State 变化是由 reducer 来处理的,reducer 接收到一个旧的 State 和新的 Action,根据 Action 的类型返回一个新的 State。而我们在 reducer 中修改的 State 不仅仅是一个普通的对象,它是由 Redux 的 createStore 方法产生的一个 immutable 对象。因此,在 reducer 中修改 State 时,需要使用较为复杂的更新方式,比如 Object.assign、Object.freeze、不可变数据等技术手段,否则会被 Redux 拒绝,返回一个新的状态给 Store,使得所有订阅了这个状态的组件都能够实时接受到这个更新。

而如果组件直接修改了 State,Redux 无法知道这个修改操作,就会认为状态被非法篡改,就会报出“错误地尝试更改 Redux 状态”的错误信息。

如何解决这个问题?

如果你在使用 Redux 过程中遇到了这个问题,可以采用下面两种方式来解决。

方式一:确保所有的修改均通过 Action 和 reducer

在 React 组件中,我们应当遵循单向数据流的模式,即父传子通过 props 传递数据,子通过调用 props 中传入的函数来触发事件。如果父组件需要修改 State 中的数据,并影响到子组件的显示,我们需要采用 Redux 的方式来进行状态管理。

可以通过以下方式来做到:

  • 定义 action 类型以及对应的 action 创建函数
export const ADD_TODO = 'ADD_TODO';

export function addTodo(text) {
  return { type: ADD_TODO, text }
}
  • 定义 reducer 来修改 state 对象
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ];
    default:
      return state;
  }
}
  • 在组件中引用上面定义的 action 创建函数,并使用 dispatch 函数来触发 action
import { addTodo } from '../actions';

// ...

dispatch(addTodo('Learn Redux'));

方式二:使用 Redux 中间件来实现异步状态管理

有些情况下,我们需要异步地修改 State 中的数据,而异步操作通常是通过 Ajax 请求或者一些耗时的计算操作来实现的,这时候通过 Action 和 reducer 来修改 State 就不方便。为此,Redux 中提供了中间件机制来管理异步操作。

可以通过以下方式来实现:

  • 定义一个异步操作的 action 创建函数
export function fetchPosts(subreddit) {
  return dispatch => {
    dispatch(requestPosts(subreddit));
    return fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then(response => response.json())
      .then(json => dispatch(receivePosts(subreddit, json)))
  }
}
  • 实现中间件,用来处理异步操作
import thunkMiddleware from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducers'

const store = createStore(
  rootReducer,
  applyMiddleware(
    thunkMiddleware
  )
)

当使用中间件时,我们可以在 action 的函数中进行异步操作,中间件会在 action 到达 reducer 之前,把异步操作的结果做为 payload 附加到 action 上,这样,我们就可以在 reducer 中处理这个 payload,在 store 中更新 state 了。这样就避免了直接修改 state 来更新状态导致的问题。

示例代码

以下是在 React 组件中使用 Redux 的 TotoList 示例代码,使用方式一来解决报错问题:

import React, { Component } from 'react'
import { connect } from 'react-redux';

class TodoList extends Component {

  handleAddClick() {
    let text = prompt('请输入要添加的内容');
    this.props.addTodo(text);
  }

  render() {
    return (
      <div>
        <h2>Todo List</h2>
        <button onClick={this.handleAddClick.bind(this)}>Add Todo</button>
        <ul>
          {this.props.todos.map((todo, index) =>
            <li key={index}>{todo.text}</li>
          )}
        </ul>
      </div>
    )
  }
}

const mapStateToProps = state => ({
    todos: state.todos
});
const mapDispatchToProps = dispatch => ({
    addTodo: text => dispatch(addTodo(text)),
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoList)

在上面的代码中,我们通过 connect 方法把组件和 store 建立起连接,然后在组件中使用 props 来触发状态变化,遵循了单向数据流的模式。这样就可以避免直接修改 state 的问题。

总结

在 Redux 中,我们需要始终遵循 Action 和 reducer 的机制,确保每一个状态变化都是经过 action 创建函数和 reducer 处理过的,否则会导致“错误地尝试更改 Redux 状态”的问题。当需要进行异步操作时,使用中间件来处理会比较方便。遵循 Redux 框架提供的方式,能够有效地保护状态可预测性,快速定位问题,对代码的可维护性和代码质量有重要作用。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65926e6eeb4cecbf2d73ac77


纠错
反馈