В данной статье приведены несколько вариантов переиспользования кода в Redux-toolkit при создании слайсов, позволяющие сделать работу с ним более гибкой и удобной.
Для адептов других стейт менеджеров
Данная статья, еще один шанс для Вас показать насколько другой стейт-менеджер лучше чем redux, поэтому поделитесь, пожалуйста, кодом, решающим аналогичную задачу на другом стейт-менеджере. И, возможно, ваш пример убедит других разработчиков сделать правильное решение.
Вариант 1 - Полное дублирование слайсов
Самый простой вариант - создать функцию, создающую одинаковые слайсы, но с разными, уникальными названиями. Такой вариант подходит, если необходимо создать несколько идентичных по поведению слайсов, но со своими экземплярами стейта и экшенов.
const createPageSlice = (name: string) => {
    const initialState = ...
    return createSlice({name, initialState, reducers: {...}})
}
const rootReducer = combineReducers({
    page1: createPageslice('page1').reducer,
    page2: createPageslice('page2').reducer,
})
Задача со звездочкой для тех, кто хочет попрактиковаться
Создать генератор слайсов, который может быть использован для быстрого создания списка с фильтром по поиску.
Создайте генератор слайсов, который примет дженериком тип массива , состоящего из объектов произвольного типа, и пропсом название поля из этого объекта по которому будет производиться фильтрация.
В итоге должен получится слайс с полями: список, отфильтрованный список, поиск. И с экшенами setInput, clearInput, setArray 
Вариант 2 - Расширения функциональности слайса
Но что если у нас не совсем одинаковые слайсы, а только одинаковое ядро и логика вокруг него? Для расширения функциональности слайса, при создании слайса можно передать дополнительные экшены.
import {
    SliceCaseReducers,
    ValidateSliceCaseReducers,
    createSlice,
    PayloadAction,
} from "@reduxjs/toolkit";
// Базовый тип стейта для слайса
export type PageBaseStateSchema = {
    innerStateField: ...;
};
const createPageModel = <
    // стейт слайса может быть любой, но должен расширять базовый тип 
    State extends PageBaseStateSchema,
    // Дженерик для того чтобы тайпскрипт корректно подхватывал 
    //тип экшенов в готовом слайсе
    CaseReducers extends SliceCaseReducers<State>,
> = ({name, initialState, additionalReducer}:{
    name: string;
    initialState: State;
    additionalCaseReducers: ValidateSliceCaseReducers<State, CaseReducers>;
}) => {
    const slice = createSlice({
        name: props.name,
        initialState: props.initialState,
        reducers: {
            // Базовые экшены, которые будут в каждом экземпляре слайса
            innerAction: (
                state,
                action: PayloadAction<PageBaseStateSchema["innerStateField"]>
            ) => {
                state.innerStateField = action.payload;
            },
            ...props.additionalCaseReducers,
        },
    });
    return slice
};
Создание экземпляра слайса:
type ExtendedStateSchema = BaseStateSchema & {
    extendedStateField: ...;
};
const initialState: ExtendedStateSchema = {
    innerStateField: ...
    extendedStateField: ...,
};
const slice = createExtendedSlice({
    name: 'name',
    initialState: initialState,
    additionalReducer: {
        // Дополнительные экшены
        outerAction: (state, action: PayloadAction<...>) => {
            state.extendedStateField = ...
            state.innerStateField = ...
        },
    },
});
Минусы:
Для расширения поведение базовых экшенов, необходимо в типизации пропсов функции, создающей слайс, явно прописывать поля для дополнительного поведения для каждого из внутренних экшенов.
Невозможность простой комбинации нескольких подобных переиспользуемых слайсов
Вариант 3 - Переиспользование логики в других слайсах
В данном варианте, мы будем решать обратную задачу - как мы можем расширить любой слайс любым количеством переиспользуемых частей, например, фильтром или любым другим функционалом? Не писать же в каждом месте, где есть фильтры, в слайсах одну и ту же логику.
В качестве примера создадим функцию, позволяющую легко добавить поле ввода с валидацией в любой слайс.
Реализуем два варианта добавления в слайс:
Добавляем в поле
reducersпри создании слайсаДобавляем в поле
extraReducersпри создании слайса
Вариант с добавлением в extraReducers может пригодиться, если хочется чтобы добавленные экшены были не в общем объекте slice.actions , а отдельным объектом.
Disclaimer
Для примера специально был выбрано создание поля ввода, так что предлагаю обсудить в комментариях плюсы и минусы работы с формами в redux.
// Создадим тип для поля ввода
export type InputStateSchema = {
    value: string;
    validInfo?: InputValidateInfo;
};
function createSingleInput<
    //Тип будущего стейта необходим, чтобы можно было расширять поведение 
    State extends AnyObject, // Кастомный тип для объекта любого вида
    // поле в будущем стейте, которое имеет необходимый тип
    //PickFieldsWithType Кастомный тип, выбирает из объекта только поля с заданным типом
    FieldKey extends keyof PickFieldsWithType< 
        State,
        InputStateSchema
    > = keyof PicKFieldsWithType<State, InputStateSchema>,
