数据标准化

数据标准化

大多数应用程序通常会处理深度嵌套或关系型的数据。数据规范化的目标是有效地组织你状态下的数据。这通常是通过将集合存储为具有 id 的键的对象,同时存储这些 id 的排序数组来实现的。

手动标准化

归一化数据不需要任何特殊的库。下面是一个基本的例子,说明你如何对 fetchAll API 请求的响应进行归一化,该请求返回的数据是以{ users: [{id: 1, first_name: ’normalized’, last_name: ‘person’}] },使用一些手写的逻辑。

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import userAPI from "./userAPI";

export const fetchUsers = createAsyncThunk("users/fetchAll", async () => {
  const response = await userAPI.fetchAll();
  return response.data;
});

export const slice = createSlice({
  name: "users",
  initialState: {
    ids: [],
    entities: {},
  },
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      // reduce the collection by the id property into a shape of { 1: { ...user }}
      const byId = action.payload.users.reduce((byId, user) => {
        byId[user.id] = user;
        return byId;
      }, {});
      state.entities = byId;
      state.ids = Object.keys(byId);
    });
  },
});

虽然我们有能力编写这段代码,但它确实会变得很重复,尤其是当你在处理多种类型的数据时。此外,这个例子只处理将条目加载到状态中,而不是更新它们。

normalizr

normalizr 是现有的一个流行的数据归一化库。你可以在没有 Redux 的情况下单独使用它,但它与 Redux 一起使用非常普遍。典型的用法是格式化来自 API 响应的集合,然后在你的 reducers 中处理它们。

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { normalize, schema } from "normalizr";

import userAPI from "./userAPI";

const userEntity = new schema.Entity("users");

export const fetchUsers = createAsyncThunk("users/fetchAll", async () => {
  const response = await userAPI.fetchAll();
  // Normalize the data before passing it to our reducer
  const normalized = normalize(response.data, [userEntity]);
  return normalized.entities;
});

export const slice = createSlice({
  name: "users",
  initialState: {
    ids: [],
    entities: {},
  },
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      state.entities = action.payload.users;
      state.ids = Object.keys(action.payload.users);
    });
  },
});

与手写版本一样,这并不处理向状态中添加额外的条目,或者稍后更新它们–它只是将收到的所有条目加载进来。

createEntityAdapter

Redux Toolkit 的 createEntityAdapter API 提供了一种标准化的方式,通过将一个集合放入 { ids: [], entities: {} }. 除了这个预定义的状态形状,它还会生成一组知道如何处理数据的还原函数和选择器。

import {
  createSlice,
  createAsyncThunk,
  createEntityAdapter,
} from "@reduxjs/toolkit";
import userAPI from "./userAPI";

export const fetchUsers = createAsyncThunk("users/fetchAll", async () => {
  const response = await userAPI.fetchAll();
  // In this case, `response.data` would be:
  // [{id: 1, first_name: 'Example', last_name: 'User'}]
  return response.data;
});

export const updateUser = createAsyncThunk("users/updateOne", async (arg) => {
  const response = await userAPI.updateUser(arg);
  // In this case, `response.data` would be:
  // { id: 1, first_name: 'Example', last_name: 'UpdatedLastName'}
  return response.data;
});

export const usersAdapter = createEntityAdapter();

// By default, `createEntityAdapter` gives you `{ ids: [], entities: {} }`.
// If you want to track 'loading' or other keys, you would initialize them here:
// `getInitialState({ loading: false, activeRequestId: null })`
const initialState = usersAdapter.getInitialState();

export const slice = createSlice({
  name: "users",
  initialState,
  reducers: {
    removeUser: usersAdapter.removeOne,
  },
  extraReducers: (builder) => {
    builder.addCase(fetchUsers.fulfilled, usersAdapter.upsertMany);
    builder.addCase(updateUser.fulfilled, (state, { payload }) => {
      const { id, ...changes } = payload;
      usersAdapter.updateOne(state, { id, changes });
    });
  },
});

const reducer = slice.reducer;
export default reducer;

export const { removeUser } = slice.actions;
上一页
下一页