数据标准化
数据标准化
大多数应用程序通常会处理深度嵌套或关系型的数据。数据规范化的目标是有效地组织你状态下的数据。这通常是通过将集合存储为具有
手动标准化
归一化数据不需要任何特殊的库。下面是一个基本的例子,说明你如何对
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
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
{ 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;