Redux 中间件提供了一个第三方扩展点,让你可以在 dispatch action 到达 reducer 之前,对这个 action 进行额外的操作。这些操作可能包括日志记录、创建延迟 action、处理异步逻辑,或者在某些情况下,条件性地阻止 action 被 dispatch。
中间件的功能通过 Redux 的 applyMiddleware
函数来启用,该函数可以在创建 store 的时候使用。。
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import {Provider} from 'react-redux';
import Counter from './components/Counter';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<Counter />
</Provider>
);
src\components\Counter.js
import React from 'react';
import {connect} from 'react-redux';
import actions from '../store/actions/counter';
function Counter(props) {
const {number,add,minus} = props;
return (
<div>
<p>{number}</p>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>
);
}
export default connect(
state => state.counter,
actions
)(Counter);
src\store\index.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
src\store\action-types.js
export const ADD = 'ADD';
export const MINUS = 'MINUS';
src\store\reducers\counter.js
import * as actionTypes from '../action-types';
let initialState = { number: 0 }
function counter(state = initialState, action) {
switch (action.type) {
case actionTypes.ADD:
return { number: state.number + 1 };
case actionTypes.MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
export default counter;
src\store\reducers\index.js
import { combineReducers } from "redux";
import counter from "./counter";
let rootReducer = combineReducers({
counter
});
export default rootReducer;
src\store\actions\counter.js
import * as types from '../action-types';
const actions = {
add() {
return {type: types.ADD};
}
,
minus() {
return {type:types.MINUS};
}
};
export default actions;
redux-logger
是一个 Redux 中间件,它在控制台记录了关于 actions 和 store 的信息,对于调试 Redux 应用非常有用。它记录的信息包括每个 action 的类型和数据,以及每次 action 被处理后新的 state。
src\store\index.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
+import logger from 'redux-logger';
import rootReducer from './reducers';
+const store = applyMiddleware(logger)(createStore)(rootReducer);
export default store;
redux-thunk
是一个 Redux 的中间件,允许我们写返回函数的 action creator,而不仅仅是对象。这使得我们可以在 action creator 中执行异步操作,如 API 请求等。
src\store\index.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
+import thunk from 'redux-thunk';
import rootReducer from './reducers';
+const store = applyMiddleware(thunk,logger)(createStore)(rootReducer);
export default store;
src\store\actions\counter.js
import * as types from '../action-types';
const actions = {
add() {
return { type: types.ADD };
},
+ thunkAdd() {
+ return function (dispatch) {
+ setTimeout(function () {
+ dispatch({ type: types.ADD });
+ }, 2000);
+ }
+ },
minus() {
return { type: types.MINUS };
}
};
export default actions;
src\components\Counter.js
import React from 'react';
+import {connect} from 'react-redux';
import actions from '../store/actions/counter';
function Counter(props) {
const {number,add,minus,thunkAdd} = props;
return (
<div>
<p>{number}</p>
<button onClick={add}>+</button>
+ <button onClick={thunkAdd}>thunkAdd</button>
<button onClick={minus}>-</button>
</div>
);
}
export default connect(
state => state.counter,
actions
)(Counter);
redux-promise
是一个 Redux 的中间件,用于处理 action 中携带的 Promise 对象。当你 dispatch 一个 action,如果这个 action 的 payload 属性是一个 Promise,redux-promise
会等待这个 Promise 结束。如果 Promise resolve,redux-promise
会 dispatch 一个新的 action,并使用 Promise 的结果作为 payload。如果 Promise reject,redux-promise
会 dispatch 一个新的 action,并将错误作为 payload,并且为这个 action 添加一个 error
属性。
src\store\index.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
+import promise from 'redux-promise';
import rootReducer from './reducers';
+const store = applyMiddleware(thunk,promise,logger)(createStore)(rootReducer);
export default store;
src\store\actions\counter.js
import * as types from '../action-types';
const actions = {
add() {
return { type: types.ADD };
},
thunkAdd() {
return function (dispatch) {
setTimeout(function () {
dispatch({ type: types.ADD });
}, 2000);
}
},
+ promiseAdd() {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve({ type: types.ADD });
+ }, 1000);
+ });
+ },
+ payloadPromiseAdd() {
+ return {
+ type: types.ADD,
+ payload: new Promise((resolve, reject) => {
+ setTimeout(() => {
+ const rand = Math.random();
+ if ( rand> .5) {
+ resolve(rand);
+ } else {
+ reject(rand);
+ }
+ }, 1000);
+ }).then(data => ({data}), (err) =>( {error:true,data:err}))
+ }
+ },
minus() {
return { type: types.MINUS };
}
};
export default actions;
src\store\reducers\counter.js
import * as actionTypes from '../action-types';
let initialState = { number: 0 }
function counter(state = initialState, action) {
switch (action.type) {
case actionTypes.ADD:
+ if(action.payload?.error){
+ return { number: state.number - action.payload.data };
+ }
+ return { number: state.number + action.payload.data };
case actionTypes.MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
export default counter;
src\components\Counter.js
import React from 'react';
import {connect} from 'react-redux';
import actions from '../store/actions/counter';
function Counter(props) {
+ const {number,add,minus,thunkAdd,promiseAdd,payloadPromiseAdd} = props;
return (
<div>
<p>{number}</p>
<button onClick={add}>+</button>
<button onClick={thunkAdd}>thunkAdd</button>
+ <button onClick={promiseAdd}>promiseAdd</button>
+ <button onClick={payloadPromiseAdd}>payloadPromiseAdd</button>
<button onClick={minus}>-</button>
</div>
);
}
export default connect(
state => state.counter,
actions
)(Counter);
redux-first-history
是一个用于整合 React Router v4/v5 和 Redux 的库。它以 Redux-first 的方式将你的路由状态同步到你的 Redux store 中,这意味着你可以直接通过 dispatch actions 来改变你的 URL,同时 URL 的变化也会被反映到 Redux 的 state 中。这使得你可以像处理普通的 Redux state 一样处理你的路由状态。
redux-first-history
提供了一些 action creators,如 push
、replace
、go
、goBack
和 goForward
,你可以通过 dispatch 这些 actions 来改变 URL。同时,当 URL 改变时,LOCATION_CHANGE
action 会被自动 dispatch,并将新的 location 对象作为 payload。
在 reducer 中,你可以通过 state.router.location
获取当前的 location 对象,这个对象包括了当前的 pathname、search、hash 和 state。
总的来说,redux-first-history
提供了一种整合 React Router 和 Redux 的方法,使得你可以像处理普通的 Redux state 一样处理路由状态。同时,它还提供了一些有用的功能,如历史记录管理、延迟路由跳转等。
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import Counter from './components/Counter';
+import {store,history} from './store';
+import { HistoryRouter } from "redux-first-history/rr6";
+import { Routes, Route, Link } from "react-router-dom";
import Home from './components/Home';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
+ <Provider store={store}>
+ <HistoryRouter history={history}>
+ <ul>
+ <li><Link to="/">Home</Link></li>
+ <li><Link to="/counter">Counter</Link></li>
+ </ul>
+ <Routes>
+ <Route path="/" element={<Home />} />
+ <Route path="/counter" element={<Counter />} />
+ </Routes>
+ </HistoryRouter>
+ </Provider>
);
src\history.js
import { createBrowserHistory } from 'history';
import { createReduxHistoryContext } from "redux-first-history";
const history = createBrowserHistory();
const { routerReducer, routerMiddleware, createReduxHistory } = createReduxHistoryContext({ history });
export {
routerReducer,
routerMiddleware,
createReduxHistory
}
src\store\index.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
+import { routerMiddleware, createReduxHistory } from '../history';
import rootReducer from './reducers';
+const store = applyMiddleware(routerMiddleware,thunk,promise,logger)(createStore)(rootReducer);
+const history = createReduxHistory(store);
+export {
+ store,
+ history
+}
src\store\reducers\index.js
import { combineReducers } from "redux";
import counter from "./counter";
+import { routerReducer } from '../../history';
let rootReducer = combineReducers({
counter,
+ router: routerReducer
});
export default rootReducer;
src\components\Home.js
import React from 'react';
import { useDispatch } from 'react-redux';
import { push } from "redux-first-history";
function Home() {
const dispatch = useDispatch();
const gotoCounter = () => {
dispatch(push('/counter'));
}
return (
<div>
<p>Home</p>
<button onClick={gotoCounter}>跳转到/counter</button>
</div>
)
}
export default Home;