>({
    fieldName,
    validateFn,
    baseName,
}: {
    // базовое название, для генерации уникального экшена (Вариант 2)
    //(лучше использовать название слайста в который будет добавлять)
    baseName: string;
    fieldName: FieldKey;
    // функция для валидации инпута
    validateFn?: (val: string) => InputValidateInfo;
}) {
    // Базовое поведения стейта
    const setValue = (state: Draft<State>, value: string) => {
        const inputInfo = state[
            fieldName as keyof typeof state
        ] as IInputStateSchema;
        if (validateFn) {
            inputInfo.validInfo = validateFn(value);
        }
        inputInfo.value = value;
    };
    //функция для инициализации стейта
    const getInitialState = (initValue?: string): InputStateSchema => {
        return {
            value: initValue || "",
            validInfo: validateFn?.(initValue || ""),
        };
    };
    //Вариант №1 для добавления в reducers в slice
    const createSetValueCaseReducer =
        (
          // Возможность при создании расширить поведение экшена
            additionalCaseReducer?: CaseReducer<State, PayloadAction<string>>
        ): CaseReducer<State, PayloadAction<string>> =>
        (state, action) => {
            setValue(state, action.payload);
            additionalCaseReducer?.(state, action);
        };
    //Вариант №2 для добавления в extraReducers в slice
    //Создаем экшен
    const setValueAction = createAction<string>(
        `${baseName.toString()}/set/${fieldName.toString()}`
    );
    const addToExtraReducer = (
        //builder из extraReducer
        builder: ActionReducerMapBuilder<State>,
        // Возможность при создании расширить поведение экшена
        additionalCaseReducer?: CaseReducer<State, PayloadAction<string>>
    ) => {
        builder.addCase(setValueAction, createSetValueCaseReducer(additionalCaseReducer));
    };
    return {
        //Вариант №1 добавление в reducers в slice
        createSetValueCaseReducer,
        //Вариант №2 добавление в extraReducers в slice
        actions: {setValue: setValueAction},
        addToExtraReducer,
        //Базовое поведение
        setValue,
        getInitialState,
    };
}
Создание экземпляра слайса:
type PageWithInputStateSchema = {
    // Поле с которым будет работать наша функция
    input: InputStateSchema;
    otherState: ...;
};
const pageWithInputName = "pageWithInput"
//Создаем инпут
const inputForPage = createSingleInput<PageWithInputStateSchema>({
    fieldName: "input",
    baseName: pageWithInputName,
    validateFn: validateLength(3),
});
const initialState: PageWithInputStateSchema = {
    //Получаем стейт для нашего инпута
    input: inputForPage.getInitialState('initial value'),
    otherState: ...,
};
const pageWithInputSlice = createSlice({
    name: pageWithInputName,
    initialState: initialState,
    reducers: {
        otherAction: (state) => {
            //Можно работать с инпутом внутри любого экшена
            inputForPage.setValue(state, "");
        },
        //Вариант №1 добавление в reducers в slice
        setInput: inputForPage.createSetValueCaseReducer((state, action) => {
            //Расширение экшена, с возможность описывать сайд эффекты на стейт
            state.otherState = action.payload.length;
        }),
    },
    extraReducers: (builder) => {
        //Вариант №2 добавление в extraReducers в slice
        inputForPage.addToExtraReducer(builder, (state, action) => {
            //Расширение экшена, с возможность описывать сайд эффекты на стейт
            state.otherState = action.payload.length;
        });
    },
});
Данный вариант лишен недостатков варианта №2 и позволяет гибко добавлять и комбинировать переиспользуемые куски логики в любом слайсе и расширять их поведение. Благодаря этому по всему проекту будет меньше дублирования кода, и единообразная логика, с возможностью кастомизации.
Задача со звездочкой для тех, кто хочет попрактиковаться
Реализовать тип
PicKFieldsWithType<Obj, Type>, которыйвыбирает из объекта только поля с заданным типомСоздайте на основе варианта №3 генератор, который может создать переиспользуемую логику сразу для нескольких полей ввода, с дополнительным экшеном, который полностью очищает данные поля.
Заключение
Конечно, эти варианты не исключают, а скорее дополняют друг друга и могут использоваться совместно.
Надеюсь, что данная статья была полезна, Вы нашли для себя что-то новое и теперь можете сделать ваш код на redux более гибким и удобным для переиспользования.
Буду рад услышать критику и предложения в комментариях. Спасибо за внимание!
Темы для будущих статей:
Приятные мелочи для удобной работы с redux-toolkit.
Удобная работа с asyncThunk.
ListenerMiddleware и asyncThunk где связь?
Модульность, скрытие и изоляция в redux.
Redux-toolkit и переиспользование кода [2].
Redux и его динамические возможности.
          
 
Hellbot
Каждая такая статья показывает как правильно нарезать слайсы, помогите найти объяснение почему их надо нарезать?
hdbdbeben Автор
А что значит нарезать? Создавать отдельные слайсы, а не держать все в одном?
Hellbot
Зачем это может потребоваться?
Да и в целом, зачем создавать слайсы?