简体   繁体   中英

Fast access to json tree data structure

I have a reducer which holds tree data structure (more then 100_000 items total). This is what the data looks like

[
    {
        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
            }
        ]
    }
]

What I need to do is access this items really fast using text as an id (need to toggle expanded each time user clicks on item in a treeview). Should I just copy whole structure in to dictionary or is there a better way?

I think you may mean that the cost of handling a change of expansion is really high (because potentially you close/open a node with 100000 leaves and then 100000 UI items are notified).

However, this worries me as I hope only the expanded UI items exist at all (eg you don't have hidden React elements for everything, each sitting there and monitoring a Redux selector in case its part of the tree becomes visible).

So long as elements are non-existent when not expanded, then why is expansion a status known by anything but its immediate parent, and only the parent if it's also on screen?

I suggest that expansion state should be eg React state not Redux state at all. If they are on screen then they are expanded, optionally with their children expanded (with this held as state within the parent UI element) and if they are not on screen they don't exist.

Maybe the following will help, let me know if you need more help but please create a runnable example (code snippet) that shows the problem:

 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]);

If all you wanted to do is toggle expanded then you should probably do that with local state and forget about storing expanded in redux unless you want to expand something outside of the component that renders the item because expanded is then shared between multiple components.

Copy all the individual items into a Map<id, Node> to then access it by the 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,
// }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM