简体   繁体   English

如何从不可变树中获取更改的树,最大化节点的重用

[英]How to get altered tree from Immutable tree, maximising reuse of nodes

I have a tree structure data like this: 我有这样的树结构数据:

[{
    id: 54,
    name:123,
    children: [{
        id: 54,
        name:123,
        children: [{
            id: 154,
            name:1234,
            children []...
        }]
    }]
}, {
 ...
}]

I am using Angular 2. As far as I know, change detection kicks in whenever input changes and your change detection strategy is onPush . 我正在使用Angular 2.据我所知,无论何时输入发生变化且您的变化检测策略为onPush更改检测都会onPush

To optimise the tree structure updates (eg toggling a node at a nested level or changing any attributes of such a node), I used Immutable . 为了优化树结构更新(例如,在嵌套级别切换节点或更改此类节点的任何属性),我使用了Immutable

How can Immutable help me to optimise my updates? Immutable如何帮助我优化我的更新? I read that Immutable reuses references from old data to construct new objects whenever data changes. 我读到,当数据发生变化时, Immutable会重用旧数据中的引用来构造新对象。

How do I use Immutable data structures efficiently to update nodes at a nested level? 如何有效地使用不可变数据结构来更新嵌套级别的节点?

Assumptions 假设

  1. I don't have a keyPath for any of the nodes. 我没有任何节点的keyPath
  2. Each node has a unique id property value which can be used to query tree data (but how?) 每个节点都有一个唯一的id属性值,可用于查询树数据(但是如何?)

Problems 问题

  1. How can I update a node somewhere at a nested level? 如何在嵌套级别更新某个节点? What could be the most efficient way of reaching out to that node? 什么是最有效的方式来接触该节点?
  2. How do I update multiple nodes? 如何更新多个节点? I heard of the withMutations API, but is there any other efficient way? 我听说过withMutations API,但还有其他有效方法吗?

My approach 我的方法

  1. Deep-copy everything and then modify the newly constructed object: 深层复制所有内容,然后修改新构造的对象:

     var newState = deepClone(oldState) // deep copy everything and construct a new object newState.nodes.forEach(node => { if(node.id == 54) { node.id = 789; } }) 
  2. What I am trying to implement: 我想要实现的内容:

     var newState = Immutable.fromJS(oldState) // create an immutable object newState = newState.find(node => { node.set("id", 123); }); // any changes to object will return new object 

With the second solution I hope to achieve re-use of nodes, as pictured below: 使用第二个解决方案,我希望实现节点的重用,如下图所示:

在immutablejs的树视图

Realise that when you use Immutable for your tree structure, you cannot expect to replace a node without also changing the internal references to that node, which means that such a change will need to bubble up to the root of the tree, also changing the root. 意识到当你对树结构使用Immutable时,你不能指望在不改变对该节点的内部引用的情况下替换节点,这意味着这样的更改需要冒泡到树的根,也改变根。

More detailed: as soon as you use a method to change a certain property value, you will get a new object returned for that particular node. 更详细:只要使用方法更改某个属性值,就会获得为该特定节点返回的新对象。 Then to re-inject that new node in your tree, ie in the parent's children list, you will use a method that will create a new children list (since the children property is immutable as well). 然后,要在树中重新注入该新节点,即在父项的列表中,您将使用将创建新列表的方法(因为children属性也是不可变的)。 Now the problem is to attach that children list to the parent, which will result in a new parent node, ...etc. 现在的问题是将列表附加到父节点,这将导致新的父节点,...等。 You'll end up recreating all the ancestor nodes of the node you want to change, giving you a new tree instance, which will have some reuse of nodes that were not in the root-to-node path. 您将最终重新创建要更改的节点的所有祖先节点,从而为您提供一个新的树实例,该实例将重用一些不在根节点路径中的节点。

To re-use your image, you'll get something like this: 要重新使用您的图片,您将获得以下内容:

