npm install redux react-redux redux-logger redux-thunk @reduxjs/toolkit
npm install express cors axios
public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
</head>
<body>
<div>
<p id="value">0</p>
<button id="add">+</button>
<button id="minus">-</button>
</div>
</body>
</html>
src\index.js
import { legacy_createStore as createStore } from 'redux'
const ADD = 'ADD'
const MINUS = 'MINUS'
function add() {
return { type: ADD }
}
function minus() {
return { type: MINUS }
}
function counter(state = { number: 0 }, action) {
switch (action.type) {
case ADD:
return { number: state.number + 1 }
case MINUS:
return { number: state.number - 1 }
default:
return state
}
}
var store = createStore(counter)
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().number;
}
render()
store.subscribe(render)
document.getElementById('add').addEventListener('click', function () {
store.dispatch(add())
})
document.getElementById('minus').addEventListener('click', function () {
store.dispatch(minus())
})
action creator
增加代码量const ADD = 'ADD'
冗余switch
结构不清晰configureStore()
函数,其中覆盖了 createStore()
的功能configureStore()
函数 提供简化的配置选项。它可以自动组合切片 slice
的 reducer
,添加你提供的任何 Redux 中间件,默认 情况下包含 redux-thunk
,并启用 Redux DevTools扩展public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
</head>
<body>
<div>
<p id="value">0</p>
<button id="add">+</button>
<button id="minus">-</button>
+ <button id="async-add">async-add</button>
</div>
</body>
</html>
src\index.js
+import { configureStore } from './@reduxjs/toolkit';
+import {thunk} from 'redux-thunk';
+import logger from 'redux-logger';
const ADD = 'ADD'
const MINUS = 'MINUS'
function add() {
return { type: ADD }
}
function minus() {
return { type: MINUS }
}
function counter(state = { number: 0 }, action) {
switch (action.type) {
case ADD:
return { number: state.number + 1 }
case MINUS:
return { number: state.number - 1 }
default:
return state
}
}
+const store = configureStore({
+ reducer: counter,
+ middleware:()=> [thunk, logger]
+})
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().number;
}
render()
store.subscribe(render)
document.getElementById('add').addEventListener('click', function () {
store.dispatch(add())
})
document.getElementById('minus').addEventListener('click', function () {
store.dispatch(minus())
})
+document.getElementById('async-add').addEventListener('click', function () {
+ store.dispatch((dispatch)=>{
+ setTimeout(()=>{
+ dispatch(add())
+ },1000)
+ })
+})
src\@reduxjs\toolkit\index.js
export { default as configureStore } from './configureStore';
src\@reduxjs\toolkit\configureStore.js
import { combineReducers, applyMiddleware, legacy_createStore } from 'redux';
import {thunk} from 'redux-thunk';
function isPlainObject(value) {
if (typeof value !== "object" || value === null)
return false;
return Object.getPrototypeOf(value) === Object.prototype;
}
function configureStore(options = {}) {
let { reducer, middleware, preloadedState } = options;
let rootReducer;
if (typeof reducer === "function") {
rootReducer = reducer;
} else if (isPlainObject(reducer)) {
rootReducer = combineReducers(reducer);
}
const defaultMiddlewares = [thunk];
return applyMiddleware(...typeof middleware ==='function'?middleware(defaultMiddlewares):defaultMiddlewares)(legacy_createStore)(rootReducer, preloadedState);
}
export default configureStore;
createAction
接受一个 action
类型字符串作为参数,并返回一个使用该类型字符串的 action creator
函数src\index.js
+import { configureStore,createAction } from './@reduxjs/toolkit';
import {thunk} from 'redux-thunk';
import logger from 'redux-logger';
+const add = createAction('ADD')
+const minus = createAction('MINUS', (amount) => ({ payload: amount }))
+console.log(minus.toString());
+console.log(minus.type);
function counter(state = { number: 0 }, action) {
switch (action.type) {
+ case add.type:
return { number: state.number + 1 }
+ case minus.type:
+ return {number:state.number-action.payload}
default:
return state
}
}
const store = configureStore({
reducer: counter,
middleware:()=> [thunk, logger]
})
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().number;
}
render()
store.subscribe(render)
document.getElementById('add').addEventListener('click', function () {
store.dispatch(add())
})
document.getElementById('minus').addEventListener('click', function () {
+ store.dispatch(minus(2))
})
document.getElementById('async-add').addEventListener('click', function () {
store.dispatch((dispatch)=>{
setTimeout(()=>{
dispatch(add())
},1000)
})
})
src\@reduxjs\toolkit\index.js
export { default as configureStore } from './configureStore';
+export { default as createAction } from './createAction';
src\@reduxjs\toolkit\createAction.js
function createAction(type, prepareAction) {
function actionCreator(...args) {
if (prepareAction) {
var prepared = prepareAction.apply(null, args);
return {
type,
...prepared
};
}
return {
type,
payload: args[0]
};
}
actionCreator.toString = function () {
return "" + type;
}
actionCreator.type = type;
return actionCreator;
}
export default createAction;
createReducer
函数 ,它让使用"查找表"对象的方式编写 reducer
key
都是一个 Redux action type
字符串,value
是 reducer
函数src\index.js
+import { configureStore,createAction,createReducer } from './@reduxjs/toolkit';
import {thunk} from 'redux-thunk';
import logger from 'redux-logger';
const add = createAction('ADD')
const minus = createAction('MINUS', (amount) => ({ payload: amount }))
console.log(minus.toString());
console.log(minus.type);
+const counter = createReducer({number:0}, {
+ [add]: state => ({number:state.number+1}),
+ [minus]: state => ({number:state.number-1})
+})
const store = configureStore({
reducer: counter,
middleware:()=> [thunk, logger]
})
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().number;
}
render()
store.subscribe(render)
document.getElementById('add').addEventListener('click', function () {
store.dispatch(add())
})
document.getElementById('minus').addEventListener('click', function () {
store.dispatch(minus(2))
})
document.getElementById('async-add').addEventListener('click', function () {
store.dispatch((dispatch)=>{
setTimeout(()=>{
dispatch(add())
},1000)
})
})
src\@reduxjs\toolkit\index.js
export { default as configureStore } from './configureStore';
export { default as createAction } from './createAction';
+export { default as createReducer } from './createReducer';
src\@reduxjs\toolkit\createReducer.js
function createReducer(initialState, reducers={}) {
return function (state = initialState, action) {
let reducer = reducers[action.type];
if (reducer) return reducer(state, action);
return state;
}
}
export default createReducer;
import produce from 'immer';
function createReducer(initialState, builderCallback) {
// 用于存储每种 action 类型对应的处理函数
const actionsMap = new Map();
// 执行 builderCallback 来填充 actionsMap
const builder = {
addCase(action, reducer) {
actionsMap.set(action.type, reducer);
return this; // 允许链式调用
}
};
builderCallback(builder);
// 返回实际的 reducer 函数
return function reducer(state = initialState, action) {
// 检查这个 action 类型是否有对应的处理函数
const caseReducer = actionsMap.get(action.type);
if (caseReducer) {
// 使用 Immer 的 produce 来保证状态的不可变性
return produce(state, draftState => {
caseReducer(draftState, action);
});
}
return state;
};
}
export default createReducer;
src\index.js
+import { configureStore, createSlice} from './@reduxjs/toolkit';
import { thunk } from 'redux-thunk';
import logger from 'redux-logger';
+const counterSlice = createSlice({
+ name: 'counter',
+ initialState: { number: 0 },
+ reducers: {
+ add: (state) => ({ number: state.number + 1 }),
+ minus: (state, action) => ({ number: state.number - action.payload })
+ }
+})
+const { actions, reducer } = counterSlice
+const { add, minus } = actions
const store = configureStore({
+ reducer,
middleware: () => [thunk, logger]
})
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().number;
}
render()
store.subscribe(render)
document.getElementById('add').addEventListener('click', function () {
store.dispatch(add())
})
document.getElementById('minus').addEventListener('click', function () {
store.dispatch(minus(2))
})
document.getElementById('async-add').addEventListener('click', function () {
store.dispatch((dispatch) => {
setTimeout(() => {
dispatch(add())
}, 1000)
})
})
src\@reduxjs\toolkit\index.js
export { default as configureStore } from './configureStore';
export { default as createAction } from './createAction';
export { default as createReducer } from './createReducer';
+export { default as createSlice } from './createSlice';
src\@reduxjs\toolkit\createSlice.js
import { createReducer, createAction } from './'
function createSlice(options) {
let { name, initialState = {}, reducers = {} } = options;
let actions = {};
const prefixReducers = {};
Object.keys(reducers).forEach(function (key) {
var type = getType(name, key);
actions[key] = createAction(type);
prefixReducers[type] = reducers[key];
})
let reducer = createReducer(initialState, prefixReducers);
return {
name,
reducer,
actions
};
}
function getType(slice, actionKey) {
return slice + "/" + actionKey;
}
export default createSlice;
let {produce} = require('immer');
let baseState = {
ids: [1],
pos: {
x: 1,
y: 1
}
}
let nextState = produce(baseState, (draft) => {
draft.ids.push(2);
})
console.log(baseState.ids === nextState.ids);//false
console.log(baseState.pos === nextState.pos);//true
function produce(baseState, producer) {
// 创建一个深拷贝的草稿
let draft = JSON.parse(JSON.stringify(baseState));
// 应用修改函数
producer(draft);
// 遍历对象属性,比较修改前后的差异
// 并根据需要创建新的不可变状态
const nextState = {};
for (let key in baseState) {
if (JSON.stringify(baseState[key]) !== JSON.stringify(draft[key])) {
nextState[key] = draft[key];
} else {
nextState[key] = baseState[key];
}
}
return nextState;
}
// const { produce } = require('immer');
// 检查一个值是否为对象
const isObject = (val) => Object.prototype.toString.call(val) === '[object Object]';
// 检查一个值是否为数组
const isArray = (val) => Array.isArray(val);
// 检查一个值是否为函数
const isFunction = (val) => typeof val === 'function';
// 创建一个唯一的Symbol,用于内部状态的存储
const INTERNAL = Symbol('INTERNAL');
// 将基础状态转换为代理对象的函数
function toProxy(baseState, valueChange) {
// 存储每个属性的代理对象
let keyToProxy = {};
// 内部状态,包含草稿状态、属性代理和是否发生了变化的标志
let internal = {
draftState: createDraftState(baseState), // 创建草稿状态
keyToProxy, // 属性代理映射
mutated: false // 是否发生变化的标志
}
// 返回baseState的代理对象
return new Proxy(baseState, {
// 拦截对象属性的读取
get(target, key) {
// 特殊情况:如果读取的是INTERNAL标识符,返回内部状态
if (key === INTERNAL) {
return internal;
}
// 获取目标属性的值
const value = target[key];
// 如果值是对象或数组,需要特殊处理
if (isObject(value) || isArray(value)) {
// 如果该属性已经被代理过,直接返回代理对象
if (key in keyToProxy) {
return keyToProxy[key];
} else {
// 否则,创建新的代理对象,并存储起来
keyToProxy[key] = toProxy(value, () => {
// 在子对象变化时,更新内部状态
internal.mutated = true;
const proxyOfChild = keyToProxy[key];
const { draftState } = proxyOfChild[INTERNAL];
internal.draftState[key] = draftState;
valueChange && valueChange();
})
return keyToProxy[key];
}
} else if (isFunction(value)) {
// 如果值是函数,绑定到草稿状态,并标记为已变化
internal.mutated = true;
valueChange && valueChange();
return value.bind(internal.draftState);
}
// 返回草稿状态或基础状态的属性值
return internal.mutated ? internal.draftState[key] : baseState[key];
},
// 拦截对象属性的设置
set(target, key, value) {
// 标记为已变化
internal.mutated = true;
// 获取草稿状态
let { draftState } = internal;
// 确保草稿状态包含所有原始对象的属性
for (const key in target) {
draftState[key] = key in draftState ? draftState[key] : target[key];
}
// 设置新的值
draftState[key] = value;
valueChange && valueChange();
// 返回true表示设置成功
return true;
}
});
// 创建草稿状态的函数
function createDraftState(baseState) {
// 如果是数组,返回其副本
if (isArray(baseState)) {
return [...baseState];
} else if (isObject(baseState)) {
// 如果是对象,返回其副本
return Object.assign({}, baseState);
} else {
// 否则,直接返回原始值
return baseState;
}
}
}
// 主函数,用于生成新的状态
function produce(baseState, producer) {
// 创建代理对象
const proxy = toProxy(baseState);
// 应用修改函数
producer(proxy);
// 获取内部状态
const internal = proxy[INTERNAL];
// 根据是否发生变化返回新的状态或原始状态
return internal.mutated ? internal.draftState : baseState;
}
// 示例基础状态
let baseState = {
ids: [1],
pos: {
x: 1,
y: 1
}
};
// 生成新的状态
let nextState = produce(baseState, draft => {
draft.ids.push(2);
});
// 输出比较结果
console.log(baseState.ids === nextState.ids); // 比较ids数组是否相同
console.log(baseState.pos === nextState.pos); // 比较pos对象是否相同
src\index.js
import { configureStore, createSlice} from './@reduxjs/toolkit';
import { thunk } from 'redux-thunk';
import logger from 'redux-logger';
const counterSlice = createSlice({
name: 'counter',
initialState: { number: 0 },
reducers: {
+ add: (state) => state.number+=1,
+ minus: (state, action) => state.number-=action.payload
}
})
const { actions, reducer } = counterSlice
const { add, minus } = actions
const store = configureStore({
reducer,
middleware: () => [thunk, logger]
})
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().number;
}
render()
store.subscribe(render)
document.getElementById('add').addEventListener('click', function () {
store.dispatch(add())
})
document.getElementById('minus').addEventListener('click', function () {
store.dispatch(minus(2))
})
document.getElementById('async-add').addEventListener('click', function () {
store.dispatch((dispatch) => {
setTimeout(() => {
dispatch(add())
}, 1000)
})
})
src\@reduxjs\toolkit\createReducer.js
+import produce from 'immer';
function createReducer(initialState, reducers = {}) {
return function (state = initialState, action) {
let reducer = reducers[action.type];
if (reducer) {
+ return produce(state, draft => {
+ reducer(draft, action);
+ });
}
return state;
}
}
export default createReducer;
///const {createSelector} = require('reselect');
function createSelector(selectors, reducer) {
let lastState;
let lastValue;
return function (state) {
if (lastState === state) {
return lastValue;
}
let values = selectors.map(selector => selector(state));
lastValue = reducer(...values);
lastState = state;
return lastValue;
}
}
const selectCounter1 = state => state.counter1
const selectCounter2 = state => state.counter2
const totalSelector = createSelector(
[selectCounter1, selectCounter2],
(counter1, counter2) => {
console.log('计算结果');
return counter1.number + counter2.number;
}
)
let state = { counter1: { number: 1 }, counter2: { number: 2 } };
let state1 = totalSelector(state);
console.log(state1);
let state2 = totalSelector(state);
console.log(state2);
public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<div>
+ <p id="value1">0</p>
+ <button id="add1">add1</button>
+ <button id="minus1">minus1</button>
+ <hr />
+ <p id="value2">0</p>
+ <button id="add2">add2</button>
+ <button id="minus2">minus2</button>
+ <hr />
+ <p id="sum">0</p>
</div>
</body>
</html>
src\index.js
import { configureStore, createSlice,createSelector} from './@reduxjs/toolkit';
import { thunk } from 'redux-thunk';
import logger from 'redux-logger';
const counter1Slice = createSlice({
name: 'counter1',
initialState: { number: 0 },
reducers: {
add: state => { state.number += 1 },
minus: state => { state.number -= 1 }
}
})
const counter2Slice = createSlice({
name: 'counter2',
initialState: { number: 0 },
reducers: {
add: state => { state.number += 1 },
minus: state => { state.number -= 1 }
}
})
const { actions: { add: add1, minus: minus1 }, reducer: reducer1 } = counter1Slice
const { actions: { add: add2, minus: minus2 }, reducer: reducer2 } = counter2Slice
const store = configureStore({
reducer: { counter1: reducer1, counter2: reducer2 },
middleware: ()=>[thunk, logger]
})
var value1El = document.getElementById('value1')
var value2El = document.getElementById('value2')
var sumEl = document.getElementById('sum')
const selectCounter1 = state => state.counter1
const selectCounter2 = state => state.counter2
const totalSelector = createSelector(
[selectCounter1, selectCounter2],
(counter1, counter2) => {
return counter1.number + counter2.number;
}
)
function render() {
value1El.innerHTML = store.getState().counter1.number;
value2El.innerHTML = store.getState().counter2.number;
sumEl.innerHTML = totalSelector(store.getState());
}
render()
store.subscribe(render)
document.getElementById('add1').addEventListener('click', function () {
store.dispatch(add1())
})
document.getElementById('minus1').addEventListener('click', function () {
store.dispatch(minus1())
})
document.getElementById('add2').addEventListener('click', function () {
store.dispatch(add2())
})
document.getElementById('minus2').addEventListener('click', function () {
store.dispatch(minus2())
})
src\@reduxjs\toolkit\index.js
export { default as configureStore } from './configureStore';
export { default as createAction } from './createAction';
export { default as createReducer } from './createReducer';
export { default as createSlice } from './createSlice';
+export {createSelector } from '../../reselect';
src\reselect\index.js
export {default as createSelector} from './createSelector.js';
src\reselect\createSelector.js
function createSelector(selectors, reducer) {
let lastState;
let lastValue;
return function (state) {
if (lastState === state) {
return lastValue;
}
let values = selectors.map(selector => selector(state));
lastValue = reducer(...values);
lastState = state;
return lastValue;git add
}
}
export default createSelector;
src\index.js
import { configureStore, createSlice, createSelector, createAsyncThunk } from './@reduxjs/toolkit';
import axios from 'axios';
const getTodosList = createAsyncThunk(
"todos/list", async () => await axios.get(`http://localhost:8080/todos/list`)
);
const initialState = {
todos: [],
loading: false,
error: null,
};
const todoSlice = createSlice({
name: 'todos',
initialState,
reducers: {},
extraReducers: {
[getTodosList.pending]: (state) => {
state.loading = true;
},
[getTodosList.fulfilled]: (state, action) => {
state.todos = action.payload.data;
state.loading = false;
},
[getTodosList.rejected]: (state, action) => {
state.todos = [];
state.error = action.error.message;
state.loading = false;
}
}
})
const { reducer } = todoSlice;
const store = configureStore({
reducer
})
let promise = store.dispatch(getTodosList());
console.log('请求开始', store.getState());
//promise.abort();
promise.then((response) => {
console.log('成功', response);
setTimeout(() => {
console.log('请求结束', store.getState());
},);
}, error => {
console.log('失败', error);
setTimeout(() => {
console.log('请求结束', store.getState());
},);
});
src\@reduxjs\toolkit\index.js
export { default as configureStore } from './configureStore';
export { default as createAction } from './createAction';
export { default as createReducer } from './createReducer';
export { default as createSlice } from './createSlice';
export {createSelector } from '../../reselect';
+export { default as createAsyncThunk } from "./createAsyncThunk";
src\@reduxjs\toolkit\createSlice.js
import { createReducer, createAction } from './'
function createSlice(options) {
+ let { name, initialState = {}, reducers = {},extraReducers={} } = options;
let actions = {};
const prefixReducers = {};
Object.keys(reducers).forEach(function (key) {
var type = getType(name, key);
actions[key] = createAction(type);
prefixReducers[type] = reducers[key];
})
+ let reducer = createReducer(initialState, prefixReducers,extraReducers);
return {
name,
reducer,
actions
};
}
function getType(slice, actionKey) {
return slice + "/" + actionKey;
}
export default createSlice;
src\@reduxjs\toolkit\createReducer.js
import produce from 'immer';
+function createReducer(initialState, reducers = {}, extraReducers = {}) {
return function (state = initialState, action) {
let reducer = reducers[action.type];
if (reducer) {
return produce(state, draft => {
reducer(draft, action);
});
}
+ let extraReducer = extraReducers[action.type];
+ if (extraReducer) {
+ return produce(state, draft => {
+ extraReducer(draft, action);
+ });
+ }
return state;
}
}
export default createReducer;
src\@reduxjs\toolkit\createAsyncThunk.js
import { createAction } from './';
function createAsyncThunk(typePrefix, payloadCreator) {
let pending = createAction(typePrefix + "/pending", function () {
return ({ payload: void 0 });
});
let fulfilled = createAction(typePrefix + "/fulfilled", function (payload) {
return ({ payload });
});
let rejected = createAction(typePrefix + "/rejected", function (error) {
return ({ error });
});
function actionCreator(arg) {
return function (dispatch) {
dispatch(pending());
const promise = payloadCreator(arg);
let abort;
const abortedPromise = new Promise((_, reject) => {
abort = () => {
reject({ name: "AbortError", message: "Aborted" });
}
});
Promise.race([promise, abortedPromise]).then(result => {
return dispatch(fulfilled(result));
}, (error) => {
return dispatch(rejected(error));
});
return Object.assign(promise, { abort });
}
}
return Object.assign(actionCreator, { pending, rejected, fulfilled });
}
export default createAsyncThunk;
import createAction from './createAction';
/**
* createAsyncThunk 接收redux动作类型和一个返回 Promise的异步请求函数
* typePrefix 动作类型的前缀
* payloadCreator 异步请求函数
* 它会基于你传递的动作类型前缀生成promise生命周期的动作类型
* 并且最终会返回一个thunk动作的创建者,这个thunk动作的创建者会根据promise函烽并且派发生命周期动作
* 它抽象了处于异步请求生命周期的标准方法
*/
function createAsyncThunk(typePrefix,payloadCreator){
const pending = createAction(`${typePrefix}/pending`,()=>{
return {payload:void 0};
})
const fulfilled = createAction(`${typePrefix}/fulfilled`,(payload)=>{
return {payload};
})
const rejected = createAction(`${typePrefix}/rejected`,(error)=>{
return {error};//action={type:'todos/rejected',error}
})
function actionCreator(){//getTodos
//返回一个thunk函数
return function(dispatch){//asyncThunk
dispatch(pending());
const axiosPromise = payloadCreator();
return axiosPromise.then(
result=>dispatch(fulfilled(result)),
error=>dispatch(rejected(error)),
)
}
}
return Object.assign(actionCreator,{pending,fulfilled,rejected});
}
export default createAsyncThunk;
server.js
let express = require('express');
let cors = require('cors');
let app = express();
app.use(cors());
let success= true;
app.use((req, res, next) => {
if(success){
success = false;
next();
}else{
success = true;
res.status(500).json({message:'服务器出错'});
}
});
let todos = [{ id: 1, text: "吃饭" }, { id: 2, text: "睡觉" }];
app.get('/todos/list', (_req, res) => {
res.json(todos);
});
app.get('/todos/detail/:id', (req, res) => {
let id = req.params.id;
let todo = todos.find(item => item.id === parseInt(id));
res.json(todo);
});
app.listen(8080, () => console.log(`服务在端口8080启动`));
根据用户的交互来管理缓存的生命周期
createApi()
RTK Query的核心函数,它允许你定义endpoint
的集合用来描述如何获取数据,包含如何获取和转换数据
fetchBaseQuery()
一个用来简化请求的对fetch的封装public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
+ <div id="root"></div>
</body>
</html>
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(<Provider store={store}><App /></Provider>);
src\App.js
import todosAPI from './todosApi'
function App() {
const { data, error, isLoading } = todosAPI.endpoints.getTodos.useQuery()
console.log('isLoading=', isLoading, 'error=', error, 'data=', data);
if (isLoading) {
return <div>加载中....</div>;
} else {
if (error) {
return <div>{error.error}</div>;
} else if (data) {
return <div>{data.text}</div>;
} else {
return null;
}
}
}
export default App;
src\todosApi.js
import { createApi, fetchBaseQuery } from './@reduxjs/toolkit/query/react'
const todosApi = createApi({
reducerPath: 'todosApi',
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:8080' }),
endpoints: (builder) => {
return {
getTodos: builder.query({query: () => `/todos/list`}),
getTodo: builder.query({query: (id) => `/todos/detail/${id}`}),
}
}
})
export default todosApi;
src\@reduxjs\toolkit\query\react.js
import { createSlice } from '../'
import { useEffect, useContext, useReducer } from 'react';
import { ReactReduxContext } from 'react-redux';
const FETCH_DATA = 'FETCH_DATA';
function fetchBaseQuery({ baseUrl }) {
return function (url) {
return fetch(baseUrl + url);
}
}
function createApi({ reducerPath, baseQuery, endpoints }) {
const builder = {
query(options) {
function useQuery(...args) {
const { store } = useContext(ReactReduxContext)
const [, forceUpdate] = useReducer(x => x + 1, 0);
useEffect(() => {
const url = options.query(...args);
store.dispatch({ type: FETCH_DATA, payload: { url } });
return store.subscribe(forceUpdate);
}, [store,...args])
const state = store.getState();
return state ? state[reducerPath] : {};
}
return { useQuery };
}
}
const slice = createSlice({
name: reducerPath,
initialState: { data: undefined, error: undefined, isLoading: false },
reducers: {
setValue(state, { payload = {} }) {
for (let key in payload)
state[key] = payload[key];
}
}
});
const { actions, reducer } = slice
const api = {
reducerPath,
endpoints: endpoints(builder),
reducer,
middleware: function ({ dispatch }) {
return function (next) {
return function (action) {
if (action.type === FETCH_DATA) {
let { url } = action.payload;
; (async function () {
dispatch(actions.setValue({ isLoading: true }));
const response = await baseQuery(url);
const { status } = response;
const data = await response.json();
if (status >= 200 && status < 300) {
dispatch(actions.setValue({ data, error: undefined, isLoading: false }));
} else {
dispatch(actions.setValue({ error: { status, data }, data: undefined, isLoading: false }));
}
})();
} else {
next(action);
}
}
}
}
}
return api;
}
export { fetchBaseQuery, createApi }
src\store.js
import { configureStore } from './@reduxjs/toolkit'
import todosAPI from './todosApi'
const store = configureStore({
reducer: {
[todosAPI.reducerPath]: todosAPI.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(todosAPI.middleware)
})
export default store;
src\todosApi.js
+import { createApi } from './@reduxjs/toolkit/query/react'
+import axios from 'axios'
+axios.interceptors.response.use(function (response) {
+ return {data:response.data};
+},function (error){
+ return {error:{error:error.message}};
+});
+const axiosBaseQuery = ({ baseUrl }) => (
+ async (url) => {
+ try {
+ const data = await axios({ url: baseUrl + url })
+ return { data}
+ } catch (error) {
+ return {
+ error: {
+ status: error.response?.status,
+ data: error.response?.data || error.message,
+ },
+ }
+ }
+ }
+)
const todosApi = createApi({
reducerPath: 'todosApi',
baseQuery: axiosBaseQuery({ baseUrl: 'http://localhost:8080' }),
endpoints: (builder) => {
return {
getTodos: builder.query({query: () => `/todos/list`}),
getTodo: builder.query({query: (id) => `/todos/detail/${id}`}),
}
}
})
export default todosApi;