当我们在使用 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