Vue+Rails项目:Jbuilder与Normalizr的数据扁平化方案求助
我来帮你梳理下问题,从前后端两个方面给出解决方案,毕竟你能同时修改两端,这会让问题解决起来更顺畅~
一、后端Jbuilder代码优化
首先先修正你代码里的一个小笔误:json.trip_details hike.trip_details里的hike应该是trip,不然会抛出未定义错误。
接下来,针对归一化需求,你有两种后端结构可选:
选项1:保持嵌套结构(适合前端按关联关系遍历)
这种结构和你当前的写法一致,只是确保每个实体都带唯一id(你已经做到了):
json.user @user # All of user's lists and associated categories and items json.lists @user.lists do |list| json.(list, :id, :name, :active, :user_id) json.categories list.categories do |category| json.(category, :id, :name, :user_id, :created_at, :updated_at, :list_id) json.items category.items do |item| json.(item, :id, :name, :website, :price, :information, :notes, :category_id, :quantity) end end end # User's total hiking miles json.total_m @user.m_by_month # User's upcoming trips properties json.upcoming_trips @user.upcoming_trips do |trip| json.(trip, :id, :name, :start_date, :end_date, :data_id, :list_id) # User's todo items json.todos trip.todo_items.sort_by {|t| t[:position]} do |todo| json.(todo, :id, :title, :completed, :position, :upcoming_trip_id, :user_id) end # User's shopping list items json.shopping_list_items trip.shopping_list_items.sort_by {|s| s[:position]} do |item| json.(item, :id, :title, :completed, :position, :quantity, :upcoming_trip_id, :user_id) end # 修正笔误:hike改为trip json.trip_details trip.trip_details.order(updated_at: :desc) do |detail| json.(detail, :id, :trip_type, :label, :url, :upcoming_trip_id) end end
选项2:拆分所有实体到顶层(更适合前端归一化)
把所有关联实体都提取到顶层数组,让前端可以直接对每个实体集合做归一化:
json.user @user json.lists @user.lists, :id, :name, :active, :user_id json.categories @user.categories, :id, :name, :user_id, :created_at, :updated_at, :list_id json.items @user.items, :id, :name, :website, :price, :information, :notes, :category_id, :quantity json.total_m @user.m_by_month json.upcoming_trips @user.upcoming_trips, :id, :name, :start_date, :end_date, :data_id, :list_id json.todos @user.todo_items.sort_by {|t| t[:position]}, :id, :title, :completed, :position, :upcoming_trip_id, :user_id json.shopping_list_items @user.shopping_list_items.sort_by {|s| s[:position]}, :id, :title, :completed, :position, :quantity, :upcoming_trip_id, :user_id json.trip_details @user.trip_details.order(updated_at: :desc), :id, :trip_type, :label, :url, :upcoming_trip_id
二、前端Normalizr正确配置
你的当前schema存在关联关系错误和未定义顶层响应结构的问题,下面是修正后的代码:
1. 正确定义实体关联关系
先梳理清楚你的实体依赖链:User → Lists → Categories → ItemsUser → UpcomingTrips → Todos/ShoppingListItems/TripDetails
对应的schema代码:
import { schema } from 'normalizr'; // 最底层的Item实体,属于Category const itemSchema = new schema.Entity('items'); // Category实体,包含items数组,属于List const categorySchema = new schema.Entity('categories', { items: [itemSchema] }); // List实体,包含categories数组,属于User const listSchema = new schema.Entity('lists', { categories: [categorySchema] }); // 独立的Todo/ShoppingList/TripDetail实体 const todoSchema = new schema.Entity('todos'); const shoppingListItemSchema = new schema.Entity('shoppingListItems'); const tripDetailSchema = new schema.Entity('tripDetails'); // UpcomingTrip实体,包含三个关联数组 const upcomingTripSchema = new schema.Entity('upcoming_trips', { todos: [todoSchema], shopping_list_items: [shoppingListItemSchema], trip_details: [tripDetailSchema] }); // User实体,包含lists和upcoming_trips数组 const userSchema = new schema.Entity('user', { lists: [listSchema], upcoming_trips: [upcomingTripSchema] }); // 定义顶层响应schema:匹配后端返回的完整对象结构 export const userResponseSchema = new schema.Object({ user: userSchema, lists: [listSchema], upcoming_trips: [upcomingTripSchema] // total_m是统计数据,不需要归一化,会自动保留在result中 });
2. 正确调用normalize方法
注意要使用顶层的userResponseSchema,而不是单独的userSchema:
import { normalize } from 'normalizr'; import { userResponseSchema } from './schema'; // 假设response是后端返回的完整数据对象 const normalizedData = normalize(response, userResponseSchema);
3. 归一化后的预期结构
处理后你会得到清晰的实体映射和结果引用:
{ entities: { user: { [userId]: {...} }, lists: { [listId]: {...} }, categories: { [categoryId]: {...} }, items: { [itemId]: {...} }, upcoming_trips: { [tripId]: {...} }, todos: { [todoId]: {...} }, shoppingListItems: { [itemId]: {...} }, tripDetails: { [detailId]: {...} } }, result: { user: userId, // 对应user实体的id lists: [listId1, listId2, ...], upcoming_trips: [tripId1, tripId2, ...], total_m: [...] // 原封不动保留的统计数据 } }
三、额外注意事项
- 确保所有实体都有唯一
id字段,Normalizr依赖这个区分不同实体;如果你的实体id不是id字段,可以通过idAttribute指定,比如new schema.Entity('items', {}, { idAttribute: 'item_id' }) - 对于不需要归一化的纯数据字段(比如
total_m),不需要在schema中定义,会直接保留在result里 - 测试时可以通过
console.log(normalizedData)确认结构,之后在Vue中就能通过entities快速访问任意实体,比如normalizedData.entities.items[itemId]直接获取某个商品
内容的提问来源于stack exchange,提问作者J. Jackson
相关产品推荐
相关产品推荐

