【问题标题】:How to get altered tree from Immutable tree, maximising reuse of nodes如何从不可变树中获取更改树,最大限度地重用节点
【发布时间】:2016-12-23 09:22:42
【问题描述】:

我有一个这样的树结构数据:

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

我正在使用 Angular 2。据我所知,只要输入发生更改,更改检测就会启动,而您的更改检测策略是 onPush。

为了优化树结构更新(例如在嵌套级别切换节点或更改此类节点的任何属性),我使用了Immutable。

不可变如何帮助我优化更新?我读到不可变会在数据发生变化时重用旧数据中的引用来构造新对象。

如何有效地使用不可变数据结构来更新嵌套级别的节点?

假设

  1. 我没有任何节点的keyPath。
  2. 每个节点都有一个唯一的id 属性值,可用于查询树数据(但如何?)

问题

  1. 如何在嵌套级别的某处更新节点?联系该节点的最有效方式是什么?
  2. 如何更新多个节点?我听说过withMutations API,但是还有其他有效的方法吗?

我的方法

  1. 深拷贝一切,然后修改新构造的对象:

    var newState = deepClone(oldState) // deep copy everything and construct a new object 
    newState.nodes.forEach(node => {
        if(node.id == 54) {
            node.id = 789;
        }
    })
    
  2. 我正在尝试实现的内容:

    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
    

我希望通过第二种方案实现节点的复用,如下图:

【问题讨论】:

  • “不可变更新”是矛盾的。你有一个价值,你想要另一个相似的价值,所以你创造差异并分享相似之处
  • @Caleth 同意了。但是需要您的帮助来有效地找到一种方法来到达树中的节点并更新它
  • 您创建了一个新树。您引用它们而不是复制未更改的值,这是安全的,因为 没有任何变化
  • 那么您的数据结构是打算更新(可变)还是打算将更改应用于您制作的副本(不可变)整体复制?后者当然效率较低。
  • “如果我让树数据不可变并且只更新其中的一部分”:这是不可能。根据“不可变”的定义,不可变树不能更新它的任何部分。您只是在寻找更新树。

标签: javascript immutability immutable.js immutable-collections


【解决方案1】:

意识到当你使用 Immutable 作为你的树结构时,你不能期望在不改变对该节点的内部引用的情况下替换一个节点,这意味着这样的改变需要冒泡到树的根部,也更改根目录。

更详细:一旦您使用方法更改某个属性值,您将获得为该特定节点返回的新对象。然后在你的树中重新注入那个新节点,即在父节点的 children 列表中,你将使用一个方法来创建一个新的 children 列表(因为 >children 属性也是不可变的)。现在的问题是将该 children 列表附加到父节点,这将产生一个新的父节点,...等等。您最终将重新创建要更改的节点的所有祖先节点,为您提供一个新的树实例,该实例将重用不在根到节点路径中的节点。

要重复使用您的图像,您将获得如下信息:

不可变 API 可以使用 updateIn 方法(或 setIn,如果您的更新仅涉及目标节点的一个属性)为您执行此操作。您需要将关键路径传递给它以识别您要修改的(嵌套)节点。

因此,例如,如果您知道要更改的节点的 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);
}

您需要向它传递树、具有子项的属性的名称(在您的情况下为 children),以及将识别您正在查找的节点的函数。假设您想要 id 为 4 的节点的路径,那么您可以这样称呼它:

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

该键路径可能看起来像这样——数组中索引的更改,children 属性提供了更深的数组:

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

然后要修改该路径上的节点,您可以执行以下操作:

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

这是一个带有一些示例数据的演示:

// 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>

【讨论】:

  • 感谢您的大力帮助
猜你喜欢
  • 2022-01-13
  • 2021-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 1970-01-01
  • 2016-09-14
相关资源
最近更新 更多