从0到1了解 Redux-Toolkit 实现原理

在之前的文章,从0到1了解 Redux 实现原理介绍了 Redux 的使用,最近公司要从 Redux 迁移到 Redux-Toolkit,因此借机了解一波~

背景

Redux-Toolkit 是 基于 Redux 的二次封装,开箱即用的 Redux 工具,比 Redux 更加简单方便。

🚧 Why to use Redux-toolkit?

  1. Configuring a Redux store is too complicated

  2. I have to add a lot of packages to get Redux to do anything useful

  3. Redux requires too much boilerplate code

Toolkit 使用

Redux 该有的概念的 Toolkit 其实都是有拥有的,只是他们使用的方式不同,例如 reducer/actions 等等在 Toolkit 中都是随处可见的。

configureStore

创建 store,代码内部还是调用的 Redux 的 createStore 方法

js 复制代码
const store = configureStore({
    reducer: {
        counter: counterReducer,
        user: userReducer,
    },
});

createAction + createReducer

  • createAction -- 创建 Redux 中的 action 创建函数

    js 复制代码
    function createAction(type, prepareAction?)

    redux 中 action 的创建以及使用

    js 复制代码
    const updateName = (name: string) => ({ type: 'user/UPDATE_NAME', name });
    const updateAge = (age: number) => ({ type: 'user/UPDATE_AGE', age });

    Toolkit 中 action 的创建以及使用

    js 复制代码
    // 第一种
    const updateName = createAction < { name: string } > 'user/UPDATE_NAME';
    const updateAge = createAction < { age: number } > 'user/UPDATE_AGE';
    updateName(); // { type: 'user/UPDATE_NAME', payload: undefined }
    updateName({ name: 'FBB' }); // { type: 'user/UPDATE_NAME', payload: { name: 'FBB' } }
    updateAge({ age: 18 });
    
    // 第二种
    const updateName = createAction('user/UPDATE_NAME', (name: string) => ({
        payload: {
            name,
        },
    }));
    const updateAge = createAction('user/UPDATE_AGE', (age: number) => ({
        payload: {
            age,
        },
    }));
    updateName('FBB');
    updateAge(18);
  • createReducer -- 创建 Redux reducer 的函数

    💡 createReducer 使用 Immer 库,可以在 reducer 中直接对状态进行修改,而不需要手动编写不可变性的逻辑

    Redux 中 reducer 的创建

    js 复制代码
    export const userReducer = (
        state = initialUserState,
        action: { type: string, [propName: string]: any },
    ) => {
        switch (action.type) {
            case 'user/UPDATE_NAME':
                return { ...state, name: action.name };
            case 'user/UPDATE_AGE':
                return { ...state, age: action.age };
            default:
                return state;
        }
    };

    Toolkit 中 reducer 的创建

    js 复制代码
    export const userReducer = createReducer(initialUserState, (builder) => {
        builder.addCase(updateAge, (state, action) => {
            state.age = action.payload.age;
        });
        builder.addCase(updateName, (state, action) => {
            state.name = action.payload.name;
        });
    });

    toolkit 提供的 createAction 和 createReducer 能够帮我们简化 Redux 中一些模版语法,但是整体的使用还是差不多的,我们依旧需要 action 文件和 reducer 文件,做了改善但是不多。

redux demo toolkit createReducer demo

createSlice

接受初始状态、reducer 函数对象和 slice name 的函数,并自动生成与 reducer 和 state 对应的动作创建者和动作类型

ts 复制代码
const userSlice = createSlice({
    name: 'user',
    initialState: {
        age: 22,
        name: 'shuangxu',
    },
    reducers: {
        updateName: (state, action: PayloadAction<string>) => {
            state.name = action.payload;
        },
        updateAge: (state, action: PayloadAction<number>) => {
            state.age = action.payload;
        },
    },
});

使用 createSlice 创建一个分片,每一个分片代表某一个业务的数据状态处理。在其中可以完成 action 和 reducer 的创建。

ts 复制代码
export const userSliceName = userSlice.name;
export const { updateAge, updateName } = userSlice.actions;
export const userReducer = userSlice.reducer;
const store = configureStore({
    reducer: {
        [counterSliceName]: counterReducer,
        [userSliceName]: userReducer,
    },
});

toolkit slice demo

在 Toolkit 中直接使用 createSlice 更加方便,能够直接导出 reducer 和 action,直接在一个方法中能够获取到对应内容不在需要多处定义。

源码实现

configureStore

接受一个含有 reducer 的对象作为参数,内部调用 redux 的 createStore 创建出 store

ts 复制代码
import { combineReducers, createStore } from 'redux';
export function configureStore({ reducer }: any) {
    const rootReducer = combineReducers(reducer);
    const store = createStore(rootReducer);
    return store;
}

createAction

ts 复制代码
const updateName = createAction<string>('user/UPDATE_NAME');
const updateName = createAction('user/UPDATE_NAME', (name: string) => ({
    payload: {
        name,
    },
}));
updateName('FBB');

