redux-toolkit 翻译系列

2,272 阅读4分钟

redux-toolkit api学习

createEntityAdapter

官方描述: A function that generates a set of prebuilt reducers and selectors for performing CRUD operations on a normalized state structure containing instances of a particular type of data object. 顺便需要了解一下什么是normalized state structure

在createAsyncThunk方法调用时,经常会看见搭配这个方法。用于格式化数据(normalized 标准化),会讲实例型的json数据格式化成扁平的数据结构,常用嵌套深的json数据标准化 参考normalizr库

例如从一篇博客数据接口中获取到的数据格式大致如下

[
  {
    "id": "123",    - 文章id
    "author": {     - 作者信息
      "id": "1",      - 用户id
      "name": "小明"   - 用户名称
    },
    "title": "文章",  - 文章标题
    "comments": [    - 评论部分
      {
        "id": "324",  - 评论id
        "commenter": { - 评论用户信息
          "id": "2",   - 用户id
          "name": "小王" - 用户名称
        }
      },
      {
        "id": "325",  - 评论id
        "commenter": { - 评论用户信息
          "id": "1",   - 用户id
          "name": "小明" - 用户名称
        }
      },
    ]
  }
]

上面的数据结构中其实有很多相同的数据类型比如 文章作者的信息 与 评论用户的信息 都是包含了用户的id和姓名,这些相同的数据类型其实都可以放在一个数组中,通过唯一id编号, 这样还有一个好处,比如用户评论中,其实小明的个人在作者信息与评论信息中是共享的,所以标准化后可以减少同样数据的重复,减少数据体积(0.0哈哈单个属性也许不明显,但是想想头像图片呢,只需要缓存一次即可)

格式化结果

{
  result: "123",
  entities: {
    "articles": {
      "123": {
        id: "123",
        author: "1",
        title: "My awesome blog post",
        comments: [ "324", "325" ]
      }
    },
    "users": {
      "1": { "id": "1", "name": "小明" },
      "2": { "id": "2", "name": "小王" }
    },
    "comments": {
      "324": { id: "324", "commenter": "2" },
      "325": { id: "325", "commenter": "1" },
    }
  }
}

使用教程:

  1. 创建

createEntityAdapter这个方法可以接受一个带有两个可选属性的对象,两个key值分别是selectIdsortComparer

  • selectId : 传递一个函数,这个函数的参数是一个具体对象,从这个对象中选取一个id作为返回值, 如果不提供这个属性,函数默认会返回对象属性上的id,例如 entity => entity.id ,如果这个对象的唯一id不直接存在于这个对象属性上而是在对象子属性上,比如 entity.item.id,则必须提供一个函数返回一个选中的id。
  • sortComparer: 传递一个函数,接受两个参数,根据返回对所有的对象进行指定排序,与数据的sort 差不多
const booksAdapter = createEntityAdapter<Book>({
  // Assume IDs are stored in a field other than `book.id`
  selectId: book => book.bookId,
  // Keep the "all IDs" array sorted based on book titles
  sortComparer: (a, b) => a.title.localeCompare(b.title)
})
  1. enity adapter --- 生成后的对象 注意是一个 js object而非 class EntityAdapter 对象上包含了很多属性,分别是:

    1. CURD functions 增删改

    • addOne 添加一个enity
    • addMany 添加多个enity,传递一个数组,数组中的元素要符合enity的对象结构
    • setAll 传递一个enity数组,数组中的元素要符合enity的对象结构,将当前已经存在的enity内的所有数据替换成传递的
    • removeOne 删除一个
    • removeMany 删除多个
    • removeAll 删除所有
    • updateOne 更新一个
    • updateMany 更新多个
    • upsertOne 接受一个entity 如果存在则更新 不存在则添加
    • upsertMany 接受多个entity 如果存在则更新 不存在则添加
    1. getInitialState 初始化state 用于放在redux中,具体实现暂时未研究

    • getInitialState 获取enity, 并且一个接受一个可选对象参数这个属性将添加到返回值上
    1. Selector Functions 相当于查

    • getSelectors()
      • selectIds 获取ID数组
      • selectEntities 获取实例对象,类似表
      • selectAll 返回一个所有实例对象的数组 但是属性都是用id展示
      • selectTotal 返回实例总数
      • selectById 通过id查询实例
// 两种使用方式 其实就是指定了不同的作用域 一个是全局这种类型对象的一个是某一具体对象
const store = configureStore({
  reducer: {
    books: booksReducer
  }
})

const simpleSelectors = booksAdapter.getSelectors()
const globalizedSelectors = booksAdapter.getSelectors(state => state.books)

// Need to manually pass the correct entity state object in to this selector
const bookIds = simpleSelectors.selectIds(store.getState().books)

// This selector already knows how to find the books entity state
const allBooks = globalizedSelectors.selectAll(store.getState())
  1. Note
  • 多次调用 updateMany ,对相同ID最后一次更新的结果将覆盖前面的结果
  • updateOneupdateMany这两个函数, 修改一个已存在实例的ID 更换为 第二个实例中匹配的ID 遇到第一个完全匹配的ID后就会直接替换
import {createEntityAdapter, createSlice, configureStore} from '@reduxjs/toolkit';

// 创建一个book对象 通过title排序
const booksAdapter = createEntityAdapter({
  sortComparer: (a, b) => a.title.localeCompare(b.title)
})

const booksSlice = createSlice({
  name: 'books',
  //给book对象绑定一个属性
  initialState: booksAdapter.getInitialState({
    loading: 'idle'
  }),
  reducers: {
    //添加一本
    bookAdded: booksAdapter.addOne,
    //修改加载庄状态
    booksLoading(state, action) {
      if (state.loading === 'idle') {
        state.loading = 'pending'
      }
    },
    //book数据置换
    booksReceived(state, action) {
      if (state.loading === 'pending') {
        booksAdapter.setAll(state, action.payload)
        state.loading = 'idle'
      }
    },
    //更新其中一本书的信息
    bookUpdated: booksAdapter.updateOne
  }
})

const {
  bookAdded,
  booksLoading,
  booksReceived,
  bookUpdated
} = booksSlice.actions

const store = configureStore({
  reducer: {
    books: booksSlice.reducer
  }
})

// 检查初始状态
console.log(store.getState().books)
// {ids: [], entities: {}, loading: 'idle' }

// 返回一个selector函数作用域 方便后面调用
const booksSelectors = booksAdapter.getSelectors(state => state.books)

store.dispatch(bookAdded({ id: 'a', title: 'First' }))
console.log(store.getState().books)
// {ids: ["a"], entities: {a: {id: "a", title: "First"}}, loading: 'idle' }


store.dispatch(
  booksReceived([
    { id: 'b', title: 'Book 3' },
    { id: 'c', title: 'Book 2' }
  ])
)

console.log(booksSelectors.selectIds(store.getState()))
// "a" was removed due to the `setAll()` call
// Since they're sorted by title, "Book 2" comes before "Book 3"
// ["c", "b"]

console.log(booksSelectors.selectAll(store.getState()))
// All book entries in sorted order
// [{id: "c", title: "Book 2"}, {id: "b", title: "Book 3"}]