8

次のようなツリー構造データがあります。

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

私はAngular 2を使用しています。私の知る限り、入力が変更され、変更検出戦略がonPush.

ツリー構造の更新を最適化するために (ネストされたレベルでノードを切り替えたり、そのようなノードの属性を変更したりするなど)、Immutableを使用しました。

Immutableは更新プログラムを最適化するのにどのように役立ちますか? Immutableは古いデータからの参照を再利用して、データが変更されるたびに新しいオブジェクトを構築することを読みました。

不変データ構造を効率的に使用して、ネストされたレベルでノードを更新するにはどうすればよいですか?

仮定

  1. keyPathどのノードにも がありません。
  2. 各ノードには、idツリー データのクエリに使用できる一意のプロパティ値があります (ただし、どのように?)

問題

  1. ネストされたレベルのどこかでノードを更新するにはどうすればよいですか? そのノードに到達する最も効率的な方法は何ですか?
  2. 複数のノードを更新するにはどうすればよいですか? APIと聞いたのwithMutationsですが、他に効率的な方法はありますか?

私のアプローチ

  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
    

2 番目のソリューションでは、下の図のようにノードの再利用を実現したいと考えています。

immutablejs のツリー ビュー

4

1 に答える 1

9

ツリー構造に Immutable を使用する場合、そのノードへの内部参照を変更せずにノードを置き換えることは期待できないことに注意してください。つまり、そのような変更はツリーのルートまでバブルアップし、ルートも変更する必要があります。 .

詳細: メソッドを使用して特定のプロパティ値を変更するとすぐに、その特定のノードに対して新しいオブジェクトが返されます。次に、その新しいノードをツリー、つまり親の子リストに再注入するには、新しいリストを作成するメソッドを使用します( childrenプロパティも不変であるため)。問題は、その子リストを親にアタッチすることです。これにより、新しい親ノードが作成されます...など。変更したいノードのすべての祖先ノードを再作成することになり、ルートからノードへのパスになかったノードを再利用する新しいツリー インスタンスが得られます。

画像を再利用するには、次のようになります。

ここに画像の説明を入力

Immutable API は、updateInメソッドを使用してこれを行うことができます (またはsetIn、更新がターゲット ノードの 1 つのプロパティのみに関係する場合)。変更する (ネストされた) ノードを識別するために、キー パスを渡す必要があります。

たとえば、変更するノードの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>

于 2016-12-26T11:42:09.393 に答える