快速访问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 中存储展开,除非你想展开呈现项目的组件之外的东西,因为展开然后在之间共享多个组件。
我有一个 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 中存储展开,除非你想展开呈现项目的组件之外的东西,因为展开然后在之间共享多个组件。