Redux 中文官网
React Redux 英文官网 和 React Redux 中文文档
Redux Toolkit 英文官网 和 Redux 中文官网之 Redux Toolkit 介绍
Redux DevTools 中文官网
Redux 是一个全局应用状态管理库。
Redux 的基本思想:应用中使用集中式的全局状态来管理,并明确更新状态的模式,以便让代码具有可预测性。
Redux 期望所有状态更新都是使用不可变的方式——更新原数据的复本。
并非所有应用程序都需要 Redux,那么,何时需要考虑使用 Redux 呢?
结合 React 一般使用 react-redux(见下文)。
安装 react-redux:
npm i -S react-redux
官方推荐的 Redux 的包:
Action 是一个对象。用来描述应用程序中发生了什么的事件。
Action 需要通过 store.dispatch() 方法来发送。
Action 对象包含 2 个属性:
域/事件名称
”——“该 action 的特征或类别 / 该 action 表示的具体事情”。例如:
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
Reducer 是一个纯函数。它是一个事件监听器,它根据接收到的 action(事件)类型处理事件。
Reducer 函数接收 2 个参数:
【拓展】Reducer 是一个纯函数,所以绝对不要在 reducer 里面做一些引入 副作用 的事情。比如:
例如:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// 检查 reducer 是否关心这个 action
if (action.type === 'counter/increment') {
// 如果是,复制 `state`
return {
...state,
// 使用新值更新 state 副本
value: state.value + 1
}
}
// 返回原来的 state 不变
return state
}
根 reducer(root reducer)是管理整个 State 的 reducer。
Redux 里,一个 Store 对应一个 State 状态,所以整个 State 对象就应该由一个总的 reducer 函数管理。但是,如果所有的状态更改逻辑都放在这一个 reducer 里面,显然会变得巨大而难以维护。得益于纯函数的实现,我们只需要让状态树上的每个字段都有一个自己的 reducer 函数来管理,就可以将大的 reducer 拆分成多个小的 reducer 了。然后在根 reducer 里合并成一个大的 reducer。
在根 reducer 里合并 reducerA 和 reducerB:
function someApp(state = {}, action) {
return {
a: reducerA(state.a, action),
b: reducerB(state.b, action)
};
}
为了简化 reducer 合并,Redux 提供了一个工具函数 combineReducers()。
import { combineReducers } from 'redux';
const someApp = combineReducers({
a: reducerA,
b: reducerB
});
Store 是一个对象。
应用中应有且仅有一个 Store。
Store 的作用:
官方推荐使用 @reduxjs/toolkit 包(见下文)的 configureStore() 方法来创建一个 store。需要给 configureStore 方法传入一个 reducer 对象。通过 getState() 的方法可以获取当前 store 中 state 对象。
例如:
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState()) // {value: 0}
当然,也可以直接使用 Redux 包的 createStore() 方法来创建一个 store。
例如:
import { createStore } from 'redux';
import someApp from './reducers';
let store = createStore(someApp);
// 你也可以额外指定一个初始 State(initialState),这对于服务端渲染很有用
// let store = createStore(someApp, window.STATE_FROM_SERVER);
Dispatch 是一个函数。它是更新 state 的唯一方法。store 通过调用 Dispatch 来执行所有 reducer 函数,并最终计算出更新后的 state。
Dispatch 函数接收 1 个参数:一个 action 对象。 返回一个新的 state 对象,若该 state 对象没有发生变化则与原来一样。
例如:
// 使用 subscribe 方法创建一个函数,监听 State 的更改
let unsubscribe = store.subscribe(() => console.log(store.getState()));
// 分发 action
store.dispatch({ type: 'CHANGE_A' });
store.dispatch({ type: 'CHANGE_B', payload: 'Modified b' });
// 停止侦听状态更新
unsubscribe();
// 获取更新后的 state 对象
console.log(store.getState())
import {createStore} from 'redux';
function count(state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1
case 'REDUCER':
return state - 1;
default:
return state
}
}
// 创建一个仓库
let store = createStore(count);
let currentValue = store.getState();
console.log('当前的值:', currentValue);
// 定义一个监听的方法
let listener = () => {
const previosValue = currentValue;
currentValue = store.getState();
console.log('上一个值:', previosValue, '当前值:', currentValue)
}
// 创建一个监听
store.subscribe(listener);
// 分发任务
store.dispatch({type:"ADD"});
store.dispatch({type:"ADD"});
store.dispatch({type:"ADD"});
store.dispatch({type:"REDUCER"});
Redux 的 API
React Redux 的 API
Redux Toolkit 的 API
Redux 的 API
创建一个包含程序完整 state 树的 Redux store。
createStore 可以接收 3 个参数:
createStore 返回值一个 Store。保存了应用程序所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe state 的变化,然后更新 UI。
Store() 方法:
随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。
combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。
通过为传入对象的 reducer 命名不同的 key 来控制返回 state key 的命名。
通常的做法是命名 reducer,然后 state 再去分割那些信息,这样你可以使用 ES6 的简写方法:combineReducers({ counter, todos })。这与 combineReducers({ counter: counter, todos: todos }) 是等价的。
combineReducers 方法接收一个参数:
combineReducers 方法返回一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象。
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
Middleware 最常见的使用场景是无需引用大量代码或依赖类似 Rx 的第三方库实现异步 actions。这种方式可以让你像 dispatch 一般的 actions 那样 dispatch 异步 actions。
applyMiddleware 方法的参数:
applyMiddleware 方法的返回值:一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名是 createStore => createStore,但是最简单的使用方法就是直接作为最后一个 enhancer 参数传递给 createStore() 函数。
把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Redux,react-redux 会提供 dispatch 函数让你直接调用它 。
惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
为方便起见,你也可以传入 action creator 作为第一个参数,并且得到一个 dispatch 函数作为返回值。
bindActionCreators 方法的参数:
bindActionCreators 方法的返回值:一个与原对象类似的对象,只不过这个对象的 value 都是会直接 dispatch 原 action creator 返回的结果的函数。如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
从右到左来组合多个函数。
这是函数式编程中的方法,为了方便,被放到了 Redux 里。
当需要把多个 store enhancers 依次执行的时候,需要用到它。
compose 方法的参数:
compose 方法的返回值:从右到左把接收到的函数合成后的最终函数。
React Redux 的 API
Provider 组件使需要访问 Redux 存储的任何嵌套组件都可以使用 Redux 存储器。
由于 React Redux 应用程序中的任何 React 组件都可以连接到商店,因此大多数应用程序将在顶层呈现 Provider 组件,整个应用程序的组件树都在其中。
connect 仍然有效,并且在 React Redux 8.x 中受支持。在 React 推行 Hooks 之后,Redux 官方推荐使用 React-Redux hooks API (useSelector 和 useDispatch)作为默认方法来代替 Connect() 方法。
connect() 函数将React组件连接到 Redux 存储。
它为其连接的组件提供了它需要的来自存储的数据片段,以及它可以用来将操作分派到存储的函数。
它不会修改传递给它的组件类;相反,它返回一个新的、连接的组件类,该类包装了您传入的组件。
mapStateToProps和mapDispatchToProps分别处理Redux存储的状态和调度。state和dispatch将作为第一个参数提供给mapStateToProps或mapDispatchToProps函数。
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
mapStateToProps和mapDispatchToProps的返回在内部分别称为stateProps和dispatchProps。如果已定义,它们将作为第一个和第二个参数提供给mergeProps,其中第三个参数将是ownProps。组合结果通常称为mergedProps,然后将提供给连接的组件。
connect 方法接受 4 个可选的参数:
connect 方法返回值:是一个包装函数,它接受您的组件,并返回一个包装组件及其注入的附加属性。
如果您使用的是 React 18,则不需要使用该 API。React 18 自动批处理所有状态更新,无论它们在何处排队。
React 的 unstable_batchedUpdates() API 允许将事件循环标记中的任何 React 更新批处理到单个呈现过程中。React 已经在内部将其用于自己的事件处理程序回调。这个 API 实际上是 ReactDOM 和 React Native 等渲染器包的一部分,而不是 React 核心本身。
由于 React Redux 需要同时在 ReactDOM 和 React Native 环境中工作,因此我们已经在构建时从正确的渲染器导入了这个 API,以供自己使用。我们现在也公开重新导出这个函数,重命名为 batch() 方法。可以使用它来确保在 React 之外调度的多个操作只会导致单个渲染更新。
允许您使用选择器函数从Redux存储状态提取数据。
选择器函数应该是纯函数,因为它可能在任意时间点多次执行。
例如:
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector((state) => state.counter)
return <div>{counter}</div>
}
默认情况下,useSelector() 将在调度操作后运行选择器函数时对所选值进行引用相等比较,并且仅在所选值发生更改时才会导致组件重新呈现。但是,与connect() 不同,useSelector() 不会阻止组件因其父级重新渲染而重新渲染,即使组件的属性没有更改。如果需要进一步的性能优化,可以考虑将函数组件包装在React.memo() 中:
const CounterComponent = ({ name }) => {
const counter = useSelector((state) => state.counter)
return (
<div>
{name}: {counter}
</div>
)
}
export const MemoizedCounterComponent = React.memo(CounterComponent)
这个钩子从Redux存储区返回对调度函数的引用。您可以根据需要使用它来分派操作。
例如:
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
当使用dispatch将回调传递给子组件时,有时可能需要使用useCallback将其记忆。如果子组件尝试使用React优化渲染行为。memo() 或类似的方法,这样可以避免由于回调引用的更改而不必要地呈现子组件。
import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
const incrementCounter = useCallback(
() => dispatch({ type: 'increment-counter' }),
[dispatch]
)
return (
<div>
<span>{value}</span>
<MyIncrementButton onIncrement={incrementCounter} />
</div>
)
}
export const MyIncrementButton = React.memo(({ onIncrement }) => (
<button onClick={onIncrement}>Increment counter</button>
))
只要将同一存储实例传递给 Provider 组件,调度函数引用就会稳定。通常,该存储实例在应用程序中从不更改。
然而,React 钩子 lint 规则不知道分派应该是稳定的,并且会警告分派变量应该添加到 useEffect 和 useCallback 的依赖数组中。最简单的解决方案就是这样做:
export const Todos = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchTodos())
// 安全地将调度添加到依赖项数组
}, [dispatch])
}
这个钩子返回对传递给 Provider 组件的同一个 Redux 存储的引用。
这个钩子可能不应该经常使用。首选使用 Selector() 作为主要选择。然而,这对于需要访问商店的不太常见的场景可能有用,例如更换减速器。
例如:
import React from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
// 如果存储状态更改,组件将不会自动更新(仅供参考!不要在真正的应用程序中这样做)。
return <div>{store.getState()}</div>
}
Redux Toolkit 的 API
Redux Toolkit 目前有 7 个 API:
Redux 使用 “单向数据流”:数据驱动视图的更新,用户在视图上操作,触发actions,通过action的方法更改state里的数据。形成一个数据单向流动的闭环。
具体来说,对于 Redux,我们可以将这些步骤分解为更详细的内容:
dispatch({type: 'counter/increment'})
。action -> store.dispatch(action) -> reducer(state, action) -> store.getState()
。
动态展示图:
静态展示图(此图包含 redux 中间件,下文会说):
为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?
- Reducer:纯函数,只承担计算 State 的功能,不适合承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
- View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
- Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
想来想去,只有发送 Action 的这个步骤,即 store.dispatch() 方法,可以添加其他功能。
Redux 中间件(Redux Middleware)就是一个函数。其本质是对 store.dispatch 方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
Redux 中间件用于:在 dispatch 一个 action 之后,且在执行 reducer 之前,进行一些额外的操作。比如:日志记录、创建崩溃报告、调用异步接口或者路由等功能。
redux 的中间件函数一般都接收 2 个参数:dispatch 和 getState。
redux 的中间件函数一般都返回一个函数,该函数以 dispatch 为参数,该函数的返回值是接收 action 为参数的函数(可以看做另一个 dispatch 函数)。
在中间件链中,以 dispatch 为参数的函数的返回值将作为下一个中间件(准确的说应该是返回值)的参数,下一个中间件将它的返回值接着往下一个中间件传递,最终实现了 store.dispatch 在中间件间的传递。
redux 的中间件只关注 dispatch 函数的传递,至于在传递的过程中干了什么中间件并不关心。
Redux 只处理同步数据流,异步数据流交给其 “中间件” 处理。
Redux 异步数据管理
Redux 官方之常见的问题汇总
强推:React 中使用 Redux 的最佳实现(来自 Redux 官方)
react-redux 提供了两个 API:Provider 和 Connect。
使用 Provider 和 Connect 将 Redux 绑定到 React 实例上。
Provider 组件用来接受 Store,并且让 Store 对子组件可用。
例如:
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import App from './app';
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
这时候 Provider 组件的子组件 App 才可以使用 connect 方法关联 store。
【拓展】Provider 组件的现原理:
Provider 利用了 React 的 Context 特性,Context 用来传递一些父容器的属性对所有子孙组件可见,在某些场景下面避免了用 props 传递多层组件的繁琐。
Connect() 方法用来连接 React 组件与 Redux store,不会改变原来的组件类。
Connect() 方法可以接收 4 个可选的参数,这里简要概述一下,具体详见官网:
Connect() 方法会返回另一个函数,这个返回的函数用来接受一个组件类作为参数,最后它会返回一个和 Redux store 关联起来的新组件。
例如:
class App extends Component { ... }
export default connect()(App);
这样就可以在 App 这个组件里面通过 props 拿到 Store 的 dispatch 方法。
React Redux 的 API
我们建议在 React 组件中使用 React Redux hooks API 作为默认方法。
现有的 connect API 仍然有效,并将继续受到支持,但hooks API更简单,与TypeScript一起使用效果更好。
与 connect() 一样,您应该首先将整个应用程序包装在Provider组件中,以使存储区在整个组件树中可用。
例如:
const store = createStore(rootReducer)
// As of React 18
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)
从这里,您可以导入列出的任何React-Redux挂钩API,并在函数组件中使用它们。
Provider 组件允许您通过上下文属性指定备用上下文(动态的上下文)。如果您正在构建一个复杂的可重用组件,并且您不希望您的存储与消费者的应用程序可能使用的任何 Redux 存储相冲突,那么这是非常有用的。
要通过 hooks API 访问备用上下文,请使用 hook creator 函数:
import React from 'react'
import {
Provider,
createStoreHook,
createDispatchHook,
createSelectorHook,
} from 'react-redux'
const MyContext = React.createContext(null)
// 如果希望在其他文件中使用自定义挂钩,请导出它们。
export const useStore = createStoreHook(MyContext)
export const useDispatch = createDispatchHook(MyContext)
export const useSelector = createSelectorHook(MyContext)
const myStore = createStore(rootReducer)
export function MyProvider({ children }) {
return (
<Provider context={MyContext} store={myStore}>
{children}
</Provider>
)
}
默认情况下,即使组件的属性没有更改,组件也会因其父级重新渲染而重新渲染。如何阻止这一情况的发生呢?
可以考虑将函数组件包装在 React.memo() 中,从而提升组件渲染的性能。
例如:
const CounterComponent = ({ name }) => {
const counter = useSelector((state) => state.counter)
return (
<div>
{name}: {counter}
</div>
)
}
export const MemoizedCounterComponent = React.memo(CounterComponent)
【推荐阅读】
阮一峰老师的三篇杰作:
Redux 入门教程(一):基本用法
Redux 入门教程(二):中间件与异步操作
Redux 入门教程(三):React-Redux 的用法
【参考文章】
redux 中文官网
React Redux 中文文档