在此输入图像描述

The Immutable API can do this for you with the updateIn method (or setIn if your update concerns only one property of the targeted node). Immutable API可以使用updateIn方法为您执行此操作(如果您的更新仅涉及目标节点的一个属性, setIn )。 You will need to pass it the key path to identify the (nested) node you want to modify. 您需要将密钥路径传递给它以标识要修改的(嵌套)节点。

So, if for instance you know the id of the node to be changed, you could use a little helper function to find the key path to that particular node. 因此,如果您知道要更改的节点的id ,则可以使用一个小辅助函数来查找该特定节点的关键路径。

function findKeyPathOf(tree, childrenKey, predicate) {
    var path;
    if (Immutable.List.isList(tree)) {
        tree.some(function (child, i) {
            path = findKeyPathOf(child, childrenKey, predicate);
            if (path) return path.unshift(i); // always returns truthy
        });
        return path;
    } 
    if (predicate(tree)) return [];
    path = findKeyPathOf(tree.get(childrenKey), childrenKey, predicate);
    if (path) return [childrenKey].concat(path);
}

You need to pass it the tree, the name of the property that has the children (so children in your case), and the function that will identify the node you are looking for. 您需要将它的树,有孩子(所以属性名children在你的情况下),并且,将确定你正在寻找该节点的功能。 Let's say you want the path to the node with id 4, then you would call it like this: 假设您想要id为4的节点的路径,那么您可以像这样调用它:

var keyPath = findKeyPathOf(tree, 'children', node => node.get('id') == 4);

That key path could look something like this -- an alteration of an index in the array, and the children property providing the deeper array: 该关键路径可能看起来像这样 - 数组中索引的更改,以及提供更深层数组的children属性:

[0, 'children', 0, 'children', 1]

Then to modify the node at that path, you would do something like this: 然后要修改该路径上的节点,您可以执行以下操作:

var newTree = tree.updateIn(keyPath, node => node.set('name', 'Hello'));

Here is a demo with some sample data: 这是一个包含一些示例数据的演示:

 // Function to get the path to a certain node in the tree function findKeyPathOf(tree, childrenKey, predicate) { var path; if (Immutable.List.isList(tree)) childrenKey = tree.findKey(child => path = findKeyPathOf(child, childrenKey, predicate)); else if (predicate(tree)) return []; else path = findKeyPathOf(tree.get(childrenKey), childrenKey, predicate); return path && [childrenKey].concat(path); } // Function to compare two trees function differences(tree1, tree2, childrenKey) { if (Immutable.List.isList(tree1)) { return tree1.reduce(function (diffs, child, i) { return diffs.concat(differences(child, tree2.get(i), childrenKey)); }, []); } return (tree1 !== tree2 ? [tree1] : []) .concat(differences(tree1.get(childrenKey), tree2.get(childrenKey), childrenKey)); } // Sample data var tree = [{ id: 1, name: 'Mike', children: [{ id: 2, name: 'Helen', children: [{ id: 3, name: 'John', children: [] },{ id: 4, name: 'Sarah', children: [{ id: 5, name: 'Joy', children: [] }] }] }] }, { id: 6, name: 'Jack', children: [{ id: 7, name: 'Irene', children: [] },{ id: 8, name: 'Peter', children: [] }] }]; // Create immutable tree from above plain object: var tree = Immutable.fromJS(tree); // Use the function to find the node with id == 4: var keyPath = findKeyPathOf(tree, 'children', node => node.get('id') == 4); // Found it? if (keyPath) { // Set 'name' to 'Hello' in that node: var newTree = tree.updateIn(keyPath, node => node.set('name', 'Hello')); // Print the new tree: console.log(newTree.toJS()); // Compare all nodes to see which ones were altered: var altered = differences(tree, newTree, 'children').map(x => x.get('id')); console.log('IDs of nodes that were replaced: ', altered); } else { console.log('Not found!'); } 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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