5

问题(tl;博士)

我们如何使用redux-toolkitcreateSlice创建一个自定义的 redux-orm reducer

有没有比这个问题中提供的尝试更简单、推荐、更优雅或只是其他解决方案?

细节

自定义 redux-orm reducer的示例如下(简化):

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;
}

可以通过redux-toolkitcreateSlice的功能来简化 reducer (基于 redux-toolkit usage-guide):

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

但是,在 redux-orm reducer 的开始我们需要创建一个 session

const session = orm.session(dbState);

然后我们使用我们的 redux-orm reducer 魔法,最后我们需要返回状态

return session.state;

所以我们错过了添加此功能的类似方法beforeEachReducerafterEachReducer方法。createSlice

解决方案(尝试)

我们创建了一个withSession创建会话并返回新状态的高阶函数。

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

我们需要将每个 reducer 逻辑包装在 this 中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

4

3 回答 3

4

我遇到了这个问题,希望将redux-toolkitredux-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;

redux-toolkit 切片作为类的静态属性创建,然后以类似于 Ducks (ORMDucks??) 的方式导出模型及其操作。

唯一要做的其他修改是为 redux-orm 的 reducer 定义一个自定义更新器:

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 很好地说明了 redux-toolkit 的一些功能没有被使用,因为 redux-orm 正在管理状态。对我来说,使用这种方法的两个最大好处是不必与一大群动作创作者争吵,也不必与糟糕的switch 陈述作斗争:D。
  • 我正在使用第 3 阶段的类字段和静态类功能提案。(参见 https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)。为了使这个 ES6 兼容,您可以轻松地重构模型类以使用当前语法(即Book.modelName = 'Book';)定义其静态道具。
  • 如果您决定将上述模型与未定义 a 的模型混合使用slice,则需要createReducer稍微调整更新程序中的逻辑。

对于真实世界的示例,请在此处查看我如何在我的项目中使用该模型: https ://github.com/vallerance/react-orcus/blob/70a389000b6cb4a00793b723a25cac52f6da519b/src/redux/models/OrcusApp.js 。该项目仍处于早期阶段。我心中最大的问题是这种方法的扩展性如何;但是,我乐观地认为,随着我的项目成熟,它将继续提供许多好处。

于 2020-02-28T05:02:16.300 回答
4

这对我来说是一个有趣的问题,因为我创建了 Redux Toolkit ,并且在我的“Practical Redux”教程系列中写了大量关于使用 Redux-ORM 的文章。

在我的脑海中,我不得不说你的withSession()包装看起来是目前最好的方法。

同时,我不确定同时使用 Redux-ORMcreateSlice()是否真的会给你带来很多好处。您没有在内部使用 Immer 的不可变更新功能,因为 Redux-ORM 正在处理模型内的更新。在这种情况下,唯一真正的好处是生成动作创建者和动作类型。

您最好createAction()单独调用,并在 switch 语句中使用原始的 reducer 表单和生成的操作类型:

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已经非常复杂。在这里添加更多复杂性将是不好的。

于 2020-01-07T16:40:30.523 回答
0

尝试使用normalized-reducer。它是一个高阶归约器,它采用描述关系的模式,并返回一个归约器、动作和根据关系写入/读取的选择器。

它还可以与 Normalizr 和 Redux Toolkit 轻松集成。

于 2020-04-22T17:53:54.767 回答