Ember.js: Невозможно наблюдать изменения вложенных свойств в массиве с помощью @each

Созданный на 27 февр. 2012  ·  75Комментарии  ·  Источник: emberjs/ember.js

Добавление наблюдателя к вложенному свойству объектов в коллекции не вызывает надежного срабатывания. Я создал тестовый пример, который можно добавить к ember-runtime/tests/mixins/array_test.js который иллюстрирует это. Наблюдение за вложенным свойством на самом EveryProxy, похоже, работает нормально. Однако наблюдение за свойством с помощью @each.nest.isDone - нет.

В следующем тесте count1 увеличивается до 1, а count2 - нет:

test('modifying nested contents of an object in an array should call <strong i="11">@each</strong> observers', function() {
  var ary = new TestArray([
      Em.Object.create({ nest: Em.Object.create({ isDone: false}) }),
      Em.Object.create({ nest: Em.Object.create({ isDone: false}) }),
      Em.Object.create({ nest: Em.Object.create({ isDone: false}) }),
      Em.Object.create({ nest: Em.Object.create({ isDone: false}) })
    ]);

  var get = Ember.get, set = Ember.set;
  var count1 = 0, count2 = 0;

  // Works
  var each = get(ary, '<strong i="12">@each</strong>');
  Ember.addObserver(each, 'nest.isDone', function() { count1++; });

  // Doesn't work
  Ember.addObserver(ary, '@each.nest.isDone', function() { count2++; });

  count1 = count2 = 0;
  var item = ary.objectAt(2);
  get(item, 'nest').set('isDone', true);

  equal(count1, 1, '@each.nest.isDone should have notified - observing <strong i="13">@each</strong> array');
  equal(count2, 1, '@each.nest.isDone should have notified - observing chain containing <strong i="14">@each</strong>');
});

Самый полезный комментарий

Вы можете просто добавить новое вычисляемое свойство, которое будет содержать только очень вложенный объект, который вы пытаетесь наблюдать. И @each будет работать правильно
Используйте следующий метод

  allTickets: function () {
    var sections = this.get('sections'),
      each = Ember.$.each,
      tickets = [];

    each(sections, function (i, section) {
      each(section.rows, function (j, row) {
        each(row.tickets, function (k, ticket) {
          tickets.addObject(ticket);
        })
      });
    });

    return tickets;
  }.property(),

  checkedCount: function () {
    ///custom logic
    return something;
  }.property('[email protected]_checked')

вместо

  checkedCount: function () {
    ///custom logic
    return something
  }.property('[email protected][email protected][email protected]_checked')

Все 75 Комментарий

@each поддерживается только на один уровень. Было бы неплохо позволить больше, но я не знаю, насколько это сложно технически. Я позволю @wycats или @tomdale вмешаться в это.

Я не думаю, что это правда ... Он использует обычную инфраструктуру цепочки и должен работать на сколь угодно многоуровневой глубине.

@wycats Значит, это похоже на ошибку. Я никогда не видел, чтобы он работал глубже, чем на один уровень.

Согласен, это похоже на ошибку. Я считаю, что в какой-то момент это сработало. Спасибо за модульный тест.

Итак, я ломал голову над этим. Кажется, это сводится к обработке EachProxy внутренней настройки наблюдателей, когда вы вызываете addObserver с самим собой в качестве корня. Однако, будучи частью связанного пути, ChainNode отрывает имя ключа верхнего уровня от EachProxy и продолжает свой веселый путь, а EachProxy никогда не вмешивается. По сути, в игре есть два уровня массивов.

просмотр '@ each.nest. @ each.isDone' ДЕЙСТВИТЕЛЬНО работает в этом случае.

Забыл уточнить, что когда ChainNode отрывает имя ключа от EachProxy, он получает EachArray, отсюда и необходимость вложения наблюдателей @ each.

Принцип работы цепочек заключается в том, что каждый узел цепочки имеет соответствующий chainWatcher, который наблюдает за ключом в значении родительского узла (), чтобы это работало, цепочка должна остановиться на @each и настроить chainWatcher с оставшимся путем.

