Идентичность в Ractive массивах данных

У меня есть объект потоков сообщений, который выглядит так:

ractive.data.messages:

{
    stream_id1: {
        some_stream_metadata: "foo",
        stream: [  
            {id: "someid1", message: "message1"}, 
            {id: "someid2", message: "message2"}
        ]
    },
    stream_id2: {
        some_stream_metadata: "bar",
        stream: [
            {id: "someid3", message: "message3"},
            {id: "someid4", message: "message4"}
        ]
    }
}

основной_шаблон:

{{#messages[ current_stream_id ]}}
    {{>render_message_stream}}
{{/messages[ current_stream_id ]}}

render_message_stream:

{{#stream}}
    <div class="stream">
    ...someotherstuff...
        {{>render_message}}
    </div>
{{/stream}}

рендер_сообщение:

<div class="message">
...someotherstuff...
    {{message}}
</div>

Я меняю «current_stream_id», чтобы изменить отображаемый поток сообщений.

При обновлениях я меняю содержимое потоков сообщений следующим образом:

ractive.merge(
    "messages.stream_id1.stream",
    new_message_stream,
    {
        compare: function ( item ) { return item.id; }
    });

Я также попробовал параметр compare: true вместо функции с теми же результатами:

Ractive всегда считает, что эти два сообщения принадлежат одному и тому же элементу DOM, даже если они находятся в совершенно другом потоке сообщений:

ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message

Проблемы:

  • Когда есть анимация вступления / завершения, ractive всегда анимирует только конец потока сообщений, даже если сообщение в середине потока было удалено, мне нужна помощь, чтобы заставить ractive понять, какие сообщения идентичны.

  • Когда я меняю current_stream_id, ractive не выполняет повторную визуализацию полного фрагмента {{>render_message_stream}}, а переходит внутрь существующего dom и изменяет поле {{message}} во всех существующих сообщениях, хотя это может быть полезно для повторного использования элемента dom, это вызывает много неправильных анимаций. (Например, он запускает анимацию вступления/конца для последнего сообщения в потоке, если в потоке 1 на одно сообщение больше, чем в потоке 2).


person jan    schedule 02.02.2014    source источник


Ответы (1)


На один из этих вопросов есть прямой ответ; к сожалению другой нет.

Начну с простого - с того, что

ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message

принадлежат одному и тому же элементу DOM. Вы правы в том, что Ractive обновляет существующие элементы, а не удаляет их и создает новые — это основная часть его дизайна. В данном случае это нежелательное поведение, но вы можете обойти это следующим образом:

// instead of immediately switching to a new stream ID like this...
ractive.set( 'current_stream_id', 'stream_id2' );

// you can set it to a non-existent ID. That will cause the existing DOM
// to be removed. When you set it to an ID that *does* exist, new DOM
// will be created:
ractive.set( 'current_stream_id', null );
ractive.set( 'current_stream_id', 'stream_id2' );

// or, if you'd like the initial transitions to complete first...
ractive.set( 'current_stream_id', null ).then(function () {
  ractive.set( 'current_stream_id', 'stream_id2' );
});

Другая проблема — то, что merge() не сливается, а вместо этого ведет себя так, как если бы вы делали ractive.set('messages.stream_id1.stream', new_message_stream), — сложнее. Проблема в том, что хотя мы с вами знаем, что {{#messages[ current_stream_id ]}} равно messages.stream_id1, когда current_stream_id === 'stream_id1, Ractive нет.

Что он знает, так это то, что у нас есть выражение, значение которого определяется messages и current_stream_id. Когда значение любой из этих ссылок изменяется, выражение оценивается повторно, и если изменяется это значение, DOM обновляется, но с использованием стандартного set(). Когда вы выполняете ractive.merge('messages.stream_id1.stream', ...), Ractive обновляет все, что зависит от ключевых путей, которые находятся «вверх по течению» или «вниз по течению» от messages.stream_id1.stream, включая messages. Таким образом, выражение узнает, что его нужно переоценить.

Вполне возможно, что будущая версия Ractive сможет более разумно справиться с этим случаем. Возможно, он мог бы отметить массивы, которые подлежат операциям слияния, и проверить результаты оценки, чтобы увидеть, идентичны ли они одному из этих массивов, и если да, использовать merge(), а не set(). Возможно, он мог бы каким-то образом проанализировать функцию, чтобы увидеть, должен ли раздел {{#messages[ current_stream_id ]}} регистрироваться как зависимый от messages.stream_id1 до тех пор, пока current_stream_id === 'stream_id1', а не сгенерированный внутри ключевой путь ${messages-current_stream_id-}.

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

основной_шаблон:

{{#current_messages}} <!-- rather than `messages[ current_stream_id ]` -->
    {{>render_message_stream}}
{{/current_messages}}

render_message_stream:

{{#current_message_stream}} <!-- rather than `stream` -->
    <div class="stream">
        {{>render_message}}
    </div>
{{/current_message_stream}}

код:

ractive.observe( 'current_stream_id', function ( id ) {
    var current_messages = this.get( 'messages.' + id );

    this.set( 'current_messages', current_messages );

    // hide existing stream, then show new stream
    this.set( 'current_message_stream', null ).then(function () {
        this.set( 'current_message_stream', current_messages.stream );
    });
});

// when ANY message stream changes, we see if it's the current one - if so, we
// perform a merge on the top-level `current_message_stream` array
ractive.observe( 'messages.*.stream', function ( new_stream, old_stream, keypath, id ) {
    // the value of any * characters are passed in as extra arguments, hence `id`

    if ( id === this.get( 'current_stream_id' ) ) {
        this.merge( 'current_message_stream', new_stream, {
            compare: function ( item ) {
                return item.id;
            }
        });
    }
});

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

person Rich Harris    schedule 21.02.2014
comment
Для дальнейшего справки: с последним Ractive строка var id_of_changed_stream = /messages\.([^\.]+)\.stream/.exec( keypath )[1]; не нужна, идентификатор, соответствующий подстановочному знаку * в messages.*.stream, будет передан в качестве дополнительного аргумента для обратного вызова. Дополнительные сведения см. в нижнем абзаце наблюдателей шаблонов. - person pembeci; 14.05.2015