快速访问json树数据结构

Fast access to json tree data structure

我有一个 reducer,它包含树数据结构(总共超过 100_000 个项目)。这是数据的样子

[
    {
        text: 'A',
        expanded: false,
        items:
        [
            {
                text: 'AA',
                expanded: false
            },
            {
                text: 'AB',
                expanded: false,
                items:
                [
                    {
                        text: 'ABA',
                        expanded: false,
                    },
                    {
                        text: 'ABB',
                        expanded: false,
                    }
                ]
            }
        ]
    },
    {
        text: 'B',
        expanded: false,
        items:
        [
            {
                text: 'BA',
                expanded: false
            },
            {
                text: 'BB',
                expanded: false
            }
        ]
    }
]

我需要做的是使用文本作为 id 非常快速地访问这些项目(每次用户单击树视图中的项目时都需要切换展开)。我应该将整个结构复制到字典中还是有更好的方法?

将所有单个项目复制到 Map<id, Node> 中,然后通过 ID 访问它。

const data = []// your data

// Build Map index
const itemsMap = new Map();

let itemsQueue = [...data];
let cursor = itemsQueue.pop();
while (cursor) {
  itemsMap.set(cursor.text, cursor);
  if (cursor.items)
    for (let item of cursor.items) {
      itemsQueue.push(item);
    }
  cursor = itemsQueue.pop();
}


// Retrieve by text id
console.log(map.get('ABB'));
// {
//   text: 'ABB',
//   expanded: false,
// }

我想你的意思可能是处理扩展变化的成本非常高(因为你可能 close/open 一个有 100000 个叶子的节点,然后通知 100000 个 UI 项目)。

然而,这让我担心,因为我希望只存在扩展的 UI 项(例如,你没有为所有内容隐藏 React 元素,每个元素都坐在那里并监视 Redux 选择器以防它的一部分树的部分变得可见)。

只要元素在未展开时不存在,那么为什么展开的状态除了其直接父级之外任何人都知道,而且只有当它也在屏幕上时才为父级所知?

我建议展开状态应该是e.g. React 状态根本不是 Redux 状态。如果它们在屏幕上,那么它们会被展开,可以选择展开它们的子项(在父 UI 元素中作为状态保存),如果它们不在屏幕上,它们就不存在。

也许以下内容会有所帮助,如果您需要更多帮助,请告诉我,但请创建一个可显示问题的可运行示例(代码片段):

const items = [
  {
    text: 'A',
    expanded: false,
    items: [
      {
        text: 'AA',
        expanded: false,
      },
      {
        text: 'AB',
        expanded: false,
        items: [
          {
            text: 'ABA',
            expanded: false,
          },
          {
            text: 'ABB',
            expanded: false,
          },
        ],
      },
    ],
  },
  {
    text: 'B',
    expanded: false,
    items: [
      {
        text: 'BA',
        expanded: false,
      },
      {
        text: 'BB',
        expanded: false,
      },
    ],
  },
];
//in your reducer
const mapItems = new Map();
const createMap = (items) => {
  const recur = (path) => (item, index) => {
    const currentPath = path.concat(index);
    mapItems.set(item.text, currentPath);
    //no sub items not found in this path
    if (!item.items) {
      return;
    }
    //recursively set map
    item.items.forEach(recur(currentPath));
  };
  //clear the map
  mapItems.clear();
  //re create the map
  items.forEach(recur([]));
};
const recursiveUpdate = (path, items, update) => {
  const recur = ([current, ...path]) => (item, index) => {
    if (index === current && !path.length) {
      //no more subitems to change
      return { ...item, ...update };
    }
    if (index === current) {
      //need to change an item in item.items
      return {
        ...item,
        items: item.items.map(recur(path)),
      };
    }
    //nothing to do for this item
    return item;
  };
  return items.map(recur(path));
};
const reducer = (state, action) => {
  //if you set the data then create the map, this can make
  //  testing difficult since SET_ITEM works only when
  //  when you call SET_DATA first. You should not have
  //  side effects in your reducer (like creating the map)
  //  I broke this rule in favor of optimization 
  if (action.type === 'SET_DATA') {
    createMap(action.payload); //create the map
    return { ...state, items };
  }
  if (action.type === 'SET_ITEM') {
    return {
      ...state,
      items: recursiveUpdate(
        mapItems.get(action.payload.text),
        state.items,
        action.payload
      ),
    };
  }
  return state;
};
//crate a state
const state = reducer(
  {},
  { type: 'SET_DATA', payload: items }
);
const changed1 = reducer(state, {
  type: 'SET_ITEM',
  payload: { text: 'A', changed: 'A' },
});
const {
  items: gone,
  ...withoutSubItems
} = changed1.items[0];
console.log('1', withoutSubItems);
const changed2 = reducer(state, {
  type: 'SET_ITEM',
  payload: { text: 'ABB', changed: 'ABB' },
});
console.log('2', changed2.items[0].items[1].items[1]);
const changed3 = reducer(state, {
  type: 'SET_ITEM',
  payload: { text: 'BA', changed: 'BA' },
});
console.log('3', changed3.items[1].items[0]);

如果你想做的只是切换展开,那么你应该用本地状态来做,而忘记在 redux 中存储展开,除非你想展开呈现项目的组件之外的东西,因为展开然后在之间共享多个组件。