Я считаю, что специальный регистр @ означает только это.

@kselden , ваша simplify-properties вообще этого касается?

Похоже, это все еще проблема.

цепочки не связаны через свойства, которые создают массивы, это требует некоторых изменений, о которых я говорил выше, нет, я не работал над этим. Также проблематично, что при изменении массива изменяется сам

Я обошел это для всех, кто сталкивается с этой проблемой (особенно при использовании ассоциаций ember-data), добавив свойство на одной стороне ассоциации, которое проверяет свойство на другом конце ассоциации (в моем случае, isLoaded), а затем используя это после @each . Как я уже сказал, это взлом. Но до 1.1 у меня работает.

@tpitale : не могли бы вы показать пример такого взлома? Я не уверен, что вы имеете в виду, добавляя свойство по другую сторону ассоциации.

@kselden @wagenet : http://jsfiddle.net/uErrd/4/ ?

@drogus, учитывая исходный пример, для пути nest.isDone будет свойство nestIsDone. Итак, вы могли наблюдать @each.nestIsDone .

очень хотелось бы увидеть это в 1.0, а не в 1.1: +1:

@mspisars Я тоже, но я не хочу блокировать 1.0 для вещей, которые трудно исправить и несущественные.

последняя активность 4 месяца назад ...: +1:

@mspisars , @darthdeus Я тоже хотел бы увидеть это в 1.0. Дело не в желании, а в способностях.

: +1: для 1.0: было бы действительно полезно, чтобы это работало.

: +1: для 1.0

Объявление общественной службы: +1 не предоставляют ресурсы для развития там, где их не хватает.

Мы уже договорились, что хотели бы иметь эту функцию, но ее очень нетривиально поддерживать. Конечно, мы не будем блокировать 1.0.

Согласовано с @wagenet, это нетривиальные пути кода, которые касаются критически важных частей ember.

Есть обновления по этому поводу? Мы пытались использовать это для веб-приложения, которое будет использоваться крупной сетью отелей, и ни один из обходных путей, похоже, не работает. Единственное работоспособное решение на данный момент - переделать эту часть интерфейса, чтобы сделать его проще, что очень досадно.
Любая информация будет принята с благодарностью и спасибо за вашу огромную работу.

Это на удивление сложная проблема. Частично проблема заключается в том, что даже если вы реализовали это или обошли его стороной, обновление зависимого массива (который, кажется, является наиболее распространенным вариантом использования) займет время O (n * _2), что является большой проблемой даже для довольно небольшие наборы данных. Чтобы получить O (n), нам потребуются более умные примитивы массива для составления зависимых массивов. Питер и другие * над этим работали, и люди определенно заинтересованы в том, чтобы продвигать это вперед, но я бы не стал задерживать дыхание.

По теме: # 1288, # 2711.

На данный момент я бы рекомендовал создавать зависимые массивы на сервере, в основном как «вычисляемые отношения».

Вот еще одна скрипка, демонстрирующая эту проблему: http://jsfiddle.net/3bGN4/268.

Пример обходного пути, демонстрирующий вложенный @each :

https://gist.github.com/meelash/5785328

Если это на заднем плане, было бы полезно отметить такое поведение в документации по API (возможно, я его пропустил?), Поскольку мне потребовалось время, чтобы найти этот билет.

@dandehavilland Именно так! Здесь обязательно стоит отметить: ' http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/ '. "@ each.goo.bar" все еще не работает, мне понадобились дни, чтобы это выяснить.

Но технически вы можете просматривать вложенные свойства, но не вложенные коллекции свойств, верно?

работы - флагшток и флаг в единственном числе

    [email protected][email protected]

но должно быть

    [email protected]

* не работает - флаги это сборник *

    [email protected][email protected] 

@ kingpin2k Нет, я не верю, что это работает. Они не будут правильно обновляться.

[Edit: может я ошибаюсь]

@joliss, вот jsbin, показывающий это. http://jsbin.com/IXalezO/11/edit

@kselden , это то, над чем вы, вероятно, скоро будете работать?

Это зарегистрировано как не работающее. Было бы здорово добавить, но это явно сложно / недостаточно приоритетно. Если кто-то захочет над этим поработать, мы с радостью рассмотрим PR.

Вы можете просто добавить новое вычисляемое свойство, которое будет содержать только очень вложенный объект, который вы пытаетесь наблюдать. И @each будет работать правильно
Используйте следующий метод

  allTickets: function () {
    var sections = this.get('sections'),
      each = Ember.$.each,
      tickets = [];

    each(sections, function (i, section) {
      each(section.rows, function (j, row) {
        each(row.tickets, function (k, ticket) {
          tickets.addObject(ticket);
        })
      });
    });

    return tickets;
  }.property(),

  checkedCount: function () {
    ///custom logic
    return something;
  }.property('[email protected]_checked')

вместо

  checkedCount: function () {
    ///custom logic
    return something
  }.property('[email protected][email protected][email protected]_checked')

: +1:

Это так легко пропустить в путеводителях . Я считаю, что при обнаружении вложенного @each должно появиться предупреждение.

Да, согласен. Он должен как минимум выдать предупреждение: +1:

Спустя много лет это все еще проблема, верно? Есть ли какое-то поддерживаемое обходное решение?

Да, вы всегда можете просто создать вычисляемые свойства на каждом уровне и наблюдать за вычисляемым свойством вместо каждого свойства в каждой коллекции.

@ kingpin2k Я пробовал что-то подобное, но ничего не

Вот пример: http://emberjs.jsbin.com/faximekico/2/edit По сути, вам нужно агрегировать значения на среднем уровне.

Я понимаю, как это сделать, свойства просто не срабатывают, поэтому пришлось использовать наблюдения.

Можете ли вы показать мне пример, свойства загружаются лениво, поэтому, если вы на самом деле не пытаетесь получить свойства, они не будут «срабатывать».

Я никогда не делал явного .get для извлечения рассматриваемого свойства, хотя у меня было другое вычисленное свойство, которое должно было его прослушивать. Кажется, что только явное получение или использование в шаблоне заставляет их срабатывать, а отсутствие наблюдения за ними другим свойством.

Верно, свойства рассчитываются только по запросу, простое наблюдение за свойством не означает, что свойство нужно рассчитывать. Справедливо, если никто не хочет использовать собственность, нет необходимости вычислять собственность.

@ kingpin2k так что рекомендовать в этом случае? Нам больше не положено использовать наблюдения.

В моем примере показано, как выполнять вложенные зависимости each без наблюдателя. Я не припомню, чтобы кто-нибудь говорил, что наблюдателей больше не следует использовать, но я не обращаю на это религиозного внимания.

@ kingpin2k в вашем примере используется свойство .property('[email protected]')

Это дает мне ту же проблему, когда он не срабатывает, поскольку это свойство. У меня есть еще одно свойство, которое необходимо соблюдать.

@ rwwagner90, подход определенно работает, я подозреваю, что в вашем коде есть еще одна проблема, вызывающая эту проблему. Попробуйте опубликовать jsbin, показывающий проблему, чтобы выяснить, что это такое

Я могу понять, к чему вы клоните, не могли бы вы изменить мой пример, чтобы прояснить ситуацию?

@ rwwagner90 относительно наблюдателей, я считаю, что использование наблюдателей в случаях, когда вы не взаимодействуете с кодом, отличным от ember, - это большой запах кода. Таким образом, использование наблюдателей, например, для запуска рендеринга графика d3 при изменении данных вашего приложения, является полностью допустимым, но использование наблюдателей для установки одного свойства ember при изменении другого почти всегда легче выполнить с другим подходом.

@alexspeller @ kingpin2k У меня два свойства, и одно должно изменяться при изменении другого. Это невозможно, потому что свойства не оцениваются, поэтому второе свойство никогда не обновляется.

