Перепишите редуктор-редуктор с помощью редук-инструментария - PullRequest
4 голосов
/ 07 января 2020

Issue (tl; dr)

Как мы можем создать пользовательский редуктор-редуктор с redux-toolkit 's createSlice ?

Есть ли более простое, рекомендованное, более элегантное или просто другое решение, чем попытка, представленная в этом вопросе?

Подробности

Пример пользовательский редуктор-редуктор выглядит следующим образом (упрощенно):

function ormReducer(dbState, action) {
    const session = orm.session(dbState);
    const { Book } = session;

    switch (action.type) {
    case 'CREATE_BOOK':
        Book.create(action.payload);
        break;
    case 'REMOVE_AUTHOR_FROM_BOOK':
        Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
        break;
    case 'ASSIGN_PUBLISHER':
        Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
        break;
    }

    return session.state;
}

Можно упростить редукторы с помощью функции createSlice redux -toolkit (основано на инструментарии redux-toolkit Руководство по использованию ):

const ormSlice = createSlice({
  name: 'orm',
  initialState: [],
  reducers: {
    createBook(state, action) {},
    removeAuthorFromBook(state, action) {},
    assignPublisher(state, action) {}
  }
})
const { actions, reducer } = ormSlice
export const { createBook, removeAuthorsFromBook, assignPublisher } = actions
export default reducer

Однако в начале редуктора orx-редуктора нам необходимо создать сеанс

const session = orm.session(dbState);

затем мы выполняем магию редуктора-редуктора c, и в конце нам нужно вернуть состояние

return session.state;

Таким образом, мы пропускаем что-то вроде beforeEachReducer и afterEachReducer методы в createSlice для добавления этой функциональности.

Решение (попытка)

Мы создали withSession функцию более высокого порядка, которая создает сеанс и d возвращает новое состояние.

const withSession = reducer => (state, action) => {
  const session = orm.session(state);
  reducer(session, action);
  return session.state;
}

Нам нужно обернуть каждую логику редуктора c в это withSession.

import { createSlice } from '@reduxjs/toolkit';
import orm from './models/orm'; // defined elsewhere
// also define or import withSession here

const ormSlice = createSlice({
  name: 'orm',
  initialState: orm.session().state, // we need to provide the initial state
  reducers: {
    createBook: withSession((session, action) => {
      session.Book.create(action.payload);
    }),
    removeAuthorFromBook: withSession((session, action) => {
      session.Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
    }),
    assignPublisher: withSession((session, action) => {
      session.Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
    }),
  }
})

const { actions, reducer } = ormSlice
export const { createBook, removeAuthorsFromBook, assignPublisher } = actions
export default reducer

Ответы [ 3 ]

1 голос
/ 28 февраля 2020

Я сталкивался с этим вопросом, пытаясь объединить преимущества redux-toolkit и redux-orm . Мне удалось найти решение, которым я до сих пор был очень доволен. Вот как выглядит моя модель redux-orm:

class Book extends Model {

    static modelName = 'Book';

    // Declare your related fields.
    static fields = {
        id: attr(), // non-relational field for any value; optional but highly recommended
        name: attr(),
        // foreign key field
        publisherId: fk({
            to: 'Publisher',
            as: 'publisher',
            relatedName: 'books',
        }),
        authors: many('Author', 'books'),
    };

    static slice = createSlice({
      name: 'BookSlice',
      // The "state" (Book) is coming from the redux-orm reducer, and so will
      // never be undefined; therefore, `initialState` is not needed.
      initialState: undefined,
      reducers: {
        createBook(Book, action) {
            Book.create(action.payload);
        },
        removeAuthorFromBook(Book, action) {
            Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
        },
        assignPublisher(Book, action) {
            Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
        }
      }
    });

    toString() {
        return `Book: ${this.name}`;
    }
    // Declare any static or instance methods you need.

}

