Redux 实战:React 应用中如何处理全局状态

在 React 应用开发过程中,全局状态管理是一个不可避免的问题,尤其是在业务逻辑复杂的情况下。管理全局状态的方案有很多,其中 Redux 是比较受欢迎的一种解决方案。本文将介绍 Redux 的基本概念、使用方法和最佳实践,帮助读者更好地理解和使用 Redux。

Redux 的基本概念

Redux 是一个 JavaScript 状态容器,其中所有的状态都存在一个单一的、可预测的数据源中。Redux 的主要思想是应用中所有的状态都以一个对象树的形式存储在一个单一的存储库中。

Redux 的核心包括三个部分:

  1. Store:存储应用中所有状态的容器。
  2. Action:用于描述状态变化的对象。
  3. Reducer:用于处理 Action 并返回新的状态。

Redux 的使用方法

安装 Redux

首先,需要安装 Redux 包。可以使用 npm 或者 yarn 进行安装。

npm install --save redux
# or
yarn add redux

Redux 还有一些相关的包,比如 react-redux 用于在 React 应用中使用 Redux。可以根据情况安装。

创建 store

在创建 store 之前,需要定义 Redux 应用的状态,即 state。在 Redux 应用中,state 是不可变的。具体来说,一个 state 对应一个应用的状态,所有的修改都产生一个新的 state。

{
  visibilityFilter: 'SHOW_ALL',
  todos: [{
    text: 'Consider using Redux',
    completed: true,
  }, {
    text: 'Keep all state in a single tree',
    completed: false
  }]
}

创建 store 时需要传入 reducer。reducer 是处理 action 以及返回新状态的函数。一个 reducer 接收旧的状态和新的 action,返回一个新的状态。reducer 必须是纯函数,即对于相同的输入,总是返回相同的输出。

import { createStore } from 'redux'

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return { ...state, visibilityFilter: action.filter }
    case ADD_TODO:
      return [
        ...state.todos,
        { text: action.text, completed: false }
      ]
    // ...
    default:
      return state
  }
}

let store = createStore(todoApp)

更改状态

store 提供了三个方法来处理状态:getState、dispatch 和 subscribe。

  • getState():获取当前状态。
  • dispatch(action):改变当前状态。
  • subscribe(listener):添加一个监听器,当状态更改时调用。

在改变状态时,需要 dispatch 一个 action。一个 action 是一个描述状态变化的对象,其中 type 字段是必须的。其他字段可以根据情况添加。在 reducer 中处理 action 时需要根据 type 进行分类。

const ADD_TODO = 'ADD_TODO'

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

store.dispatch(addTodo('Learn Redux'))

使用 React-Redux

使用 React-Redux 可以更方便地在 React 应用中使用 Redux。

  • Provider:将 store 声明为应用的顶层组件。
  • connect:将组件连接到 Redux store。
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './containers/App'
import rootReducer from './reducers'

let store = createStore(rootReducer)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

Redux 的最佳实践

让 reducer 更纯粹

reducer 必须是纯函数。它不应直接修改输入的 state,而是返回一个新的 state。

拆分 reducer

将 reducer 拆分成更小的 reducer,每个 reducer 只处理 state 中的一部分。使用 combineReducers 可以将多个 reducer 合并成一个 root reducer。

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
  todos,
  visibilityFilter
})

使用 action 创建函数

为了更方便地创建 action,可以使用 action 创建函数,它们返回一个 action。Action 创建函数接收输入参数,并返回一个 action 对象。

export const setVisibilityFilter = filter => ({
  type: SET_VISIBILITY_FILTER,
  filter
})

与 React 结合

在 React 应用中使用 Redux 时,应避免在组件内存储状态。所有状态都应该存在 Redux store 中。在 React 的生命周期方法中调用 dispatch 方法。

示例代码

下面是一个使用了 Redux 和 React 的 TodoList 应用的示例代码。

import React from 'react'
import { connect } from 'react-redux'

import { addTodo } from '../actions'

const AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => input = node} />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}

export default connect()(AddTodo)
import React from 'react'
import { connect } from 'react-redux'

import { toggleTodo } from '../actions'

const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
)

const Todos = ({ todos, toggleTodo }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => toggleTodo(todo.id)}
      />
    )}
  </ul>
)

const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter)
})

const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Todos)
import React from 'react'
import { connect } from 'react-redux'

import { setVisibilityFilter } from '../actions'

const Link = ({ active, children, onClick }) => (
  <button
    onClick={e => {
      e.preventDefault()
      onClick()
    }}
    disabled={active}
    style={{
      marginLeft: '4px'
    }}
  >
    {children}
  </button>
)

const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)
import React from 'react'

import AddTodo from '../containers/AddTodo'
import Todos from '../containers/Todos'
import Footer from './Footer'

const App = () => (
  <div>
    <AddTodo />
    <Todos />
    <Footer />
  </div>
)

export default App
import React from 'react'

import FilterLink from '../containers/FilterLink'

const Footer = () => (
  <div>
    <FilterLink filter="SHOW_ALL">
      All
    </FilterLink>
    <FilterLink filter="SHOW_ACTIVE">
      Active
    </FilterLink>
    <FilterLink filter="SHOW_COMPLETED">
      Completed
    </FilterLink>
  </div>
)

export default Footer

总结

Redux 是一个非常强大的状态管理工具,能够帮助我们更好地管理 React 应用中的状态。本文介绍了 Redux 的基本概念、使用方法和最佳实践,并给出了示例代码。希望读者能够更好地理解和掌握 Redux。

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


纠错反馈