@ rwwagner90 наличие одного свойства, которое изменяется при изменении другого, является основной функцией вычисляемых свойств, поэтому я не уверен, что вы описываете - если вы покажете код, который используете, вероятно, кто-то может помочь.

@alexspeller Я преобразовал все это в использование наблюдений, но позвольте мне попытаться вернуть это обратно и показать вам.

let extendedObject = Ember.Object.extend({
          valueLength: function() {
            return this.get('value').length;
          }.property('value.<strong i="7">@each</strong>')
        });

Затем у меня есть свойство, которое должно обновляться при изменении valueLength:

}.property('model.data.<strong i="11">@each</strong>', '[email protected]'),

Однако это никогда не срабатывает, поэтому я был вынужден изменить свое свойство valueLength на наблюдателя, который вместо этого устанавливает valueLength следующим образом:

let extendedObject = Ember.Object.extend({
          valueLengthUpdater: function() {
            this.set('valueLength', this.get('value').length);
          }.observes('value.<strong i="15">@each</strong>')
        });

@ rwwagner90 ваши ключи зависимости не соответствуют тому, что вы получаете, ваш ключ зависимости должен быть model.data.length

Кроме того, [email protected] в порядке, тогда вы должны фактически получить ('valueLength') при вычислении значения. В CP, вообще говоря, если вы завершаете зависимость только с помощью «

Как правило, наблюдатели находятся на низком уровне, и их сложно понять, на самом деле вы не должны работать с ними (это может привести к условиям гонки, слишком частым пересчетам и т. Д.), Они должны планировать работу только один раз для каждого кадра события.

То же самое @krisselden. Кроме того, я предполагаю, что ваша модель на каком-то контроллере имеет свойство data которое представляет собой набор extendedObject и в контроллере у вас есть вычисленное свойство, а ваш extendedObject имеет свойство length на нем (звучит так, как будто это должен быть массив, а не объект, но игнорируя это:

Контроллер
something: function(){
   // inside of here you should actually fetch `valueLength` off of each item, or else it isn't really a dependecy
  // assuming data returns an array
  var lengths = this.get('model.data').getEach('valueLength');
}.property('model.data.length', '[email protected]'),

Редактировать:

Вы можете заменить свойство valueLength на valueLength: Ember.computed.alias('value.length') .

Тогда ваш зависимый ключ станет:

}.property('[email protected]')

См. Объяснение @krisselden ниже, почему то, что я написал ранее, не работает.

@ rwwagner90 valueLength здесь не обязателен - вы можете просто использовать свойство value.length. Зависимый ключ свойства можно упростить до:

}.property('[email protected]')

@each не следует использовать без дополнительного свойства в конце. Если вы просто хотите наблюдать, когда элементы добавляются или удаляются, используйте вместо этого somearray.[] . Если вы наблюдаете [email protected] вам также не нужно наблюдать foo.[]

@alexspeller На самом деле вы не можете

@alexspeller get ('@ each.value'), к сожалению, возвращает массив значений вместо того, чтобы возвращать EachProxy, который убивает дальнейшую цепочку. Я не знаю, почему это было сделано таким образом, но я не знаю, могу ли я изменить это с учетом семвера, и, честно говоря, я нервничаю по поводу включения этого.

Писклявое колесо получает масло, пока оно не затронет большую часть сообщества тлеющих углей, ему не будет уделяться приоритетное внимание, существует множество других низко висящих фруктов, которые помогают 90% сообщества.

@alexspeller Ember.computed.alias работал. Благодаря! : +1:

@krisselden Цепочка @each была бы потрясающей, но за последние несколько лет никто не занимался этим, поэтому маловероятно, что это когда-либо будет.

Я надеюсь, что проблема не устранена, потому что я не знаю, где опубликовать ее.
Я использую Ember 2.1.0 + 45f524a3

Наблюдатель ЗАПУСТИТСЯ с этим:

var o = Ember.Object.create({
    a: [{ b: 1 }, {b: 2} ]
})

