Der Übergang von Redux zu Redux Toolkit (Teil 1)

Einführung in den Hintergrund

Die Front-End-Zeit besteht darin, Gerüste für den internen Gebrauch zu schreiben, über Datenverwaltung zu schreiben und viele datenverwaltungsbezogene Bibliotheken zu recherchieren. Wie zustand, redux toolkit, unstated-next usw. Später habe ich das Redux-Toolkit sorgfältig studiert und fand es zu umfangreich, also habe ich mir die entsprechende Quellcode-Implementierung angesehen, während das Eisen heiß war. Es ist kaum etwas Neues darin, es basiert alles auf einigen früheren Redux-Wrappern, und ich muss mir einen Daumen nach oben geben. So gibt es diese Artikelserie über die Umwandlung von Redux in das Redux-Toolkit (das erste und die nächsten beiden), durch einen einfachen Vergleich, kombiniert mit der Implementierung des Quellcodes, um zu teilen, was das Redux-Toolkit (im Folgenden als rtk bezeichnet) hat erledigt.

Zweck bauen

Der Zweck des Aufbaus von rtk besteht darin, die Logik zum Schreiben von Redux zu standardisieren. Es wurde ursprünglich entwickelt, um die folgenden drei Probleme zu lösen:

  • Das Erstellen eines Redux-Speichers ist zu kompliziert (Erstellen von Reducern, Aktionen, Aktionserstellern usw.)
  • Um Redux nützlicher zu machen, müssen Benutzer verschiedene Bibliotheken (wie Redux-Thunk, Redux-Sagger usw.)
  • redux erfordert viel Boilerplate-Code

Um also den gesamten Nutzungsprozess zu vereinfachen, wurde rtx ins Leben gerufen. Gleichzeitig bietet rtk auch ein sehr nützliches Tool zum Abrufen und Zwischenspeichern von Daten, rtk query. Auf diese Weise wird ein komplettes System aufgebaut.

Schritt-für-Schritt-Parsing

Lassen Sie uns einen einfachen Fall verwenden, um die Unterschiede zwischen redux und rtk in der Verwendung zu vergleichen. Hier wird der Fall einer einfachen digitalen Addition und Subtraktion auf der offiziellen rtx-Website (wie in der Abbildung unten gezeigt) für eine vergleichende Analyse verwendet.Bild

  • Allgemeiner Teil:

页面结构
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .container {
            display: flex;
            align-items: center;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="container">
            <button class="minuse">-</button>
            <div id="value"></div>
            <button class="add">+</button>
            <button class="add-async">add async</button>
        </div>
    </div>
</body>
</html>

公用js
const $value = document.getElementById("value");
function render() {
    $value.innerHTML = store.getState()?.num
}

function eventListener() {
    const $add = document.querySelector('.add');
    const $minuse = document.querySelector('.minuse');
    const $addAsync = document.querySelector('.add-async');
    $add.addEventListener('click', function() {
        store.dispatch(increment(2));
    })
    $minuse.addEventListener('click', function() {
        store.dispatch(decrement(3));
    })
    $addAsync.addEventListener('click', function() {
        store.dispatch((dispatch) => {
            setTimeout(() => {
                dispatch(increment(3))
            }, 1000)
        })
    })
}
//这里的store是由redux或者redux-toolkit创建出来的store
store.subscribe(render);
render();
eventListener();
  • Redux-Implementierung

Gemäß der Logik, die wir zuvor redux geschrieben haben, lautet der einfache Code wie folgt:

/*
 * @Author: ryyyyy
 * @Date: 2022-07-03 08:22:26
 * @LastEditors: ryyyyy
 * @LastEditTime: 2022-07-04 16:31:43
 * @FilePath: /toolkit-without-react/src/redux-index.js
 * @Description: 
 * 
 */
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';

const incrementType = "counter/increment";
const decrementType = "counter/decrement";

const increment = (count=2) => {
  return {
    type: incrementType,
    payload: count
  };
};

const decrement = (count=2) => {
  return {
    type: decrementType,
    payload: count
  };
};

const counterReducer = (state, action) => {
  switch (action.type) {
    case incrementType:
      return { ...state, num: state.num + (action?.payload || 1) };
    case decrementType:
      return { ...state, num: state.num - (action.payload || 1) };
    default:
      return state;
  }
};
  

const store = createStore(counterReducer, {num: 0}, applyMiddleware(logger, thunk));


export default store;

export {
    increment,
    decrement
}

Im Inneren denken wir immer noch wie gewohnt, definieren den actionCreator, definieren den Reducer, um die Aktion zu verarbeiten, und unterstützen dann das Drucken des Protokolls, indem wir den Logger einführen, und führen den Thunk ein, um den Versand zu unterstützen, ein asynchrones Antiion Kennen Sie diese Logiken, damit sie nicht mehr zu viel Beschreibung sind.

  • Implementierung des Redux-Toolkits

In diesem Teil verwenden wir rtk, um die obige Logik neu zu schreiben.Zur Bequemlichkeit des Vergleichs werden wir sie Schritt für Schritt neu schreiben.

  1. Action und Reducer Ersatz
import {createAction,createReducer } from '@reduxjs/toolkit';
const increment = createAction('counter/increment');
const decrement = createAction('counter/decrement');
//写法一
const counterReducer = createReducer({num: 0}, (builder) => {
    builder
      .addCase(increment, (state, action) => {
        state.num += action.payload
      })
      .addCase(decrement, (state, action) => {
        state.num -= action.payload
      })
})
//写法二
const counterReducer = createReducer({num: 0}, {
    [increment]: (state, action) => {state.num += action.payload},
    [decrement]: (state, action) => {state.num -= action.payload},
})

非常简单,这里通过createAction传入type就生成了increment和decrement两个actionCreator,createAction的第二个参数prepareAction?,用于对传入的action进行增强(后面源码分析会讲到)。然后利用createReducer简单的传入initialState和对应的描述各个reducer分支的逻辑,就能直接生成reducer。这里支持Builder Callback和Map Object两种写法,前者可以通过builder链式的调用,配置不同的reducer分支逻辑;后者,则通过map的形式,更为直观的给出各个reducer分支的配置。细心的读者还可以观察到,在各个reducer分支的实现里面,我们是直接操作state,是的,createReducer里面内置了immer的逻辑,简直棒呆! 2. store的生成

const store = configureStore({
    reducer: counterReducer,
    middleware: [logger]
})

其实这里跟原本的redux的createStore差不太多,只不过这里形参采用map的形式,更让你明白各个字段都是用作什么的。当然,这里不止这么些个参数配置,想要了解详情的小伙伴,请移步rtk官网。细心的读者会发现,这里我们并没有引入redux-thunk,哈哈哈,因为在其内部实现中已经帮我们内置了thunk的功能,突出一个方便。想不想更方便一点呢,当然有办法,rtx提供了一个createSlice的方法,讲上面的action,reducer等都融合在了一起,参看下面的代码:

const counterSlice = createSlice({
    name: 'counter', //用作命名空间,和别的slice区分开
    initialState: {num: 0},
    reducers: {
        increment(state, action) {
            state.num += action.payload;
        },
        decrement(state, action) {
            state.num -= action.payload;
        }
    }
})

const {reducer, actions} = counterSlice;
const {increment, decrement} = actions;

通过createSlice返回了actions和reducer,真的不能更简单了。下面,我们参照源码,来实现下rtk上述几个基本方法。

rtx源码实现

  • configureStore
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import isPlainObject from './utils/isPlainObject'; //工具函数,判断是不是一个对象
import thunk from 'redux-thunk';

const configureStore = (options) => {
    const {
        reducer,
        middleware = undefined,
        devTools = true,
        preloadedState = undefined
    } = options;
    let rootReducer;
    if (typeof reducer === 'function') {
        rootReducer = reducer
    } else if (isPlainObject(reducer)) {
        rootReducer = combineReducers(reducer);
    } else {
        throw new Error(
            '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers'
        )
    }
    const composedMiddleware = Array.isArray(middleware) ? middleware.concat(thunk) : [thunk];
    const composeEnhancers = devTools ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__||compose : compose;
    return createStore(reducer, preloadedState, composeEnhancers(applyMiddleware(...composedMiddleware)));
}

export default configureStore;

函数接受四个参数(官网是5个,我简化了)。首先reducer的创建,如果是函数的话,表示传入的是单个reducer,如果是对象,则使用combineReducers进行组合。然后是middleware,根据传入的middleware组合内置的thunk构建新的middleware。接着是enhancer部分,这里会判断是否打开Redux DevTools Extension(就是下面截图这玩意儿)。

rtx-dev-tool.png 最后调用redux的createStore,齐活儿。从这里我们就能看出,其实并没有什么新的逻辑,全是redux的一些概念。

  • createAction
import isPlainObject from "./utils/isPlainObject";
const createAction = (type, prepareAction) => {
    const actionCreator = (...args) => {
        if (prepareAction) {
            let prepared = prepareAction(...args);
            if (!isPlainObject(prepared)) {
                throw new Error('prepareAction did not return an object')
            }
            return {
                type,
                payload: prepared.payload
            }
        }
        return {
            type,
            payload: args[0]
        }
    }
    actionCreator.type = type;
    actionCreator.toString = () => `${type}`;
    return actionCreator;
}

export default createAction;

action的创建比较简单,直接返回了一个actionCreator。因为我们可以通过increment.type或者increment.toString()拿到action的type,所以在actionCreator上挂了两个属性。关于第二个参数prepareAction,如果传入了,则根据它生成新的payload,是对之前的payload的一个增强。

/*
 * @Author: ryyyyy
 * @Date: 2022-07-04 13:53:49
 * @LastEditors: ryyyyy
 * @LastEditTime: 2022-07-04 15:04:38
 * @FilePath: /toolkit-without-react/toolkit/createReducer.js
 * @Description:
 *
 */
import produce from "immer";

export const executeReducerBuilderCallback = (builderCallback) => {
  const actionsMap = {};
  const builder = {
    addCase: (typeOrActionCreator, reducer) => {
      const type =
        typeof typeOrActionCreator === "string"
          ? typeOrActionCreator
          : typeOrActionCreator.type;
      if (!actionsMap[type]) actionsMap[type] = reducer;
      return builder;
    },
  };
  builderCallback(builder);
  return [actionsMap];
};

const createReducer = (initialState, mapOrBuilderCallback) => {
  function reducer(state = initialState, action) {
    const type = typeof mapOrBuilderCallback;
    if (type !== "function" && type !== "object") {
      throw new Error(
        "mapOrBuilderCallback must be a map or a builder function"
      );
    }
    let [actionsMap] =
      type === "function"
        ? executeReducerBuilderCallback(mapOrBuilderCallback)
        : [mapOrBuilderCallback];
    let reducer = actionsMap[action.type];
    if (reducer) {
      return produce(state, (draft) => {
        reducer(draft, action);
      });
    }
    return state;
  }

  return reducer;
};

export default createReducer;

别看createReducer的代码多,因为是为了兼容上面两种写法,所以显得代码多了些。内部返回了一个reducer。由第二个参数mapOrBuilderCallback,来决定如何获取actionsMap。然后根据action的type来确定最后使用reducer的哪个分支actionsMap[action.type]。内部通过immer的produce方法实现了immutable的数据保证。

  • createSlice
import createAction from "./createAction";
import createReducer from "./createReducer";
const createSlice = (options) => {
    const {name, initialState, reducers} = options;
    const actions = {}, newReducers = {};
    Object.keys(reducers).forEach((key) => {
        const type = `${name}/${key}`;
        actions[key] = createAction(type);
        newReducers[type] = reducers[key];
    })
    return {
        actions,
        reducer: createReducer(initialState, newReducers)
    }
}

export default createSlice;

这里内部主要调用了上述createAction和createReducer去生成对应的action和reducer。值得注意的是,传入createReducer的reducer需要重新构建,因为其对应的action是用命名空间加上原来的reducers配置的key生成的新的key。到此,rtk的一些基本函数实现就已经完成了,想要全面了解每个细节,我建议直接去读源码。

写在最后

Es ist ersichtlich, dass die Implementierung von rtx nichts Neues ist, aber seine Verwendungslogik bringt uns großen Komfort. Im nächsten Artikel werden einige asynchrone Logik- und RTK-Abfragen weiterhin eingehend untersucht, also bleiben Sie dran, danke.

ähnliche Links

Offizielle Website des Redux-Toolkits

Ich denke du magst

Origin juejin.im/post/7116447560163852318
Empfohlen
Rangfolge