通过上面的示例,能够分析出来 createAction 返回的是一个函数,接受第一个参数 type 返回{ type: 'user/UPDATE_NAME', payload: undefined };对于具体的 payload 值需要传入第二个参数来改变

ts 复制代码
export const createAction = (type: string, preAction?: Function) => {
    function actionCreator(...args: any[]) {
        if (!preAction)
        return {
            type,
            payload: args[0],
        };
        const prepared = preAction(...args);
        if (!prepared) {
            throw new Error('prepareAction did not return an object');
        }
        return {
            type,
            payload: prepared.payload,
        };
    }
    actionCreator.type = type;
    return actionCreator;
};

createReducer

ts 复制代码
export const userReducer = createReducer(initialUserState, (builder) => {
    builder
        .addCase(updateAge, (state, action) => {
            state.age = action.payload.age;
        })
        .addCase(updateName, (state, action) => {
            state.name = action.payload.name;
        });
});

每一个 reducer 都是一个函数(state = initialState, action) => {},因此 createReducer 返回值为函数

通过一个 createReducer 函数,内部还需要知道每一个 action 对应的操作

ts 复制代码
import { produce as createNextState } from 'immer';
export const createReducer = (
    initialState: any,
    builderCallback: (builder: any) => void,
) => {
    const actionsMap = executeReducerBuilderCallback(builderCallback);
    return function reducer(state = initialState, action: any) {
        const caseReducer = actionsMap[action.type];
        if (!caseReducer) return state;
        return createNextState(state, (draft: any) => caseReducer(draft, action));
    };
};

// 通过 createReducer 的第二个参数,构建出 action 对应的操作方法
export const executeReducerBuilderCallback = (
    builderCallback: (builder: any) => void,
) => {
    const actionsMap: any = {};
    const builder = {
        addCase(typeOrActionCreator: any, reducer: any) {
            const type =
                typeof typeOrActionCreator === 'string'
                    ? typeOrActionCreator
                    : typeOrActionCreator.type;
            actionsMap[type] = reducer;
            return builder;
        },
    };
    builderCallback(builder);
    return actionsMap;
};

createSlice

ts 复制代码
const counterSlice = createSlice({
    name: 'counter',
    initialState: {
        count: 1,
    },
    reducers: {
        increment: (state: any) => {
            state.count += 1;
        },
        decrement: (state: any) => {
            state.count -= 1;
        },
    },
});
const counterSliceName = counterSlice.name;
const { increment, decrement } = counterSlice.actions;
const counterReducer = counterSlice.reducer;

createSlice 返回的是一个对象{ name, actions, reducer },接受{ name, initialState, reducers }三个参数。通过 reducers 中相关参数得到对应的 actions 和 reducer。

在 createSlice 中主要还是靠 createAction 和 createReducer 方法。通过 name 和 reducers 的每一个属性拼接成为 action.type,调用 createReducer 遍历 reducers 的属性添加 case

ts 复制代码
import { createAction } from './createAction';
import { createReducer } from './createReducer';
export default function createSlice({ name, initialState, reducers }: any) {
    const reducerNames = Object.keys(reducers);
    const actionCreators: any = {};
    const sliceCaseReducersByType: any = {};
    reducerNames.forEach((reducerName) => {
        const type = `${name}/${reducerName}`;
        const reducerWithPrepare = reducers[reducerName];
        actionCreators[reducerName] = createAction(type);
        sliceCaseReducersByType[type] = reducerWithPrepare;
    });

    function buildReducer() {
        return createReducer(initialState, (builder) => {
            for (let key in sliceCaseReducersByType) {
                builder.addCase(key, sliceCaseReducersByType[key]);
            }
        });
    }

    return {
        name,
        actions: actionCreators,
        reducer: (state: any, action: any) => {
            const _reducer = buildReducer();
            return _reducer(state, action);
        },
    };
}

总结

本文讲了 toolkit 的使用和源码实现,toolkit 是基于 redux 实现的,相对于 redux 来说少了很多的模版语言更加的方便快捷。从 redux 升级到 redux-toolkit 也是没有破坏性变更的。

相关推荐
萌萌哒草头将军6 小时前
⚡⚡⚡尤雨溪宣布开发 Vite Devtools,这两个很哇塞 🚀 Vite 的插件,你一定要知道!
前端·vue.js·vite
_一条咸鱼_6 小时前
揭秘 Android TextInputLayout:从源码深度剖析其使用原理
android·java·面试
_一条咸鱼_6 小时前
揭秘!Android VideoView 使用原理大起底
android·java·面试
_一条咸鱼_6 小时前
深度揭秘!Android TextView 使用原理全解析
android·java·面试
小彭努力中6 小时前
7.Three.js 中 CubeCamera详解与实战示例
开发语言·前端·javascript·vue.js·ecmascript
_一条咸鱼_6 小时前
深度剖析:Android Canvas 使用原理全揭秘
android·java·面试
_一条咸鱼_6 小时前
深度剖析!Android TextureView 使用原理全揭秘
android·java·面试
_一条咸鱼_6 小时前
揭秘!Android CheckBox 使用原理全解析
android·java·面试