Ember.addObserver(o, '[email protected]', o, function () {
    console.log("FIRE")
});

Ember.set(o.a[0], 'b', 3)

Но с этим ничего не происходит

var o = Ember.Object.create({
    A: [{ b: 1 }, {b: 2} ]
})

Ember.addObserver(o, '[email protected]', o, function () {
    console.log("FIRE")
});

Ember.set(o.A[0], 'b', 3)

(Разница в случае названия свойства "a" и "A")

Итак, если кто-нибудь осознает эту проблему, пожалуйста, помогите мне объяснить. Благодарю.

Благодарю. Но у меня он срабатывает со строчной "а".

var o = Ember.Object.create({
    a: [{ b: 1 }, {b: 2} ]
})

Ember.addObserver(o, '[email protected]', o, function () {
    console.log("FIRE")
});

Ember.set(o.a[0], 'b', 3)

И здесь нет вычисляемой собственности.

О, верхний регистр a не поддерживается. Используйте имена свойств, которые начинаются со строчных букв.

Благодаря,
Это происходит с именем в верхнем регистре и ". @ Each".
Ничего страшного с простой собственностью. Как это:

var o = Ember.Object.create({
    A: 1
})

Ember.addObserver(o, 'A', o, function () {
    console.log("FIRE")
});

Ember.set(o, 'A', 3)

Я нигде не могу найти документацию по этому поводу.
Я думаю, что получу кучу псевдонимов в нижнем регистре для названных свойств в верхнем регистре.

Исторически (до 2.0) пути с заглавной буквы указывали на глобальный (так что в вашем случае это будет window.A ). Эта проблема касается вложенных путей с @each , которые в вашем комментарии не обсуждаются.

Большую часть (надеюсь, все) особую оболочку путей с заглавной буквы следует удалить. Мы должны протестировать против 2.8.1. Если он все еще не работает в версии 2.8, пожалуйста, откройте новую проблему с воспроизведением twiddle / jsbin.

Это в основном функциональность, которой, к сожалению, не хватает. Было бы полезно сосредоточиться на правильном изучении основ, прежде чем приступать к добавлению всех этих наворотов в структуру.

@timothyerwin на самом деле довольно легко обойти это. Также я думаю, что сейчас неподходящий момент, чтобы это изменить. Такие вещи, как " @tracked " im

Это проблема Ember, с которой я сталкиваюсь чаще всего в последнее время, часто потому, что она скрыта за чем-то еще, например, за фильтром или макросом сортировки CP, где легко забыть, что вы фактически создаете зависимый ключ, который представляет собой @each с вложенными свойствами.

@luxferresum Не решить эту проблему ? По общему признанию, меня это неоднократно ставило в тупик, и я знаю, что мне давали некоторые подсказки через канал помощи сообщества Slack, но я не могу их вспомнить и не могу найти хороший пост в Интернете. Я думаю, что уловка заключалась в том, чтобы каким-то образом выделить каждый уровень как его собственное вычисляемое свойство.

Итак, если у меня есть такой массив, как traces: [{ x: [...], y: [...]}, ...] , и я хочу запустить обновление внешней библиотеки диаграмм на основе (не разрешенного) ключа traces.@each.{x,y}.[] , я бы сгладил его так?

// Not only is this ugly, but it doesn't work since `_oneBigMess` won't get
// called again when something in one of its nested arrays changes,
// e.g. Changing traces[0].x[0] does not trigger 'traces.[]'
_oneBigMess: computed('traces.[]', function() {
  return this.get('traces').reduce(function(result, item, i, a) {
    return {
      x: result.x.concat(item.x),
      y: result.x.concat(item.y)
    };
  }, { x: [], y: []})
}),
_somethingChanged: observer('_oneBigMess.x.[]', '_oneBigMess.y.[]', function() {
  // update chart
})

Я уверен, что приведенный выше код является идиотским для кого-то более опытного и знакомого с доступными инструментами, но я хватаюсь за соломинку здесь.

но, похоже, ни у кого нет решения для описанной мной ситуации 😢

