web-dev-qa-db-ja.com

オブジェクトの配列のプロパティを観察して変更を確認します

Aurelia を使用していますが、グリッドにバインドされたアイテムの配列があり、それらには選択されたプロパティがあります。いずれかの項目が真の場合に有効になるようにボタンをバインドしたい。リストをフィルタリングして選択したアイテムを返すゲッターを使用するブルートフォースアプローチを実行できますが、それはアプリで常にダーティチェックを実行することを意味し、それを実行したくありません。より効率的なアプローチを望んでいます。何か案は?

16
Eric J. Smith

あなたができることはほとんどありません-私があなたのユースケースを正しく持っていると仮定すると:

ダーティチェック(これは1つのプロパティのみで、大したことではありません)

export class Item {
  selected = false;
}

export class ViewModel {
  items = [new Item(), new Item(), new Item()];

  get anySelected() {
    var items = this.items, i = items.length;
    while(i--) {
      if (items[i].selected) {
        return true; 
      }
    }
    return false;
  }
}

アイテムを観察する

import {BindingEngine, inject} from 'aurelia-framework';

export class Item {
  selected = false;
}

@inject(BindingEngine)
export class ViewModel {
  items = [new Item(), new Item(), new Item()];    
  anySelected = false;
  subscriptions = [];

  constructor(locator) {
    this.bindingEngine = bindingEngine;
  }

  updateAnySelected() {
    var items = this.items, i = items.length;
    while(i--) {
      if (items[i].selected) {
        this.anySelected = true;
        return;
      }
    }
    this.anySelected = false;
  }

  activate() {
    var items = this.items, i = items.length, observer;
    while(i--) {
      observer = this.bindingEngine.propertyObserver(items[i], 'selected');
      subscriptions.Push(observer.subscribe(() => this.updateAnySelected());
    }
    this.updateAnySelected();
  }

  deactivate() {
    let dispose;
    while(subscription = subscriptions.pop()) {
      subscription.dispose();
    }
  }
}

コレクションクラスを使用する

import {computedFrom} from 'aurelia-framework';

export class Item {
  _selected = false;

  constructor(parent) {
    this.parent = parent;
  }

  @computedFrom('_selected')
  get selected() {
    return this._selected;
  }
  set selected(newValue) {
    newValue = !!newValue;
    if (newValue === _selected) {
      return;
    }
    _selected = newValue;
    this.parent.itemChanged(newValue);
  }
}

export class Items {
  items = [];
  selectedCount = 0;
  anySelected = false;

  createItem() {
    let item = new Item(this);
    this.items.Push(item);
    return item;
  }

  itemChanged(selected) {
    this.selectedCount += (selected ? 1 : -1);
    this.anySelected = this.selectCount > 0;    
  }
}

export class ViewModel {
  items = new Items();

  constructor() {
    let item = this.items.createItem();
    item = this.items.createItem();
    item = this.items.createItem();
  }
}

選択したブール小道具の代わりにselectedItems配列を使用します

export class ViewModel {
  items = [{}, {}, {}];
  selectedItems = [];

  selectItem(item) {
    this.items.Push(item);
  }

  deselectItem(item) {
    this.items.splice(this.items.indexOf(item), 1);
  }
}

バインドの目的で、「選択した」プロパティとしてselectedItems.lengthを使用します

35
Jeremy Danyow

示されているようにEventAggregatorを活用することもできると思います ここ 。このようにして、常にダーティチェックを実行する必要はなく、代わりにアイテム選択イベントを独自に処理してVMそしてイベントデータを公開します。反対側のサブスクライバーは同じものをリッスンします)必要な体操を行います。

しかし、一度も使ったことがないので、詳細はわかりません。しかし、ドキュメントからはかなり簡単に見えます。

0
Sayan Pal

Jeremyの例に加えて、次のようなカスタムセッターを作成できます。

class Item {
   // this is your ~private~ field
  _isSelected = false;

  // in our constructor, we pass the view model and the property name
  constructor(vm, prop, name) {
    this.vm = vm;
    this.prop = prop;
    this.name = name;
  }

  get isSelected() {
    return this._isSelected;
  }
  // when you set the value, you increase the vm's property
  set isSelected(value) {
    if (value !== this._isSelected) {
      this.vm[this.prop] += value ? 1 : -1;
      this._isSelected = value;
    }
  }
}

export class MyViewModel
{
  items = [];
  itemsSelected = 0; // that's the property we'll pass to the class we've created

  constructor() 
  {
    for (let i = 0; i < 50; i++) {
      // instead of adding a annonymous {} here, we add an instance of our class
      this.items.Push(new Item(this, 'itemsSelected', `Item ${i+1}`));
    }
  }

  toggleIsSelected(item) {
    item.isSelected = !item.isSelected;
  }
}

私はあなたのためにプランカーを作成しました: http://plnkr.co/edit/OTb2RDLZHf5Fy1bVdCB1?p=preview


そうすることで、アイテムが変更されたかどうかを確認するためにループすることはありません。

0
Buzinas

ジェレミーは私にこれについて考えさせました このバグで 。したがって、カスタムのバインディング動作を介してバインディングを更新することもできるようです。うまくいけば、ジェレミーは私がここであまり愚かなことをしていないことを確認できます。

このように使用されます:

repeat.for="item of items | filter & array:'propertyName'"

これは、標準の監視動作をオーバーライドし、各アイテムで定義した配列とプロパティを監視します。おそらく、より一般的なものに改善することができます...

function observeProperty(obj, property) {
  this.standardObserveProperty(obj, property);
  let value = obj[property];
  if (Array.isArray(value)) {
    this.observeArray(value); 
    for(let each of value){   
      this.standardObserveProperty(each, this.propertyName); 
    }
  }
}

export class ArrayBindingBehavior {
  bind(binding, source, property) {
    binding.propertyName = property;
    binding.standardObserveProperty = binding.observeProperty;
    binding.observeProperty = observeProperty;
  }

  unbind(binding, source) {
    binding.observeProperty = binding.standardObserveProperty;
    binding.standardObserveProperty = null;
    delete binding.propertyName;
  }
}
0
Tim