You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否使用Normalizr构建n深度树形结构并实现双向转换?

使用Normalizr实现扁平数据与嵌套树形结构的双向转换

嘿,我完全懂你纠结Normalizr用法的心情——很多人刚接触它时,都会混淆它的适用场景,尤其是想用它做嵌套树形转换的时候。其实Normalizr不仅能把嵌套数据扁平化(这是它最常见的用法,比如给Redux存数据),反过来用它的Schema和实体系统,也能轻松实现扁平数据到嵌套结构的转换,还能把树形结构再转回去。咱们一步步来实现你要的功能:

第一步:定义Normalizr Schema

首先得为你的节点数据定义Schema,因为你的数据是层级嵌套的,每个节点的children是同类型的节点数组,所以我们需要递归引用Schema:

import { schema, normalize, denormalize } from 'normalizr';

// 定义单个节点的Schema
const nodeSchema = new schema.Entity('nodes', {
  // 递归引用自身,说明children是节点数组
  children: [nodeSchema]
});

// 顶层是节点数组,所以定义一个数组Schema
const nodeListSchema = [nodeSchema];

第二步:扁平数据转嵌套树形结构

你的原始扁平数据里,children是包含id的对象数组(比如[{id: 3076}]),而Normalizr更习惯直接处理id数组,所以先做个简单预处理,把children转成纯id数组:

const rawData = [ 
  { "id": 1077, "title": "Hello world", "children": [{ "id": 3076 }] }, 
  { "id": 3076, "title": "Hello foo", "children": [{ "id": 3077 }] }, 
  { "id": 3077, "title": "Hello bar", "children": [] }, 
  { "id": 3078, "title": "Goodbye world", "children": [] } 
];

// 预处理:将children从对象数组转为id数组
const flattenedData = rawData.map(item => ({
  ...item,
  children: item.children.map(child => child.id)
}));

接下来,我们先把预处理后的扁平数据标准化(这一步会生成Normalizr需要的entities实体库和result顶层id列表),再用denormalize方法还原成嵌套结构:

// 标准化扁平数据,得到实体库和顶层id列表
const normalized = normalize(flattenedData, nodeListSchema);

// 从实体库还原出嵌套树形结构
const nestedTree = denormalize(normalized.result, nodeListSchema, normalized.entities);

console.log(nestedTree);
// 输出就是你想要的嵌套树形结构:
// [
//   { "id": 1077, "title": "Hello world", "children": [{ "id": 3076, "title": "Hello foo", "children": [{ "id": 3077, "title": "Hello bar", "children": [] }] }] },
//   { "id": 3078, "title": "Goodbye world", "children": [] }
// ]

第三步:嵌套树形结构转扁平数据(用于发送给服务器)

这一步就简单了,直接用normalize方法处理嵌套树形结构,得到标准化后的实体库,再把实体库转成你需要的扁平格式(如果需要把id数组转回对象数组的话):

// 假设nestedTree是你的嵌套树形结构
const normalizedBack = normalize(nestedTree, nodeListSchema);

// 提取实体库中的所有节点,再把children转回对象数组(匹配服务器需要的格式)
const flattenedForServer = Object.values(normalizedBack.entities.nodes).map(node => ({
  ...node,
  children: node.children.map(id => ({ id }))
}));

console.log(flattenedForServer);
// 输出就是和你原始输入一致的扁平数据

关键说明

  • Normalizr的核心是管理关联数据的引用,它帮你省去了自己写递归遍历、匹配id的麻烦
  • 递归Schema的定义是实现层级转换的关键,一定要确保children字段引用的是自身的数组Schema
  • 如果你的服务器返回/接收的直接是id数组(而非{id: xxx}对象数组),那预处理和后处理的步骤可以省略

这样用下来,你应该能清晰感受到Normalizr在处理这类关联数据时的便捷性了~

内容的提问来源于stack exchange,提问作者Simon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:08:38