Обновление : кто-нибудь, пожалуйста, поправьте меня, если я ошибаюсь, но после некоторой борьбы с этим сегодня вечером кажется, что проблема здесь не просто в предоставлении большего синтаксического сахара, а в ограничении текущей системы. Если я правильно понимаю (на основе старого комментария выше и некоторых результатов поиска), эта функция еще не предоставляется, потому что нет эффективного / чистого способа сделать это. Совет, который мне дали, состоял в том, чтобы не делать этого, а лучше изменить дизайн моего кода, чтобы в этом не было необходимости. Вероятно, это хороший совет, но иногда он может оказаться невыполнимым (например, недостаточное время или требования, не зависящие от вас). Для двухуровневого решения одним (довольно ужасным и неэффективным) решением является создание длинного (максимальный размер данных, который ваш код должен правильно обрабатывать) списка ключей, каждый из которых связан с одним элементом массива. Например:

_watchChanges: observer('data.[]', 'data.@each.{name,type}',
  ...(new Array(100).fill(0).map((z,i) => `data.${i}.x.[]`)),
  ...(new Array(100).fill(0).map((z,i) => `data.${i}.y.[]`)), function() {
  // something in one of these arrays has changed --> go do something
})

Другой вариант (хотя я не пробовал, и он все еще не идеален) - перебрать массив верхнего уровня и использовать addArrayObserver для просмотра каждого вложенного массива (а затем removeArrayObserver позже чтобы избежать утечки).

@jacobq

а лучше изменить дизайн моего кода

правда. однако есть гораздо более простые способы справиться с этим, чем вы предлагаете.
позвольте мне показать вам это на вашем исходном примере с массивом traces: [{ x: [...], y: [...]}, ...] , где вы в основном хотите получить список всех x и всех y .

основной трюк / обходной путь здесь - преобразовать каждую трассировку в объект ember, а затем получить CP на Trace с ключом зависимости x.[] и y.[] . Этот CP будет срабатывать всякий раз, когда что-то добавляется / удаляется в массивах x / y . Затем вы можете предоставить простой CP, который имеет [email protected] в качестве ключа зависимости, где traces - это список объектов ember Trace , а myProp - это CP Я только что говорил об этом.

Или с кодом:

const Trace = Ember.Object.extend({
  wrapped: Ember.computed('x.[]', 'y.[]', {
    get() {
      return {
        x: this.get('x'),
        y: this.get('y'),
      }
    }
  }),
});

export default Ember.Component.extend({
  traces: null,
  tracesAsEmberObjects('traces.[]', {
    get() {
      this.get('traces').map(t => Trace.create(t));
    }
  }),
  oneBigMess: Ember.computed('[email protected]', {
    get() {
      return this.get('tracesAsEmberObjects').reduce((result, item, i, a) => {
        return {
          x: result.x.concat(item.x),
          y: result.x.concat(item.y)
        };
      }, {x:[], y:[]});
    }
  }),
});

на самом деле не имеет значения, что возвращает wrapped , если у него есть правильные ключи зависимости, вы .get() it и имеете его как ключ зависимости [email protected] во внешнем CP. Однако я предпочитаю вернуть что-нибудь полезное.

@luxferresum Спасибо, что EmberObject с вычисляемым свойством позволяет реагировать на изменения массивов, содержащихся в этом объекте, процесс создания этого объекта, похоже, создает копию исходных данных, поэтому изменение исходных данных делает не запускать CP / наблюдателя.

Например, в аддоне ember-cli-plotly, который я пытаюсь создать, у меня есть интеграционный тест, который изменяет данные, которые нужно построить, и он работает, когда я использую неуклюжий обходной путь, когда основной компонент генерирует дочерний компонент без тегов. для каждой трассировки, которая затем запускает действие при изменении данных в их наборе .

Я попробую ваш метод еще немного на случай, если я просто делаю что-то не так (очень возможно), но то, как он действует сейчас, заставляет меня думать, что это не так просто, как вы предлагаете.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги