Redux中文官网:www.redux.org.cn/
一. 什么是Redux
用于JavaScript应用的可预测状态容器。 可以帮助你开发出行为稳定可预测的、运行于不同的环境(客户端、服务器、原生应用)、易于测试的应用程序。
状态(State)
state直译过来就是状态,如果使用过React,对于state应该非常熟悉。state不过就是一个变量,一个用来记录(组件)状态的变量。组件可以根据不同的状态值切换为不同的显示,比如,用户登录和没登录看到页面应该是不同的,那么用户的登录与否就应该是一个状态。再比如,数据加载与否,显示的界面也应该不同,那么数据本身就是一个状态。换句话说,状态控制了页面的如何显示。
但是需要注意的是,状态并不是React中或其他类似框架中独有的。所有的编程语言,都有状态,所有的编程语言都会根据不同的状态去执行不同的逻辑,这是一定的。所以状态是什么,状态就是一个变量,用以记录程序执行的情况。
www.redux.org.cn/tutorials/f...
容器(Container)
容器当然是用来装东西的,状态容器即用来存储状态的容器。状态多了,自然需要一个东西来存储,但是容器的功能却不是仅仅能存储状态,它实则是一个状态的管理器,除了存储状态外,它还可以用来对state进行查询、修改等所有操作。(编程语言中容器几乎都是这个意思,其作用无非就是对某个东西进行增删改查)
可预测(Predictable)
可预测指我们在对state进行各种操作时,其结果是一定的。即以相同的顺序对state执行相同的操作会得到相同的结果。简单来说,Redux中对状态所有的操作都封装到了容器内部,外部只能通过调用容器提供的方法来操作state,而不能直接修改state。这就意味着外部对state的操作都被容器所限制,对state的操作都在容器的掌控之中,也就是可预测。
总的来说,Redux是一个稳定、安全的状态管理器。
二. 为什么是Redux
问:不对呀?React中不是已经有state了吗?为什么还整出一个Redux来作为状态管理器呢?
答:state应付简单的值还可以,如果值比较复杂的话并不是很方便。
问:复杂的值可以用useReducer嘛!
答:的确可以啊!但无论是state还是useRuducer,state在传递起来还是不方便,至上至下一层层的传递并不方便。
问:那不还是有context吗?
答:的确使用context可以解决state的传递问题,但依然是简单的数据尚可,如果数据结构过于复杂会使得context变得异常的庞大,不方便维护。
Redux可以理解为是reducer和context的结合体,使用Redux即可管理复杂的state,又可以在不同的组件间方便的共享传递state。当然,Redux主要使用场景依然是大型应用,大型应用中状态比较复杂,如果只是使用reducer和context,开发起来并不是那么的便利,此时一个有一个功能强大的状态管理器就变得尤为的重要。
三. 图解Redux
我们可以使用一张 gif 图来图解 Redux 应用中的数据流。
- actions 会在用户交互如点击时被 dispatch
- store 通过执行 reducer 方法计算出一个新的 state
- UI 读取最新的 state 来展示最新的值
四. 编写Redux代码
Redux 核心库
npm install redux
Redux Toolkit
bash
npm install @reduxjs/toolkit
Redux Toolkit (RTK) 是我们Redux官方推荐的编写 Redux 逻辑的标准方式。
RTK 包含了有助于简化许多常见场景的工具,包括 配置 Store, 创建 reducer 并编写 immutable 更新逻辑, 甚至还包含 一次性创建整个 State 的 "Slice"。
1.设置 Store
使用 configureStore
Redux Toolkit 的 configureStore
API,可简化 store 的设置过程 。configureStore
包裹了 Redux 核心 createStore
API,并自动为我们处理大部分 store 设置逻辑。事实上,我们可以有效地将其缩减为一步:
typescript
import { configureStore } from '@reduxjs/toolkit';
import { formReducer } from './formSlice';
import { errorReducer } from './errorSlice';
const store = configureStore({
reducer: {
formInfo: formReducer,
errorInfo: errorReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
configureStore
为我们完成了所有工作:
- 将
formReducer
和errorReducer
组合到根 reducer 函数中,它将处理看起来像{form, error}
的根 state - 使用根 reducer 创建了 Redux store
- 自动添加 middleware redux-toolkit.js.org/rtk-query/o...
- 自动设置 Redux DevTools 扩展连接
使用 createSlice
createSlice
接收一个包含三个主要选项字段的对象:
name
:一个字符串,将用作生成的 action types 的前缀initialState
:reducer 的初始 statereducers
:一个对象,其中键是字符串,值是处理特定 actions 的 "case reducer" 函数 让我们先看一个独立的小示例。
javascript
import { createSlice } from '@reduxjs/toolkit';
const formSlice = createSlice({
name: 'userInfo',
initialState: {
userName: '',
age: null,
email: ''
},
reducers: {
setName(state, action) {
console.log(action);
state.userName = action.payload;
},
setAge(state, action) {
state.age = action.payload;
},
setEmail(state, action) {
state.email = action.payload;
}
}
});
export const { setName, setAge, setEmail } = formSlice.actions;
export const { reducer: formReducer } = formSlice;
- 我们在
reducers
对象中编写 case reducer 函数,并赋予它们高可读性的名称 createSlice
会自动生成对应于每个 case reducer 函数的 action creators- createSlice 在默认情况下自动返回现有 state
createSlice
允许我们安全地"改变"(mutate)state!
生成的 action creators 我们可以单独解构和导出它们。
完整的 reducer 函数可以作为slice.reducer
使用,和之前一样,我们通常会export default slice.reducer
。 那么这些自动生成的 action 对象是什么样的呢?让我们调用并打印其中一个 action 来看下:
lua
console.log(action);
// {type: 'userInfo/setName', payload: 'xiaoming'}
createSlice
通过将 slice 的name
字段与 reducer 函数的 setName
名称相结合,为我们生成了 action type 字符串。默认情况下,action creator 接收一个参数,并将其作为 "action.payload" 放入 action 对象中。
在生成的 reducer 函数内部,createSlice
将检查 dispatch action 的 action.type
是否与它生成的名称之一相匹配。如果匹配成功,它将运行那个 case reducer 函数。
2.使用 Provider
透传 Store
现在组件可以从 store 中读取 state,并 dispatch action 到 store。但是仍少了点什么。比如 React-Redux hook 在哪里以及如何找到正确的 Redux store?hook 仅仅是一个 JS 函数,它并不能从 store.js 中自动导入 store。
我们必须明地确告诉 React-Redux 当前组件需要的 store。为此,我们使用 <Provider>
组件包裹 <App>
组件,并将 Redux store 作为 prop 传递给 <Provider>
组件。之后,应用程序中的每个组件都可以在需要时能够访问到 Redux store。
javascript
import { Provider } from 'react-redux';
import store from './examples/store/store';
import ErrorMessage from '@/app/examples/errorMessage/page';
export default function RootLayout({
children
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<head>
<link rel="stylesheet" href="https:****" />
</head>
<body>
<Provider store={store}>
<ErrorMessage />
{children}
</Provider>
</body>
</html>
);
}
3.使用 useSelector
从 Store 中读取 State
你应该熟悉类似 useState
的 React hook,在 React 函数组件中可以通过调用它们来访问 React state 值。
和许多其他库一样,React-Redux 也有它的自定义 hook,你可以直接在组件中使用它们。React-Redux hook 使 React 组件能够通过读取 state 以及 dispatch action 来和 Redux store 进行交互。 我们将看到的第一个 React-Redux hook 是 useSelector
,它使得 React 组件可以从 Redux store 中读取数据。
useSelector
接收一个 selector 函数。selector 函数接收 Redux store 的 state 作为其参数,然后从 state 中取值并返回
javascript
'use client';
import { useSelector } from 'react-redux';
import { RootState } from '../store/store';
export default function UserInfo() {
const formInfo = useSelector((state: RootState) => state.formInfo);
return (
<div className="hook-form">
<div className="list-form-lg">
<div className="field" role="group" aria-labelledby="legend5">
<label className="field-label" id="legend5">
UserName
</label>
<div className="field-control">{formInfo.userName}</div>
</div>
<div className="field" role="group" aria-labelledby="legend5">
<label className="field-label" id="legend5">
Age
</label>
<div className="field-control">{formInfo.age}</div>
</div>
<div className="field" role="group" aria-labelledby="legend5">
<label className="field-label" id="legend5">
Email
</label>
<div className="field-control">{formInfo.email}</div>
</div>
</div>
</div>
);
}
<UserInfo>
组件第一次渲染时,useSelector
hook 会调用 (state: RootState) => state.formInfo
并传入 全部的 Redux state 对象。无论 selectTodos 返回什么,useSelector 都会把它返回给组件。因此,组件中的 const formInfo
最终会和 Redux store state 中的 state.formInfo
数组保持一致。
假设我们 dispatch {type: 'userInfo/setName'}
这个 action,将会发生什么?Redux state 会被 reducer 更新,但是组件没有感知到变化并用新值重新渲染。
虽然 可以 调用 store.subscribe()
来监听每个组件中 store 的变化,但是这样会变得重复且难以控制。
- 灵活性 :
store.subscribe()
可以用于任何环境,不仅限于 React 应用。
- 性能开销 :
store.subscribe()
会在每次 state 更新时触发回调,这可能导致不必要的性能开销,尤其是在大型应用中。
- 复杂性:手动管理订阅和取消订阅可能会增加代码的复杂性。
幸运的是,**useSelector
会自动订阅 Redux store!**这样,任何时候 dispatch action,它都会立即再次调用对应的 selector 函数。如果 selector 返回的值与上次运行时相比发生了变化,useSelector
将强制组件使用新值重新渲染 。我们仅需要在组件中调用一次 useSelector()
即可。
4.使用 useDispatch
来 Dispatch Action
组件怎么向 store dispatch action 呢? React-Redux 的 useDispatch
hook 函数会返回 store 的 dispatch
方法。(事实上这个 hook 的内部实现真的是 return store.dispatch
。)
因此,我们可以在任何需要 dispatch action 的组件中使用 const dispatch = useDispatch()
,然后根据需要调用 dispatch(someAction)
。