【问题标题】:Rewrite redux-orm reducer with redux-toolkit使用 redux-toolkit 重写 redux-orm reducer
【发布时间】:2020-04-24 10:45:48
【问题描述】:

问题(tl;dr)

我们如何用redux-toolkitcreateSlice 创建一个custom redux-orm reducer

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

详情

custom 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的开始我们需要创建一个会话

const session = orm.session(dbState);

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

return session.state;

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

解决方案(尝试)

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

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

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

【问题讨论】:

    标签: redux redux-orm redux-toolkit


    【解决方案1】:

    这对我来说是一个有趣的问题,因为I created Redux Toolkit,我在my "Practical Redux" tutorial series 中写了大量关于使用 Redux-ORM 的文章。

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

    同时,我不确定同时使用 Redux-ORM 和 createSlice() 是否真的会给你带来很多好处。您没有在内部使用 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% 的用例,createSlice 的 TS 类型已经非常复杂。在这里增加任何复杂性都是不好的。

    【讨论】:

      【解决方案2】:

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

      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';)。
      • 如果您决定将上述模型与未定义的模型混合使用 slice,那么您需要调整 createReducer 更新程序中的逻辑 略。

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

      【讨论】:

      • 嗯,真漂亮!
      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 2020-09-13
        • 1970-01-01
        • 2021-04-23
        • 2021-05-05
        • 2021-04-15
        • 1970-01-01
        • 2021-01-24
        • 2020-11-08
        • 2021-09-21
        相关资源
        最近更新 更多