1.Redux Toolkit 是什么? #

2. 安装 #

npm install redux redux-logger redux-thunk @reduxjs/toolkit   express cors  axios --save

3. 正常用法 #

3.1 public\index.html #

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>Redux Toolkit</title>
  </head>
  <body>
    <div>
      <p id="value">0</p>
      <button id="add">+</button>
      <button id="minus">-</button>
    </div>
  </body>
</html>

3.2 src\index.js #

src\index.js

import { 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())
})

3.3 问题 #

4. configureStore #

4.1 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>Redux Toolkit</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>

4.2 src\index.js #

src\index.js

-import { createStore } from 'redux';
+import {configureStore} from '@reduxjs/toolkit';
+import {configureStore} from './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
    }
}

-let store = createStore(counter)
+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)
+  })
+})

4.3 toolkit\index.js #

src\toolkit\index.js

export { default as configureStore } from './configureStore';

4.4 configureStore.js #

src\toolkit\configureStore.js

import { combineReducers,applyMiddleware,createStore,compose} from 'redux';
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 composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    return createStore(rootReducer, preloadedState, composeEnhancers(enhancer));
}
export default configureStore;

compose

function add1(str){
    return str+1;
}
function add2(str){
    return str+2;
}
function compose(fn1,fn2){
 return function(str){
   return fn1(fn2(str))
 }
}
let fn = compose(add1, add2);
let result = fn('zhufeng');
console.log(result);//zhufeng21

5. createAction #

5.1 src\index.js #

src\index.js

+import {configureStore,createAction} from './toolkit';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
-const ADD = 'ADD'
-const MINUS = 'MINUS'

-function add() {
-    return { type: ADD }
-}
+const add = createAction('ADD')
-function minus() {
-    return { type: MINUS }
-}
+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)
  })
})

5.2 toolkit\index.js #

src\toolkit\index.js

export { default as configureStore } from './configureStore';
+export { default as createAction } from './createAction';

5.3 createAction.js #

src\toolkit\createAction.js

function createAction(type, prepareAction) {
    function actionCreator(...args) {
        if (prepareAction) {
            var prepared = prepareAction.apply(null, args);
            return {
                type: type,
                payload: prepared.payload,
                error: prepared.error
            };
        }
        return {
            type: type,
            payload: args[0]
        };
    }
    actionCreator.toString = function () {
        return "" + type;
    }
    actionCreator.type = type;
    return actionCreator;
}
export default createAction;

6. createReducer #

6.1 src\index.js #

src\index.js

import {configureStore,createAction,createReducer} from './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 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)
  })
})

6.2 createReducer.js #

src\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;

6.3 toolkit\index.js #

src\toolkit\index.js

export { default as configureStore } from './configureStore';
export { default as createAction } from './createAction';
+export { default as createReducer } from './createReducer';

7. createSlice #

7.1 src\index.js #

//import {configureStore,createAction,createReducer,createSlice} from '@reduxjs/toolkit';
import {configureStore,createAction,createReducer,createSlice} from './toolkit';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
-const add = createAction('ADD')
-const minus = createAction('MINUS', (amount) => ({ payload: amount }))
-const counter = createReducer({number:0}, {
-  [add]: state => ({number:state.number+1}),
-  [minus]: state => ({number:state.number-1})
-})

+const counterSlice = createSlice({
+  name: 'counter',
+  initialState: {number:0},
+  reducers: {
+    add: (state)  =>  ({number:state.number+1}),//派发的时候动作类型是 counter/add
+    minus: (state,action) => ({number:state.number-action.payload})
+  }
+})
const { actions, reducer } = counterSlice
console.log(actions);
const { add, minus } = actions
console.log(add);

const store = configureStore({
  reducer: 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)
  })
})

7.2 createSlice.js #

src\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;

7.3 toolkit\index.js #

src\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';

8. immer #

8.1 基础 #

8.1.1 介绍 #

8.1.2 produce #

let produce = require('immer').default;
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

8.2 使用 #

8.2.1 src\index.js #

src\index.js

//import {configureStore,createAction,createReducer,createSlice} from '@reduxjs/toolkit';
import {configureStore,createAction,createReducer,createSlice} from './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,//派发的时候动作类型是 counter/add
+   minus: (state,action) => state.number-=action.payload
  }
})
const { actions, reducer } = counterSlice
console.log(actions);
const { add, minus } = actions
console.log(add);

const store = configureStore({
  reducer: 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)
  })
})

8.2.2 createReducer.js #

src\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;

9. reselect #

9.1 基础 #

///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);

9.2 使用 #

9.2.1 index.html #

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>Redux Toolkit</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>

9.2.2 src\index.js #

src\index.js

//import {configureStore,createAction,createReducer,createSlice} from '@reduxjs/toolkit';
import {configureStore,createAction,createReducer,createSlice,createSelector} from './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())
})

9.2.3 toolkit\index.js #

src\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';

9.2.4 reselect\index.js #

src\reselect\index.js

export {default as createSelector} from './createSelector';

9.2.5 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;
    }
}
export default createSelector;

10. createAsyncThunk #

10.1 src\index.js #

src\index.js

import { configureStore, createSlice, createAsyncThunk } from './toolkit';
import axios from 'axios';
//接收redux动作类型字符串和一个返回promise回调的函数
//它会基于你传递的动作类型前缀生成promise生命周期的动作类型
//并且返回一个thunk动作创建者,这个thunk动作创建者会运行promise回调并且派发生命周期动作
export 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: 'todo',
  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());
  },);
});

10.2 configureStore.js #

src\toolkit\configureStore.js

