Redux 是什么
redux 和 react-redux
-
redux
redux 是 JavaScript 应用的状态容器,是一个 JS 库,提供可预测的状态管理。和 Vuex 是一样的。
社区通常认为 Redux 是 Flux 的简化设计版本,但它吸收了 Elm 的架构思想,更像一个混合产物。 -
react-redux
React Redux 是 Redux 的官方 React UI 绑定库。使得 React 组件能够从 Redux store 中读取到数据,并且可以用dispatch
、actions
去更新 store 中的 state。
既然已经有了 Redux,为什么还要使用 React Redux 呢?
将 Redux 和任意的 UI 框架一起使用都需要相同一致的以下几个步骤:
- 创建一个 Redux Store;
- 订阅更新;
- 订阅回调内部:
i. 获取当前的 store state;
ii. 提取这部分 UI 需要的数据;
iii. 使用数据更新 UI。 - 如有必要,用初始的 state 去渲染 UI;
- 通过 dispatching Redux actions 去响应 UI 层的交互。
虽然可以手动编写上述逻辑,但这样做会变得非常重复。此外,优化 UI 性能需要很复杂的逻辑。
订阅 store,检查更新数据或触发重新渲染的过程可以变得更加通用和可重用。React Redux 作为 React 官方的 React UI 绑定,能帮助提升性能优化,并且 React Redux 拥有庞大的用户社区。
Flux
Flux 是一种使用单向数据流形式来组合 React 组件的应用架构。
- View:视图层,即代码中的 React 组件,负责显示用户界面。
- Store:数据层,维护数据和数据处理的逻辑。
- Dispatcher:接收 Actions、执行回调函数。是管理数据流动中的中央枢纽,每一个 Store 提供一个回调。
- Action:驱动 Dispatcher 的 JS 对象,通常用 type 标记。
Flux 我目前还没有接触过,有兴趣的伙伴们可以去看一下阮老师的Flux 架构入门教程。
Elm
Elm,一种语言,主要用于网页开发。
- 全局单一数据源,不像 Flux 一样有多个 Store;
- 纯函数,可以保证输入输出的恒定;
- 静态类型,可以确保运行安全。
Redux 三原则
-
单一数据源(Single Source of Truth)
整个应用的 state 被存储在一棵 object 树中,并且这棵 object 树只存在于唯一一个 Store 中。
-
纯函数 Reducer(Change are made with pure functions)
为了描述 Action 是如何改变状态树而编写的一个纯函数 Reducer。
Reducer 的函数签名是这样的(state, action) => newState
。 -
State 只读(State is read-only)
唯一可以改变 state 的方法就是触发 Action,Action 是一个用于描述已发生事件的普通对象。
Redux 的 Store 状态设计的一个主要原则:避免冗余的数据。
Redux 原理
redux 要求我们把数据都放在 Store 公共存储空间。
一个组件改变了 Store 里的数据内容,其他组件就能感知到 Store 的变化,再来取数据,从而间接的实现了这些数据传递的功能。
react-redux 的用法
-
使用
configureStore
创建 Redux Store,并自动配置 Redux DevTools 扩展,以便在开发时检查 store。// store.js import { configureStore } from '@reduxjs/toolkit'; export default configureStore({ reducer: {}, });
Redux Toolkit 是开箱即用的一个高效 Redux 开发工具集。
它包括几个实用程序功能,可以简化最常见场景下的 Redux 开发,包括配置 store、定义 reducer,不可变的更新逻辑、可以立即创建整个状态的“切片 slice”,而无需手动编写任何 action creator 或 action type。它还自带了一些最常用的 Redux 插件,例如用于异步逻辑 Redux Thunk,用于编写选择器 selector 的函数 Reselect 等。 -
为 React 提供 Redux Store。
创建 store 后,在应用程序外层包裹一个 React Redux<Provider>
组件,使其对 React 组件可用。
将 store 作为参数传递。// main .js import React from 'react'; import { Provider } from 'react-redux'; import ReactDOM from 'react-dom/client'; import App from './App.jsx'; import store from './store'; ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
-
使用
createSlice
创建一个 Redux slice reducer。
创建 slice 需要一个字符串名称来标识 slice。一个初始 state 值,以及一个或多个 reducer 函数来定义如何更新 state 。
创建 slice 后,可以导出生成的 Redux action creators 和整个 slice reducer 函数。// counterSlice.js import { createSlice } from '@reduxjs/toolkit'; export const counterSlice = createSlice({ name: 'counter', initialState: { num: 0, name: '', age: 0, }, reducers: { increment: (state, props) => { // Redux Toolkit 允许在 reducers 中编写 mutating 逻辑。 // 它实际上并没有 mutate state,因为它使用了 Immer(https://immerjs.github.io/immer/) 库 // 它检测到草稿 state 的变化并产生一个全新的基于这些更改的不可变 state state.num += 1; state.name = props.payload.name; }, decrement: (state, props) => { state.num -= 1; state.age = props.payload.age; }, }, }); export const { increment, decrement } = counterSlice.actions; export default counterSlice.reducer;
-
添加 Slice Reducers 到 Store。
// store.js import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './counterSlice'; export default configureStore({ reducer: { counter: counterReducer, }, });
-
在 React 函数组价中使用 React Redux
useSelector/useDispatch
hooks。-
useSelector
hook 从 store 读取数据。 -
useDispatch
hook 获取 dispatch 函数,并根据需要 dispatch actions。
类组件中可以使用// ChildB.jsx import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement } from '../utils/counterSlice'; const ChildB = () => { const state = useSelector((state) => state.counter); const { name, age, num } = state; const dispatch = useDispatch(); return ( <> ChildB 中 counter:{num} <br /> 名字:{name} <br /> 年龄:{age} <br /> <button onClick={() => dispatch(increment({ name: 'zhangsan' }))}> increment </button> <button onClick={() => dispatch(decrement({ age: 20 }))}> decrement </button> </> ); }; export default ChildB;
connect
去读取 Redux Store。
connect
接收两个参数:-
mapStateToProps
:在每一次 store state 改变时被调用,它接收整个 store state,并返回该组件需要的数据对象。 -
mapDispatchToProps
:此参数可以是一个 function,或一个 object。- function:在 component 创建时立马被调用,它接收
dispatch
作为一个参数,并返回一个 object,其中包含使用dispatch
来 dispatch actions 的函数。 - object(充满 action creators):每个 action creator 都会变成一个 prop 函数,在调用时会自动 dispatches 其 action。
- function:在 component 创建时立马被调用,它接收
// ChildA.jsx - demo1 import React from 'react'; import { connect } from 'react-redux'; import { increment, decrement } from '../utils/counterSlice'; class ChildA extends React.Component { render() { const { num, name, age, dispatch } = this.props; return ( <> ChildA 中 counter:{num} <br /> 名字:{name} <br /> 年龄:{age} <br /> <button onClick={() => dispatch(increment({ name: 'lisa' }))}> increment </button> <button onClick={() => dispatch(decrement({ age: 18 }))}> decrement </button> </> ); } } export default connect((state) => state.counter)(ChildA); // ChildA.jsx - demo2 import React from 'react'; import { connect } from 'react-redux'; import { increment, decrement } from '../utils/counterSlice'; class ChildA extends React.Component { render() { const { num, name, age, dispatch } = this.props; return ( <> ChildA 中 counter:{num} <br /> 名字:{name} <br /> 年龄:{age} <br /> decrement </button> <button onClick={() => dispatch({type: 'counter/increment', payload: {name: 'lisa'}})}> increment </button> <button onClick={() => dispatch({type: 'counter/decrement', payload: {age: 18}})}> decrement </button> </> ); } } export default connect((state) => state.counter)(ChildA); // ChildA.jsx - demo3 import React from 'react'; import { connect } from 'react-redux'; class ChildA extends React.Component { render() { const { num, name, age, incre, decre } = this.props; return ( <> ChildA 中 counter:{num} <br /> 名字:{name} <br /> 年龄:{age} <br /> <button onClick={() => incre({ name: 'lisa' })}>increment</button> <button onClick={() => decre({ age: 18 })}>decrement</button> </> ); } } export default connect((state) => state.counter, { incre: increment, decre: decrement, })(ChildA);
-
Redux 中间件
中间件就是在源数据和目标数据之间做处理,有利于程序的可扩展性。
在 Redux 中,中间件的作用在于调用 dispatch
触发 reducer
之前做一些其他操作,也就是说它改变的是执行 dispatch
到触发 reducer
的流程。
有的中间件有次序要求,比如 logger 必须放在最后。
- 如果使用基础 Redux 中,中间件都需要
applyMiddleware
进行注册,作用是将所有的中间件组成一个数组,依次执行,然后作为第二个参数传入createStore
中。import { creatStore, applyMiddleware } from 'redux'; import reducer from './reducer'; const store = creatStore(reducer, applyMiddleware(...middlewares));
- 如果使用 Redux Toolkit,
configureStore
API 接收的对象参数中有一个middleware
参数可以添加中间件。// store.js import { configureStore } from '@reduxjs/toolkit'; export default configureStore({ reducer: {}, middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), ...middlewares] });
createStore
用于创建 Redux Store,它接收三个参数:
- reducer:一个纯函数,用于根据先前的状态和给定的 Action 来计算新的状态。
- preloadedState:可选参数,用于初始化状态。通常在服务端渲染或者从本地存储加载状态时使用。
- enhancer:可选参数,一个函数,用于增强 store 的功能,例如使用中间件。
function createStore(reducer, preloadedState, enhancer) {
// 初始化 currentState,用于存储状态
let currentState = preloadedState;
// 初始化 currentReducer,用于存储当前的 reducer 函数
let currentReducer = reducer;
// 初始化 listeners,用于存储订阅状态变化的回调函数
let listeners = [];
// getState 函数,用于获取当前的状态
function getState() {
return currentState;
}
// dispatch 函数,用于派发 action 到 reducer,更新状态,并触发订阅状态变化的回调函数
function dispatch(action) {
currentState = currentReducer(currentState, action);
listeners.forEach(listener => listener());
return action;
}
// subscribe 函数,用于订阅状态的变化,每当状态发生变化时触发注册的回调函数
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
listeners = listeners.filter(l => l !== listener);
};
}
// 如果有 enhancer 函数,则使用 enhancer 来增强 createStore
if (typeof enhancer === 'function') {
return enhancer(createStore)(reducer, preloadedState);
}
// 初始化 currentState,使用一个初始的 "INIT" action 来获取初始状态
dispatch({ type: '@@redux/INIT' });
// 返回一个具有 getState、dispatch 和 subscribe 方法的对象,即 Redux store
return {
getState,
dispatch,
subscribe
};
}
applyMiddleware
applyMiddleware
是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。
function applyMiddleware(...middlewares) {
// 返回一个enhancer函数,接收createStore作为参数
return function(createStore) {
// 返回一个新的createStore函数,接收reducer和preloadedState作为参数
return function(reducer, preloadedState) {
// 调用原始的 createStore 函数创建 store
const store = createStore(reducer, preloadedState);
let dispatch = store.dispatch;
let chain = [];
// 定义 middleware API
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
// 调用每个 middleware,传入 middlewareAPI
chain = middlewares.map(middleware => middleware(middlewareAPI));
// 组合所有中间件,得到一个 dispatch 函数
dispatch = compose(...chain)(store.dispatch);
// 返回一个增强版的 store,其中 dispatch 已经被包装
return {
...store,
dispatch
};
};
};
}
compose
compose 函数实现了将多个函数组合在一起的功能,从右到左,依次实现。
function compose(...funcs) {
// 如果没有传入任何函数,则直接返回一个接收参数并返回参数的函数
if (funcs.length === 0) {
return arg => arg;
}
// 如果只传入一个函数,则直接返回该函数
if (funcs.length === 1) {
return funcs[0];
}
// 返回一个组合了所有函数的新函数
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
configureStore
configureStore
API 内部自动调用了 applyMiddleware
、compose
、createStore
函数。
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk'; // 引入 Redux Thunk 中间件
import rootReducer from './reducers'; // 引入根 reducer
/**
* configureStore 函数用于创建 Redux store
* @param {Object} preloadedState 初始状态对象,可选
* @returns {Object} Redux store 对象
*/
function configureStore(preloadedState) {
// 定义中间件数组
const middlewares = [thunkMiddleware]; // 引入 Redux Thunk 中间件
// Redux DevTools Extension
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// 创建 store
const store = createStore(
rootReducer, // 根 reducer
preloadedState, // 初始状态
composeEnhancers(
applyMiddleware(...middlewares) // 应用中间件
)
);
return store;
}
export default configureStore;
redux-thunk
处理异步 Action。
- 如果使用的是 Redux Toolkit,
configureStore
API 已经默认添加了 thunk 中间件。 - 如果使用的基础 Redux 的
(已经被废弃),则需要安装。createStore
$ npm install redux-thunk $ yarn add redux-thunk
// index.jsx import { creatStore, applyMiddleware } from 'redux'; import { thunk } from 'redux-thunk'; import reducer from './reducer'; const store = creatStore(reducer, applyMiddleware(thunk));
redux-logger
实现日志功能。
$ npm install redux-logger
$ yarn add redux-logger
- 如果使用的是 Redux Toolkit:
// store.js import { configureStore } from '@reduxjs/toolkit'; import logger from 'redux-logger'; import counterReducer from './counterSlice'; export default configureStore({ reducer: { counter: counterReducer, }, middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), logger] });
- 如果使用的是基础 Redux
// index.jsx import { creatStore, applyMiddleware } from 'redux'; import logger from 'redux-logger'; import reducer from './reducer'; const store = creatStore(reducer, applyMiddleware(logger));
参考:Redux 官方文档
参考:React Redux 官方文档
参考:Redux 工具包 官方文档
如有问题,欢迎指正~