Redux
首先一张图看看Redux基本内容:
react-redux
还是一张图看看如何在React中使用Redux:
combineReducers
combineReducers 是 Redux 提供的一个函数,用于将多个 reducer 合并成一个单一的 reducer。它的作用是将多个处理不同 state 切片的 reducer 函数合并成一个根 reducer,从而让 Redux store 能够管理更复杂的 state 结构。
比如我们存在多个reduxer:
// userReducer.js
const initialUserState = {
name: '',
age: 0,
};
const userReducer = (state = initialUserState, action) => {
switch (action.type) {
case 'SET_USER_NAME':
return { ...state, name: action.payload };
case 'SET_USER_AGE':
return { ...state, age: action.payload };
default:
return state;
}
};
export default userReducer;
// postsReducer.js
const initialPostsState = {
posts: [],
};
const postsReducer = (state = initialPostsState, action) => {
switch (action.type) {
case 'ADD_POST':
return { ...state, posts: [...state.posts, action.payload] };
case 'REMOVE_POST':
return { ...state, posts: state.posts.filter(post => post.id !== action.payload) };
default:
return state;
}
};
export default postsReducer;
需要将多个reducer合并后发送:
// rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
import postsReducer from './postsReducer';
const rootReducer = combineReducers({
user: userReducer,
posts: postsReducer,
});
export default rootReducer;
最后创建store:
// store.js
import { createStore } from 'redux';
import rootReducer from './rootReducer';
const store = createStore(rootReducer);
export default store;
中间件
这就不是一张图可以解释的了。
什么是中间件
允许开发者在派发一个action到他最终被reducer处理之前,对action进行一些额外的处理。
主要是增强store.dispatch的能力。
如何自定义中间件
重写dispatch方法
类似于VUE中数组实现响应式的原理:
import { combineReducers,createStore } from 'redux';
import countReducer from "./reducers/count";
import userReducer from "./reducers/user";
const rootReducer = combineReducers({
count:countReducer,
user:userReducer
});
const store = createStore(rootReducer);
// 通过重写dispatch实现响应式
const originDispatch= store.dispatch; //缓存原始dispatch
store.dispatch = function(action){
// 执行中间操作
console.log('中间件获取action',action);
console.log('中间件获取state',store.getState());
originDispatch(action); //调用原始dispatch
};
export default store;
官方推荐方法
自定义 Redux 中间件通常涉及编写一个函数,该函数接收 store 对象的 dispatch 和 getState 方法,并返回一个函数,该函数再次接收 next 和 action,允许你在 action 到达 reducer 之前或之后执行一些逻辑。
const loggerStateMiddleware = store => next => action => {
console.log('自定义middleWare2-logger-state', store.getState());
return next(action);
};
export default loggerStateMiddleware;
const store = createStore(rootReducer,applyMiddleware(loggerStateMiddleware));
中间件的原理
首先从使用中间件的语法入手:
const store = createStore(
rootReducer,
applyMiddleware(middleware1, middleware2)
);
那么接下来看看createStore做了什么呢?简化版createStore如下:
function createStore(reducer, preloadedState, enhancer) {
/**createStore接收三个参数
* 第一个参数是 rootReducer,用于处理状态变化,
* 第二个参数是可选的初始状态(preloadedState),
* 第三个参数是中间件链enhancer。
*/
// 如果传入了 enhancer,则使用 enhancer 增强 createStore
if (typeof enhancer === 'function') {
return enhancer(createStore)(reducer, preloadedState);
}
// 内部状态
let currentState = preloadedState;
let currentReducer = reducer;
let listeners = [];
// 获取当前状态
function getState() {
return currentState;
}
// 触发 action,并更新状态
function dispatch(action) {
currentState = currentReducer(currentState, action);
// 通知所有监听器(订阅者)
listeners.forEach(listener => listener());
return action;
}
// 订阅状态变化
function subscribe(listener) {
listeners.push(listener);
// 返回一个取消订阅的函数
return function unsubscribe() {
listeners = listeners.filter(l => l !== listener);
};
}
// 初始化 store
dispatch({ type: '@@redux/INIT' });
// 返回 store 对象
return {
getState,
dispatch,
subscribe,
};
}
知道了createStore的基本处理,接下来看看applyMiddleware做了什么:
由下可以看出:
applyMiddleware内部也重写了dispatch
function applyMiddleware(...middlewares) {
// 返回一个高阶函数,接收 createStore 作为参数
return createStore => reducer => {
// 创建原始的 Redux store
const store = createStore(reducer);
// 初始化 dispatch 函数,指向原始的 store.dispatch
let dispatch = store.dispatch;
// middlewareAPI 包含 getState 和 dispatch 方法
const middlewareAPI = {
getState: store.getState,
dispatch: action => dispatch(action)
};
// 将每个中间件传入 middlewareAPI,得到包装后的中间件函数
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// compose 函数用于将多个函数从右到左组合起来
dispatch = compose(...chain)(store.dispatch);
// 返回一个增强版的 store,其中的 dispatch 已经被中间件包装
return {
...store,
dispatch
};
};
}
// compose 函数用于将多个函数组合成一个函数
function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
接下来一张图就能说清整体的流程了:
常用的中间件
redux-thunk
redux-thunk 是 Redux 官方推荐的中间件,用于处理简单的异步逻辑。Thunk 是一个返回函数的函数,可以在 action creator 中执行异步代码。
使用
-
安装 :npm install redux-thunk
-
注册中间件
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; const store = createStore(rootReducer, applyMiddleware(thunk)); -
使用
// actions.js export const fetchData = () => { return async (dispatch) => { dispatch({ type: 'FETCH_DATA_REQUEST' }); try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data }); } catch (error) { dispatch({ type: 'FETCH_DATA_FAILURE', error }); } }; }; -
触发异步操作
import { useDispatch } from 'react-redux'; const MyComponent = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchData()); }, [dispatch]); // 其他组件逻辑 };
原理
同步情况下,dispatch函数的参数是一个对象:{type:'',payload} 。在使用resux-thunk时,传入的是要执行的函数,函数接收dispatch和getState做为参数,拿到异步结果后,可以dispatch一个action对象。
他的原理就是判断每个经过它的action:如果是function类型,就调用这个function(并传入 dispatch 和 getState 及 extraArgument 为参数)
redux-promise-middleware
redux-promise-middleware 是 Redux 中间件的一种,旨在简化异步操作的处理,特别是那些基于 Promise 的异步操作。它通过将 Promise 的状态(如 pending、fulfilled、rejected)自动转换成相应的 Redux action,简化了异步数据流的管理。
安装
首先,安装 redux-promise-middleware:
npm install redux-promise-middleware
基本用法
以下是一个使用 redux-promise-middleware 的示例,包括如何配置 store、定义异步 action 以及处理相应的 reducer。
1. 配置 Redux Store
在你的 Redux store 配置中添加 redux-promise-middleware:
// store.js
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(promiseMiddleware)
);
export default store;
2. 定义异步 Action Creator
定义一个返回 Promise 的 action creator:
// actions.js
export const fetchData = () => ({
type: 'FETCH_DATA',
payload: fetch('https://api.example.com/data').then(response => response.json())
});
redux-promise-middleware 会自动处理这个 action,分别派发 FETCH_DATA_PENDING, FETCH_DATA_FULFILLED, 和 FETCH_DATA_REJECTED action。
3. 处理相应的 Reducer
定义一个 reducer 来处理这些 action:
// dataReducer.js
const initialState = {
items: [],
loading: false,
error: null,
};
const dataReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_DATA_PENDING':
return { ...state, loading: true, error: null };
case 'FETCH_DATA_FULFILLED':
return { ...state, loading: false, items: action.payload };
case 'FETCH_DATA_REJECTED':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
export default dataReducer;
4. 在组件中调用异步 Action
使用 connect 函数从 react-redux 库将 fetchData 连接到组件的 props 中,并在需要时调用它。例如,在组件挂载时调用 fetchData:
// MyComponent.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { fetchData } from './actions';
const MyComponent = ({ fetchData, data, loading, error }) => {
useEffect(() => {
fetchData();
}, [fetchData]);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<div>
<h1>Data</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
const mapStateToProps = (state) => ({
data: state.data.items,
loading: state.data.loading,
error: state.data.error,
});
const mapDispatchToProps = {
fetchData,
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
详细解析
- Action Creator:
fetchData返回一个对象,该对象具有type和payload属性。payload是一个 Promise,这个 Promise 会自动处理并派发相应的状态 action。 - Middleware:
redux-promise-middleware拦截所有 action,并检查它们的payload是否为 Promise。如果是,则在 Promise 的生命周期内自动派发PENDING,FULFILLED, 和REJECTEDaction。 - Reducer: 根据 action 的类型更新 state。
FETCH_DATA_PENDING表示请求开始,FETCH_DATA_FULFILLED表示请求成功并接收数据,FETCH_DATA_REJECTED表示请求失败并接收错误。
自定义 Action Type 前缀
redux-promise-middleware 默认使用 PENDING, FULFILLED, 和 REJECTED 作为后缀。你可以通过配置来自定义这些后缀:
import promiseMiddleware from 'redux-promise-middleware';
const customMiddleware = promiseMiddleware({
promiseTypeSuffixes: ['LOADING', 'SUCCESS', 'FAILURE'],
});
const store = createStore(
rootReducer,
applyMiddleware(customMiddleware)
);
这样,派发的 action 类型将变为 FETCH_DATA_LOADING, FETCH_DATA_SUCCESS, 和 FETCH_DATA_FAILURE。
总结
- 简化异步操作:
redux-promise-middleware通过处理基于 Promise 的异步操作,简化了 Redux 的异步逻辑。 - 自动派发状态 action: 根据 Promise 的状态(pending, fulfilled, rejected),自动派发相应的 Redux action。
- 灵活性: 提供了定制化的选项,可以根据项目需求调整 action 的前缀和后缀。
异步操作必须使用中间件吗
并不是必须的,比如我们要setTimeout之后更新state,大可以:
setTimeout(()=>{
dispatch({type:'add',payload})
},3000)
中间件有显著优势:
- 代码可读性和可维护性:将所有的代码都放到 action creators中,组件只负责交互和渲染
- 实现关注点分离:将组件逻辑和异步逻辑进行分离
- 处理复杂的异步流程:如
redux-saga和redux-observable提供了强大的工具来处理复杂的异步流程,例如并发请求、取消请求、以及处理多步骤的异步操作。这些中间件提供了比手动管理异步操作更高级的控制。