Redux对于React来讲,就相当于只听命于皇上的锦衣卫一样,朝中不论大事小事都在锦衣卫的监管之下,皇上想要了解一下大臣最近的具体情况,只需要问锦衣卫即可,比如某个大臣未来可能要谋反,在其计划实施之前就会被锦衣卫发现,之后汇报给皇上,这样在谋反行动还未开始时就被解决掉了。redux就扮演了这样一个角色,把所有模块中需要的状态和数据,通通收集起来统一管理,并且状态更新是可预测的、可追踪的,总而言之一切都在掌控之中。
PS:本文不讨论 Redux 的缺点
一、redux 的实现过程
首先我们先来认识三个单词
store 容器
reducer 减速器
dispatch 派发
Store
Store是一个JavaScript对象,用于管理应用程序的状态。Store将应用程序的状态存储在一个单一的对象中,并提供了一些方法来访问和更新该状态。Store 是 Redux 架构中的核心概念之一,它将Reducer、状态和动作对象联系在一起
Redux中的Store具有以下特点:
- 单一状态树: 所有应用程序状态都存储在一个单一的JavaScript对象中,这个对象被称为状态树。
- 只读状态: 不能直接修改状态,必须通过dispatch一个action来描述状态变化。
- 纯函数更新状态: 使用纯函数reducer处理动作,更新应用程序状态。
在Redux中,可以使用createStore函数来创建一个Store。createStore函数接收一个Reducer函数和一个可选的初始状态对象作为参数,并返回一个Store对象。可以使用Store对象提供的方法来访问和更新应用程序的状态。
import { createStore } from 'redux';
import rootReducer from './reducers';
const initialState = {
todos: [],
filter: 'SHOW_ALL'
};
const store = createStore(rootReducer, initialState);
export default store;
在上面的代码中,我们使用createStore函数创建了一个Redux Store,并将其导出。我们将应用程序的初始状态传递给createStore函数,以及一个由多个Reducer函数合并而成的rootReducer函数。这个Store对象可以用于访问和更新应用程序的状态。
Redux的Store对象提供了以下方法:
getState(): 获取当前的状态对象。dispatch(action): 分发一个动作对象,触发状态更新。subscribe(listener): 添加一个监听器函数,每当状态更新时被调用。replaceReducer(nextReducer): 用于动态替换Store中的Reducer函数。
使用Store对象的这些方法,我们可以访问和更新Redux应用程序的状态,并响应状态的变化。
Reducer
Reducer也是Redux架构中的核心概念之一,它负责处理所有的状态变化,而且必须是纯函数。每当动作被触发时,Redux会自动调用所有注册的Reducer函数,并将当前的状态和动作对象作为参数传递给它们。Reducer函数根据动作类型来更新状态,并返回新的状态对象。
let initial = {
count:10
};
const counterReducer = function (state = initial, action) {
state = {...state};
switch (action.type) {
case 'INCREMENT':
state.count++;
break;
case 'DECREMENT':
state.count--;
break
default:
};
return state;
}
export default counterReducer;
在上面的代码中,我们定义了一个counterReducer函数,它接收一个初始状态对象和一个动作对象作为参数。根据动作类型来更新状态,并返回新的状态对象。如果动作类型不匹配,Reducer就会返回当前的状态对象。
在Redux中,可以将多个Reducer函数合并成一个大的Reducer函数,用于管理整个应用程序的状态。合并Reducer函数的方法是使用Redux提供的combineReducers函数,该函数可以将多个Reducer函数合并成一个Reducer函数。
import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
import todosReducer from './todosReducer';
const rootReducer = combineReducers({
counter: counterReducer,
todos: todosReducer
});
export default rootReducer;
Dispatch
在Redux中,dispatch是一个用于触发状态变化的函数,它接收一个动作对象作为参数,并将该动作对象传递给注册的Reducer函数。Reducer函数会根据动作对象的类型来更新状态,并返回一个新的状态对象。这个新的状态对象会替换原有的状态对象,从而触发应用程序的重新渲染。
const ADD_TODO = 'ADD_TODO';
function increment() {
return {
type: ADD_TODO
};
}
store.dispatch(increment());
dispatch函数的参数是一个动作对象(Action Object)。动作对象是一个简单的JavaScript对象,它至少包含一个type属性,用于指定动作的类型,以便在Reducer函数中进行识别和处理。
动作对象可以包含其他属性,以便传递一些附加的数据,这些数据将被用于更新应用程序的状态。例如,以下是一个包含类型和数据属性的动作对象:
const ADD_TODO = 'ADD_TODO';
function addTodo(id, text) {
return {
type: ADD_TODO,
payload: {
id,
text
}
};
}
//需要分发时
store.dispatch(addTodo(1, 'Learn Redux'));
dispatch函数使得应用程序的状态变得可预测和可控,而且可以方便地进行调试和测试。
总结
Redux是一个JavaScript状态管理库,用于在应用程序中管理复杂的状态。Redux通过使用一个中央存储对象Store作为应用程序的状态容器,将所有的状态存储在一个单一的地方,使得状态的变化和管理变得更加容易和可预测。
Redux中的状态变化是通过调用纯函数的Reducer来处理的。Reducer是一个接受当前状态和动作对象作为参数的函数,并返回一个新的状态对象。Redux的Reducer被称为减速器(Reducer)是因为它们的主要作用是减慢状态变化的速度,确保状态变化的可预测性和一致性。
为了改变应用程序的状态,我们需要派发Dispatch一个动作(Action)对象到Redux的Store中。Dispatch是一个用于向Store发送动作的方法,它将动作对象作为参数,并将其发送到Redux的Store中。一旦Store接收到一个动作,它会调用相应的Reducer来更新状态,并通知所有已注册的状态监听器函数。
二、redux工程化
目录结构
actions文件夹中
//以 aAction.js 为例
const aAction = {
SUP() {
return {
type: 'TYPE_SUP'
};
},
OPP() {
return {
type: 'TYPE_OPP'
}
}
}
export default aAction;
// index.js
import aAction from "./aAction";
import bAction from "./bAction";
const action = {
aA: aAction,
bA: bAction
}
export default action;
reducers文件夹中
//以 aReducer.js 为例
const initial = {
supNum: 10,
oppNum: 5,
num: 0
};
export default function voteReducer(state = initial, action) {
state = { ...state };
switch (action.type) {
case 'TYPE_SUP':
state.supNum++;
break;
case 'TYPE_OPP':
state.oppNum++;
break;
default:
}
return state;
};
// index.js
import { combineReducers } from 'redux';
import aReducer from './aReducer';
import bReducer from './bReducer';
const reducer = combineReducers({
aR: aReducer,
bR: bReducer
});
export default reducer;
store文件夹中,把最高级 reducer 放到容器中
// index.js
import { createStore } from 'redux';
import reducer from './reducers';
const store = createStore(reducer);
export default store;
创建上下文对象
//在ThemeContext.js中
import React from "react";
const ThemeContext = React.createContext();
export default ThemeContext;
在入口文件中,把 store 挂载到上下文容器中
import ReactDOM from 'react-dom/client';
import App from './views/App';
import store from './store'
import ThemeContext from './ThemeContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeContext.Provider value={{ store }} >
<App />
</ThemeContext.Provider>
);
使用
在 views 文件夹中
//App.jsx中
import React, { useContext, useEffect, useState } from "react";
import A from './A';
import B from './B';
import ThemeContext from "../ThemeContext";
const App = function() {
const { store } = useContext(ThemeContext);
let { supNum, oppNum } = store.getState().aR;
let [num, setNum] = useState(0);
//手动把让组件更新的方法,放到事件池中
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setNum(Date.now())
})
return () => {
unsubscribe()
}
}, [num])
return <div>
<P>{supNum+oppNum}</P>
<A />
<B />
</div>;
};
export default App;
在A类组件中
import React from "react";
import ThemeContext from "../ThemeContext";
class A extends React.Component {
static contextType = ThemeContext;
render() {
const { store } = this.context;
let { supNum, oppNum } = store.getState().aR;
return <div>
<p>{supNum}</p>
<p>{oppNum}</p>
</div>;
}
}
export default A;
在B函数组件中
import action from '../store/actions'
const B = function () {
const { store } = useContext(ThemeContext);
return <div>
<button
onClick={() => {
store.dispatch(action.aA.SUP())
}}>
支持
</button>
<button
onClick={() => {
store.dispatch(action.aA.OPP())
}}>
反对
</button>
</div >;
};
export default B;
这样一个简单的redux工程化Demo就完成了
三、react-redux
React-Redux是Redux的官方React绑定库。它提供了一组React组件和API,使React应用程序可以使用Redux来管理其状态。它包含了一个提供了connect()函数的Provider组件和一个connect()函数,让组件可以连接到Redux存储并访问状态和操作。在React-Redux中,组件订阅Redux存储中特定状态的更改,以便它们可以在状态更改时自动重新渲染。这使得React组件可以使用Redux来管理其状态,而无需将状态作为本地组件状态存储在组件中。
简单点说就是使用起来更方便了
在之前的工程目录中,不需要我们自己去创建上下文对象了
在入口文件中
import ReactDOM from 'react-dom/client';
import App from './views/App';
/* REDUX */
import { Provider } from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<Vote />
</Provider>
);
在 Views 文件夹中
//App.jsx
import A from './A';
import B from './B';
import { connect } from 'react-redux'
const App = function (props) {
let { supNum, oppNum } = props
return <div>
<P>{supNum+oppNum}</P>
<A />
<B />
</div>;
};
export default connect(state => state.aR)(App);
//A.jsx
import React from "react";
import { connect } from 'react-redux'
class A extends React.Component {
render() {
let { supNum, oppNum } = this.props;
return <div >
<p>{supNum}人</p>
<p>{oppNum}人</p>
</div>;
}
}
export default connect(state => state.aR)(A);
//B.jsx
import React, { useContext } from "react";
import { connect } from 'react-redux'
import action from '../store/actions'
const B = function (props) {
let { SUP, OPP } = props
return <div>
<button
onClick={SUP}>
支持
</button>
<button
onClick={OPP}>
反对
</button>
</div >;
};
export default connect(
null,
action.aA
)(B);
重点
公式:connect (mapStateToProps, mapDispatchToProps) (渲染的组件)
connect函数有两个参数,第一个参数是一个函数,称为mapStateToProps,它接受整个store作为参数,返回一个对象,这个对象将作为props传递给被包装的组件。这个函数可以选择性地返回一个子集的store,只要该子集发生变化,被包装的组件就会重新渲染。
第二个参数是一个函数,称为mapDispatchToProps,它接受一个dispatch函数作为参数,返回一个对象,这个对象包含了一些函数,这些函数也将作为props传递给被包装的组件。这些函数用于派发action,以便更改store中的状态。
connect函数返回一个新函数,这个函数接受一个组件作为参数,返回一个已经连接到store的组件。这个新的组件会将mapStateToProps和mapDispatchToProps函数的返回值合并成一个props对象,传递给被包装的组件。
使用connect函数,可以将store中的状态和派发action的函数都映射到React组件的props中,从而实现React组件与store的连接。这使得React组件可以直接从store中获取数据,而不需要手动订阅(subscribe)store的变化。
四、redux 中间件
Redux中间件是一个可插入的代码层,可以在Redux应用程序中的“action”和“reducer”之间添加额外的逻辑。它们允许您将应用程序的行为分解为独立的功能块,每个块都可以独立地编写、测试和重用。
使用中间件之前需要先通过 applyMiddleware 进行注册
在store文件下
//index.js
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
import reduxLogger from 'redux-logger';
import reduxThunk from 'redux-thunk';
import reduxPromise from 'redux-promise';
const store = createStore(
reducer,
applyMiddleware(reduxLogger, reduxThunk, reduxPromise)
);
export default store;
redux-logger
使用 Logger 中间件时,每当 store 中的状态发生变化时,中间件都会将新的状态和相关的操作记录到控制台中。这可以让我们更好地调试和分析Redux应用程序。
//举个例子
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(logger)
);
store.dispatch({ type: 'INCREMENT' });
// Output: "dispatch { type: 'INCREMENT' }"
// Output: "next state { counter: 1 }"
store.dispatch({ type: 'DECREMENT' });
// Output: "dispatch { type: 'DECREMENT' }"
// Output: "next state { counter: 0 }"
redux-thunk 和 redux-promise
thunk中间件可以在“action creator”中返回一个函数,而不是一个纯对象。这个函数可以在后台执行任何异步操作,并且在完成后触发一个真正的“action”。这个函数被称为“thunk函数”,它接收两个参数:dispatch和getState。dispatch是store的dispatch函数,getState是一个函数,它返回当前的state。
Promise中间件可以在“action creator”中返回一个Promise对象,当一个“action”返回一个Promise对象时,Promise中间件会等待Promise完成后再将它的结果作为“action”的payload传递给reducer。如果Promise被拒绝,中间件将触发一个包含错误信息的新“action”。
在actions文件中
//aAction.js
// 延迟函数:返回promise实例,在指定的时间后,才会让实例为成功
const delay = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, interval);
});
};
const aAction = {
// redux-thunk中间件语法
SUP() {
return async (dispatch) => {
await delay();
dispatch({
type: TYPE_SUP
});
};
},
// redux-promise中间件语法
async OPP() {
await delay(2000);
return {
type: TYPE_OPP
};
}
};
export default aAction;
五、Redux Toolkit
Redux Toolkit是一个官方推荐的Redux工具包,它提供了一组简化Redux开发的工具和约定。使用Redux Toolkit,我们可以更快地编写Redux代码,并减少重复性的模板代码。
Redux Toolkit主要提供了三个功能:
configureStore
configureStore函数用于创建一个store。它接受一个对象作为参数,该对象包含以下属性:
- reducer:Redux store中的reducer。可以是一个单独的reducer函数,也可以是由多个reducer函数组成的对象。可以使用combineReducers函数将多个reducer函数组合成一个对象。
- middleware:Redux store中使用的中间件。可以是一个单独的中间件函数,也可以是一个中间件函数数组。可以使用applyMiddleware函数将多个中间件函数组合成一个中间件函数。
- devTools:一个布尔值或一个devTools配置对象,用于启用或禁用Redux DevTools扩展。如果传递了一个对象,则可以指定扩展的名称、配置和其他选项。
- preloadedState:Redux store的预加载状态。可以是任何类型的值,例如对象、数组或原始值。
- enhancers:Redux store的增强器。可以是单独的增强器函数,也可以是增强器函数数组。可以使用compose函数将多个增强器函数组合成一个增强器函数。
import { configureStore, combineReducers, applyMiddleware } from '@reduxjs/toolkit';
import thunkMiddleware from 'redux-thunk';
import loggerMiddleware from 'redux-logger';
import { todosReducer } from './todos';
import { userReducer } from './user';
const rootReducer = combineReducers({
todos: todosReducer,
user: userReducer,
});
const middleware = [thunkMiddleware, loggerMiddleware];
const store = configureStore({
reducer: rootReducer,
middleware: applyMiddleware(...middleware),
devTools: process.env.NODE_ENV !== 'production',
});
export default store;
在这个例子中,首先使用combineReducers函数将两个reducer函数组合成一个根reducer函数。然后,我们使用applyMiddleware函数将两个中间件函数组合成一个中间件函数数组。最后使用configureStore函数创建了一个Redux store,并将根reducer函数、中间件函数数组和开发工具配置作为参数传递。
createSlice
createSlice函数用于创建一个包含reducer和action creators的slice。它接受一个对象作为参数,该对象包含以下属性:
- name(必填):slice的名称。它将用于创建action types和action creators的前缀。
- initialState(必填):slice的初始状态。可以是任何类型的值,例如对象、数组或原始值。
- reducers:一个对象,包含用于更新slice状态的reducer函数。每个reducer函数都将接受slice的当前状态和一个action对象作为参数,并返回一个新的slice状态。注意,createSlice会自动创建action creators,这些action creators的名称与reducer函数的名称相同。
- extraReducers:一个对象,用于处理其他slice的action。与reducers不同,extraReducers将使用字符串类型的action type作为键,而不是使用action creator函数的名称。该对象的值是一个reducer函数,它接受slice的当前状态和一个action对象作为参数,并返回一个新的slice状态。
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state) => state + 1,
decrement: (state) => state - 1,
reset: () => 0,
},
});
export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;
在这个例子中,我们使用createSlice函数创建了一个名为counter的slice。slice的初始状态为0,reducers对象包含三个reducer函数:increment、decrement和reset。createSlice会自动创建与reducer函数名称相同的action creators。我们可以将这些action creators导出并在其他文件中使用。最后,我们将slice的reducer导出,以便在Redux store中使用。
createAsyncThunk
createAsyncThunk函数接受两个参数:typePrefix和一个async callback函数。
-
typePrefix是一个字符串,用于创建异步操作的相关action types。createAsyncThunk函数会自动生成三个action types,包括
typePrefix/pending、typePrefix/fulfilled、typePrefix/rejected。 -
async callback函数是一个异步的回调函数,它的返回值可以在fulfilled action中作为payload传递,或者在rejected action中作为error传递。在该函数内部,您可以使用
async/await或Promise来执行异步操作。在该函数内部,可以使用dispatch和getState来进行Redux store中的状态更新和查询。
import { createAsyncThunk } from '@reduxjs/toolkit';
import { fetchUserById } from '../api';
export const fetchUser = createAsyncThunk('users/fetchUserById', async (userId) => {
const response = await fetchUserById(userId);
return response.data;
});
在这个例子中,定义了一个名为fetchUser的异步action creator。createAsyncThunk函数将自动生成三个action types,包括users/fetchUserById/pending、users/fetchUserById/fulfilled、users/fetchUserById/rejected。async callback函数将使用传入的userId参数执行异步操作,并将响应数据作为payload返回。
最后总结
Redux是一个状态管理库,它的核心思想是将整个应用程序的状态存储在一个单一的store中,并使用纯函数reducer来更新状态。但是,使用Redux本身需要编写大量与业务逻辑无关代码,特别是在涉及到定义action types和action creators时。
为了简化Redux的使用,社区提供了一些库,如 react-redux 和 Redux Toolkit。
React-Redux是一个与React集成的Redux绑定库,它提供了一种简化Redux使用的方法。它提供了一个Provider组件,它将Redux store传递给整个React应用程序,并提供了一个connect函数,它允许组件连接到store并使用它的状态和action creator函数。
Redux Toolkit是一个由Redux官方维护的库,它旨在简化Redux的使用。它提供了一些常用的功能,如createSlice和createAsyncThunk函数,它们可以用来快速创建reducer和异步action creator函数。此外,它还提供了一个configureStore函数,它将自动配置多个Redux中间件,并使其易于使用。Redux Toolkit还提供了一些性能优化,如Immer库,它允许我们以可变方式更新状态,而不需要手动编写深层嵌套的不可变代码。
因此,从Redux到React-Redux再到Redux Toolkit可以帮助我们更轻松地管理应用程序状态,减少非业务逻辑代码的编写,提高应用程序的可维护性和性能。