本期推荐的 Redux是JavaScript 应用程序的可预测状态容器。
Redux它可以帮助您编写行为一致、在不同环境(客户端、服务器和本机)中运行并且易于测试的应用程序。最重要的是,它提供了出色的开发人员体验,例如实时代码编辑与时间旅行调试器相结合。您可以将 Redux 与React或任何其他视图库一起使用。它很小(2kB,包括依赖项),但有一个庞大的插件生态系统可用。
Redux Toolkit是我们官方推荐的编写 Redux 逻辑的方法。它围绕着 Redux 核心,并包含我们认为对于构建 Redux 应用程序至关重要的包和功能。Redux Toolkit 构建在我们建议的最佳实践中,简化了大多数 Redux 任务,防止常见错误,并使编写 Redux 应用程序更容易。
核心概念
想象一下,您的应用程序的状态被描述为一个普通对象。例如,待办事项应用程序的状态可能如下所示:
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
这个对象就像一个“模型”,只是没有设置器。这是为了让代码的不同部分不能随意改变状态,导致难以重现的错误。
要更改状态中的某些内容,您需要调度一个操作。动作是一个简单的 JavaScript 对象(注意我们没有引入任何魔法?),它描述了发生的事情。以下是一些示例操作:
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
强制将每个更改都描述为一个操作,让我们清楚地了解应用程序中正在发生的事情。如果某事发生了变化,我们就知道它为什么会发生变化。行动就像已经发生的事情的面包屑。最后,为了将状态和动作联系在一起,我们编写了一个名为 reducer 的函数。同样,它并没有什么神奇之处——它只是一个将状态和动作作为参数的函数,并返回应用程序的下一个状态。为大型应用程序编写这样的函数会很困难,因此我们编写较小的函数来管理部分状态:
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
我们编写了另一个reducer,通过调用这两个reducer来获取相应的状态键来管理我们应用程序的完整状态:
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
这基本上是 Redux 的全部思想。请注意,我们没有使用任何 Redux API。它附带了一些实用程序来促进这种模式,但主要思想是你描述你的状态如何随着时间的推移更新以响应动作对象,你编写的 90% 的代码只是纯 JavaScript,没有使用 Redux它本身、它的 API 或任何魔法。
安装Redux
Redux Toolkit 包括 Redux 核心,以及我们认为对于构建 Redux 应用程序至关重要的其他关键包(例如 Redux Thunk 和 Reselect)。
它可以作为 NPM 上的一个包与模块捆绑器或 Node 应用程序一起使用:
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
它也可以作为 UMD 构建使用,可以从unpkg 上的dist文件夹加载。UMD 构建使 Redux Toolkit 可用作window.RTK全局变量。
Redux
# NPM
npm install redux
# Yarn
yarn add redux
配套
很可能,您还需要React 绑定和开发人员工具。
npm install react-redux
npm install --save-dev @redux-devtools/core
请注意,与 Redux 本身不同,Redux 生态系统中的许多包不提供 UMD 构建,因此我们建议使用Webpack和Browserify等 CommonJS 模块捆绑器以获得最舒适的开发体验。
创建一个 React Redux
使用 React 和 Redux 启动新应用程序的推荐方法是使用官方 Redux+JS 模板或Redux+TS 模板进行Create React App,它利用了Redux Toolkit和 React Redux 与 React 组件的集成。
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
配置您的商店
首先,让我们看一下index.js我们创建商店的原始文件:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'
const store = createStore(rootReducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
在这段代码中,我们将 reducer 传递给 ReduxcreateStore函数,该函数返回一个store对象。然后我们将此对象传递给react-redux Provider组件,该组件在组件树的顶部呈现。
这确保了每当我们通过 连接到应用程序中的 Redux 时,react-redux connect我们的组件都可以使用商店。
扩展 Redux
大多数应用程序通过添加中间件或存储增强器来扩展其 Redux 存储的功能(注意:中间件很常见,增强器不太常见)。中间件为 Redux 函数增加了额外的dispatch功能;增强器为 Redux 存储添加了额外的功能。
我们将添加两个中间件和一个增强器:
- redux-thunk中间件,它允许简单的异步使用调度。
- 一个中间件,它记录分派的动作和产生的新状态。
- 一个增强器,它记录减速器处理每个动作所花费的时间。
npm install redux-thunk
中间件
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
export default logger
增强器
const round = number => Math.round(number * 100) / 100
const monitorReducerEnhancer =
createStore => (reducer, initialState, enhancer) => {
const monitoredReducer = (state, action) => {
const start = performance.now()
const newState = reducer(state, action)
const end = performance.now()
const diff = round(end - start)
console.log('reducer process time:', diff)
return newState
}
return createStore(monitoredReducer, initialState, enhancer)
}
export default monitorReducerEnhancer
让我们将这些添加到我们现有的index.js.
- 首先,我们需要 importredux-thunk加上我们的loggerMiddlewareand monitorReducerEnhancer,再加上 Redux 提供的两个额外的函数:applyMiddlewareand compose。
- 然后,我们使用applyMiddleware创建一个商店增强器,它将我们的loggerMiddleware和应用thunkMiddleware到商店的调度功能。
- 接下来,我们将composenewmiddlewareEnhancer和 our组合monitorReducerEnhancer成一个函数。
- 这是必需的,因为您只能将一个增强器传递到createStore. 要使用多个增强器,您必须首先将它们组合成一个更大的增强器,如本例所示。
- 最后,我们将这个新composedEnhancers函数createStore作为它的第三个参数传入。注意:我们将忽略的第二个参数允许您将状态预加载到存储中。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyMiddleware, createStore, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducers'
import loggerMiddleware from './middleware/logger'
import monitorReducerEnhancer from './enhancers/monitorReducer'
import App from './components/App'
const middlewareEnhancer = applyMiddleware(loggerMiddleware, thunkMiddleware)
const composedEnhancers = compose(middlewareEnhancer, monitorReducerEnhancer)
const store = createStore(rootReducer, undefined, composedEnhancers)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
学习资源
Redux 文档旨在教授 Redux 的基本概念,并解释在实际应用程序中使用的关键概念。但是,文档不能涵盖所有内容。令人高兴的是,还有许多其他很棒的资源可用于学习 Redux。我们鼓励您查看它们。其中许多涵盖的主题超出了文档的范围,或者以其他可能更适合您的学习方式的方式描述了相同的主题。
React、Redux 和 Typescript 简介:Redux 维护者 Mark Erikson 的幻灯片集,涵盖了 React、Redux 和 TypeScript 的基础知识。Redux 主题包括 store、reducers、中间件、React-Redux 和 Redux Toolkit。
https://blog.isquaredsoftware.com/2020/12/presentations-react-redux-ts-intro/
Redux 教程:概述和演练:Tania Rascia 编写良好的教程,快速解释了 Redux 的关键概念,并展示了如何组合一个基本的 Redux + React 应用程序使用 vanilla Redux 和 Redux Toolkit。
https://www.taniarascia.com/redux-react-guide/
Redux 初学者 – 学习Redux的大脑友好指南:使用 Redux Toolkit 和 React-Redux 构建一个小型 todo 应用程序,包括数据获取。
https://www.freecodecamp.org/news/redux-for-beginners-the-brain-friendly-guide-to-redux/
使用 Redux Toolkit 和 Typescript 让 Redux 变得简单:一个有用的教程,展示了如何一起使用 Redux Toolkit 和 TypeScript 来编写 Redux应用程序,以及 RTK 如何简化典型的 Redux 使用。
https://www.mattbutton.com/redux-made-easy-with-redux-toolkit-and-typescript/
基本示例
应用程序的整个全局状态存储在单个store内的对象树中。更改状态树的唯一方法是创建一个action,一个描述发生了什么的对象,并将其分派到 store。要指定状态如何更新以响应操作,您编写纯reducer函数,根据旧状态和操作计算新状态。
import { createStore } from 'redux'
/**
* 这是一个reducer - 一个接受当前状态值和描述“发生了什么”的动作对象的函数,并返回一个新的状态值。
* reducer 的函数签名是:(state, action) => newState
*
* Redux 状态应该只包含普通的 JS 对象、数组和原语。
* 根状态值通常是一个对象。重要的是你不应该改变状态对象,而是在状态改变时返回一个新对象。
*
* 你可以在 reducer 中使用任何你想要的条件逻辑。在此示例中,
* 我们使用 switch 语句,但这不是必需的。
*/
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
// 创建一个 Redux 存储来保存你的应用程序的状态。
// 它的 API 是 { subscribe, dispatch, getState }。
let store = createStore(counterReducer)
// 您可以使用 subscribe() 来更新 UI 以响应状态更改。
// 通常你会使用视图绑定库(例如 React Redux)而不是直接订阅()。
// 可能还有其他用例对订阅也有帮助。
store.subscribe(() => console.log(store.getState()))
// 改变内部状态的唯一方法是调度一个动作。
// 这些动作可以被序列化、记录或存储,然后再重放。
store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}
您无需直接更改状态,而是使用称为actions的普通对象指定要发生的更改。然后你编写一个称为reducer的特殊函数来决定每个动作如何转换整个应用程序的状态。
Redux 工具包示例
Redux Toolkit 简化了编写 Redux 逻辑和设置存储的过程。使用 Redux Toolkit,相同的逻辑如下所示:
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
// Redux Toolkit 允许我们在 reducer 中编写“变异”逻辑。它
// 没有实际上改变了状态,因为它使用了 Immer 库,
// 它检测到“草稿状态”的变化并根据这些变化产生一个全新的
// 不可变状态
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({
reducer: counterSlice.reducer
})
// 仍然可以订阅商店
store.subscribe(() => console.log(store.getState()))
// 仍然将动作对象传递给 `dispatch`,但它们是为我们创建的
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}
Redux Toolkit 允许我们编写更短且更易于阅读的逻辑,同时仍然遵循相同的 Redux 行为和数据流。
—END—