export default Book;
export const { createBook, removeAuthorFromBook, assignPublisher } = Book.slice.actions;

Срез Reduce-toolkit создается как свойство stati c класса, а затем модель и ее действия экспортируются таким образом похож на Ducks (ORMDucks ??).

Единственное другое изменение, которое необходимо сделать, - это определить пользовательский модуль обновления для редуктора redux-orm:

const ormReducer = createReducer(orm, function (session, action) {
    session.sessionBoundModels.forEach(modelClass => {
        if (typeof modelClass.slice.reducer === 'function') {
            modelClass.slice.reducer(modelClass, action, session);
        }
    });
});

Более полный пример приведен здесь: https://gist.github.com/JoshuaCWebDeveloper/25a302ec891acb6c4992fe137736160f

Некоторые заметки

  • @ markerikson дает хорошее представление о том, что некоторые из функций комплекта инструментов редукса не используются, поскольку он используется для управления штат. Для меня два величайших преимущества использования этого метода не в том, чтобы спорить с целой кучей создателей действий, и не в том, чтобы бороться с ужасными switch утверждениями: D.
  • Я использую поля класса стадии 3 и stati c классные предложения. (См. https://babeljs.io/docs/en/babel-plugin-proposal-class-properties). Чтобы сделать этот ES6-совместимым, вы можете легко провести рефакторинг класса модели, чтобы определить его характеристики * stai c, используя текущий синтаксис (т. Е. Book.modelName = 'Book';).
  • Если вы решили смешивать модели, подобные приведенной выше, с моделями которые не определяют slice, тогда вам нужно немного настроить логи c в createReducer программе обновления.

Для примера из реального мира, посмотрите, как я использую Модель в моем проекте здесь: https://github.com/vallerance/react-orcus/blob/70a389000b6cb4a00793b723a25cac52f6da519b/src/redux/models/OrcusApp.js. Этот проект все еще находится на ранней стадии. Самый большой вопрос в моей голове - насколько хорошо этот метод будет масштабироваться; Тем не менее, я оптимистично c, что он будет продолжать приносить многочисленные выгоды по мере развития моего проекта.

1 голос
/ 07 января 2020

Это захватывающий вопрос для меня, потому что я создал Redux Toolkit и много писал об использовании Redux-ORM в моей серии практических руководств Redux .

Вне головы, я бы сказал, что ваша withSession() оболочка выглядит на данный момент лучшим подходом.

В то же время я не уверен, что совместное использование Redux-ORM и createSlice() действительно принесет вам много пользы. Вы не используете возможности неизменных обновлений Immer внутри, так как Redux-ORM обрабатывает обновления внутри моделей. Единственным реальным преимуществом в этом случае является создание создателей действий и типов действий.

Возможно, было бы лучше просто вызвать createAction() отдельно и использовать исходную форму редуктора с сгенерированными типами действий в операторе switch:

export const createBook = createAction("books/create");
export const removeAuthorFromBook = createAction("books/removeAuthor");
export const assignPublisher = createAction("books/assignPublisher");

export default function ormReducer(dbState, action) {
    const session = orm.session(dbState);
    const { Book } = session;

    switch (action.type) {
    case createBook.type:
        Book.create(action.payload);
        break;
    case removeAuthorFromBook.type:
        Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
        break;
    case assignPublisher.type:
        Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
        break;
    }

    return session.state;
}

Я вижу, что вы говорите о добавлении каких-то обработчиков «до / после», но это добавит слишком много сложности. RTK предназначен для использования в 80% случаев, а типы TS для createSlice уже невероятно сложны. Добавление здесь большей сложности было бы плохо.

0 голосов
/ 22 апреля 2020

Попробуйте использовать нормализованный-редуктор . Это редуктор высшего порядка, который берет схему, описывающую отношения, и возвращает редуктор, действие и селекторы, которые пишут / читают согласно отношениям.

Он также легко интегрируется с Normalizr и Redux Toolkit.

...