import { combineReducers,applyMiddleware,createStore,compose } 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=[thunk], preloadedState } = options;
    let rootReducer;
    if (typeof reducer === "function") {
        rootReducer = reducer;
    } else if (isPlainObject(reducer)) {
        rootReducer = combineReducers(reducer);
    }
    const enhancer = applyMiddleware(...middleware);
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    return createStore(rootReducer, preloadedState, composeEnhancers(enhancer));
}
export default configureStore;

10.3 createReducer.js #

src\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;

10.4 createSlice.js #

src\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;

10.5 createAsyncThunk.js #

src\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;

10.6 toolkit\index.js #

src\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";

10.7 api.js #

api.js

let express = require('express');
let cors = require('cors');
let app = express();
app.use(cors());
app.use((req,res,next)=>{
    setTimeout(()=>{
      if(Math.random()>.5){
        next();
      }else{
        next('接口出错');
      }
    },1000);
});
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启动`));

11.Redux Toolkit Query #

11.1 src\index.js #

src\index.js

<!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>Redux Toolkit</title>
  </head>
  <body>
+    <div id="root"></div>
  </body>
</html>

11.2 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));

11.3 src\App.js #

src\App.js

import todosApi from './todos'
//使用自定义查询 hook可以自动获取数据并且返回查询结果
function App() {
    // 使用一个查询Hook,它会自动获取数据并且返回查询的值
    //const { data, error, isLoading } = useGetTodosQuery(1)
    const { data, error, isLoading } = todosApi.endpoints.getTodos.useQuery(1)
    // Individual hooks are also accessible under the generated endpoints:
    // 每个hook也
    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;

11.4 src\store.js #

src\store.js

import { configureStore } from '@reduxjs/toolkit'
//import { configureStore } from './toolkit'
import todosApi from './todos'
 //API slice会包含自动生成的redux reducer和一个自定义中间件
const store = configureStore({
  reducer: {
    [todosApi.reducerPath]: todosApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(todosApi.middleware)
})
export default store;

11.5 src\todos.js #

src\todos.js

//React entry point 会自动根据endpoints生成hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
//import { createApi, fetchBaseQuery } from './toolkit/query/react'
//使用base URL 和endpoints 定义服务
const todosApi = createApi({
  reducerPath: 'todosApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:8080' }),
  endpoints: (builder) => {
    return {
      //从参数生成查询参数 转变响应并且缓存
      getTodos: builder.query({query: (id) => `/todos/detail/${id}`}),
    }
  }
})
//导出可在函数式组件使用的hooks,它是基于定义的endpoints自动生成的
//export const { useGetTodosQuery } = todosApi
export default todosApi;

11.6 configureStore.js #

src\toolkit\configureStore.js

import { combineReducers,applyMiddleware,createStore,compose } 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=[thunk], preloadedState } = options;
    let rootReducer;
    if (typeof reducer === "function") {
        rootReducer = reducer;
    } else if (isPlainObject(reducer)) {
        rootReducer = combineReducers(reducer);
    }
+   middleware=typeof middleware === 'function'?middleware(()=>[thunk]):middleware
    const enhancer = applyMiddleware(...middleware);
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    return createStore(rootReducer, preloadedState, composeEnhancers(enhancer));
}
export default configureStore;

11.7 react.js #

src\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 async function (url) {
        url = baseUrl + url;
        let data = await fetch(url).then(res => res.json());
        return data;
    }
}

function createApi({ reducerPath, baseQuery, endpoints }) {
    let builder = {
        query(options) {
            function useQuery(id) {
                const { store } = useContext(ReactReduxContext)
                const [, forceUpdate] = useReducer(x => x + 1, 0);
                useEffect(() => {
                    let url = options.query(id);
                    store.dispatch({ type: FETCH_DATA, payload: { url } });
                    return store.subscribe(forceUpdate);
                }, [id, store])
                let state = store.getState();
                return state ? state[reducerPath] : {};
            }
            return { useQuery };
        }
    }
    let slice = createSlice({
        name: reducerPath,
        initialState: { data: null, error: null, isLoading: false },
        reducers: {
            setValue(state, { payload = {} }) {
                for (let key in payload)
                    state[key] = payload[key];
            }
        }
    });
    const { actions, reducer } = slice
    let 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 () {
                            try {
                                dispatch(actions.setValue({ isLoading: true }));
                                let data = await baseQuery(url);
                                dispatch(actions.setValue({ data, isLoading: false }));
                            } catch (error) {
                                console.log(error);
                                console.log(typeof error);
                                dispatch(actions.setValue({ error: { error: error.toString() }, isLoading: false }));
                            }
                        })();
                    } else {
                        next(action);
                    }
                }
            }
        }
    }
    return api;
}

export { fetchBaseQuery, createApi }

12.axios-basequery #

11.4.1 src\todos.js #

src\todos.js

//React entry point 会自动根据endpoints生成hooks
import { createApi, fetchBaseQuery } 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 result = await axios({ url: baseUrl + url })
      return result;
    } catch (error) {
      return error;
    }
  }
)
//import { createApi, fetchBaseQuery } from './toolkit/query/react'
//使用base URL 和endpoints 定义服务
const todosApi = createApi({
  reducerPath: 'todosApi',
  baseQuery: axiosBaseQuery({ baseUrl: 'http://localhost:8080' }),
  endpoints: (builder) => {
    return {
      //从参数生成查询参数 转变响应并且缓存
      getTodos: builder.query({query: (id) => `/todos/detail/${id}`}),
    }
  }
})
//导出可在函数式组件使用的hooks,它是基于定义的endpoints自动生成的
//export const { useGetTodosQuery } = todosApi
export default todosApi;

参考 #