Идентичност в активни масиви от данни

Имам обект от потоци от съобщения, който изглежда така:

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

render_message:

<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