Tengo una matriz de valores verdaderos / falsos / indefinidos que estoy representando como una lista de casillas de verificación.
Al cambiar un elemento de la matriz a o desde verdadero, la lista de casillas de verificación se vuelve a representar con la siguiente casilla de verificación (índice + 1) heredando el cambio junto con la casilla de verificación modificada.
Código:
{{#each range as |value idx|}}
<label><input type="checkbox" checked={{value}} {{action makeChange idx on="change"}}>{{idx}}: {{value}}</label><br/>
{{/each}}
Cuando uso {{#each range key="@index" as |value idx|}}
funciona correctamente.
Twiddle: https://ember-twiddle.com/6d63548f35f99da19cee9f58fb64db59
@andrewtimberlake parece que usar {{#each range key="@index" as |value idx|}}
soluciona el problema.
Pero parece un error, el key
es para un propósito diferente, https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/if?anchor= cada
Creo que sé lo que está pasando aquí. Es un desastre pero intentaré describirlo. A esto contribuyeron muchos casos extremos (error de usuario de la línea límite), y no estoy realmente seguro de qué es / no es un error, qué y cómo solucionarlos.
Primero que nada, necesito describir lo que hace el parámetro key
en {{#each}}
. TL; DR está tratando de determinar cuándo y si tendría sentido reutilizar el DOM existente, en lugar de simplemente crear el DOM desde cero.
Para nuestro propósito, aceptemos como un hecho que "tocar DOM" (por ejemplo, actualizar el contenido de un nodo de texto, un atributo, agregar o eliminar contenido, etc.) es costoso y debe evitarse tanto como sea posible.
Centrémonos en una plantilla bastante simple:
<ul>
{{#each this.names as |name|}}
<li>{{name.first}} {{to-upper-case name.last}}</li>
{{/each}}
</ul>
Si this.names
es ...
[
{ first: "Yehuda", last: "Katz" },
{ first: "Tom", last: "Dale" },
{ first: "Godfrey", last: "Chan" }
]
Entonces obtendrás ...
<ul>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
</ul>
Hasta aquí todo bien.
Ahora, ¿qué pasa si agregamos { first: "Andrew", last: "Timberlake" }
a la lista? Esperaríamos que la plantilla produjera el siguiente DOM:
<ul>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
<li>Andrew TIMBERLAKE</li>
</ul>
Pero cómo_?
La forma más ingenua de implementar el asistente {{#each}}
borraría todo el contenido de la lista cada vez que cambia el contenido de la lista. Para hacer esto, necesitaría realizar _ al menos_ 23 operaciones:
<li>
nodos<li>
nodosto-upper-case
4 vecesEsto parece ... muy innecesario y caro. _Sabemos_ que los primeros tres elementos no cambiaron, por lo que sería bueno si pudiéramos omitir el trabajo para esas filas.
Una mejor implementación sería intentar reutilizar las filas existentes y no hacer actualizaciones innecesarias. Una idea sería simplemente hacer coincidir las filas con sus posiciones en las plantillas. Esto es esencialmente lo que hace key="@index"
:
{ first: "Yehuda", last: "Katz" }
con la primera fila, <li>Yehuda KATZ</li>
:to-upper-case
y, por lo tanto, conocemos la salida de ese ayudante ("KATZ" ) _también_ no cambió, así que no hay nada que hacer aquí<li>
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")Entonces, con esta implementación, redujimos el número total de operaciones de 23 a 5 (👋 agitando la mano sobre el costo de las comparaciones, pero para nuestro propósito, asumimos que son relativamente baratas en comparación con el resto). No está mal.
Pero ahora, ¿qué pasaría si, en lugar de _anexar_ { first: "Andrew", last: "Timberlake" }
a la lista, lo _prependemos_ en su lugar? Esperaríamos que la plantilla produjera el siguiente DOM:
<ul>
<li>Andrew TIMBERLAKE</li>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
</ul>
Pero cómo_?
{ first: "Andrew", last: "Timberlake" }
con la primera fila, <li>Yehuda KATZ</li>
:to-upper-case
{ first: "Yehuda", last: "Katz" }
con la segunda fila, <li>Tom DALE</li>
, otras 3 operaciones{ first: "Tom", last: "Dale" }
con la segunda fila, <li>Godfrey CHAN</li>
, otras 3 operaciones<li>
to-upper-case
helper ("Chan" -> "CHAN")Son 14 operaciones. ¡Ay!
Eso parecía innecesario, porque conceptualmente, ya sea que estemos anteponiendo o agregando, todavía solo estamos cambiando (insertando) un solo objeto en la matriz. De manera óptima, deberíamos poder manejar este caso tan bien como lo hicimos en el escenario de adición.
Aquí es donde entra key="@identity"
. En lugar de confiar en el _orden_ de los elementos en la matriz, usamos su identidad de objeto JavaScript ( ===
):
===
) con el primer objeto { first: "Andrew", last: "Timberlake" }
. Como no se encontró nada, inserte (anteponga) una nueva fila:<li>
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")===
) con el segundo objeto { first: "Yehuda", last: "Katz" }
. Encontrado <li>Yehuda KATZ</li>
:to-upper-case
y, por lo tanto, conocemos la salida de ese ayudante ("KATZ" ) _también_ no cambió, así que no hay nada que hacer aquíCon eso, volvemos a las 5 operaciones óptimas.
Una vez más, se trata de hacer un gesto con la mano sobre las comparaciones y los costos de contabilidad. De hecho, esos tampoco son gratuitos y, en este ejemplo muy simple, puede que no valgan la pena. Pero imagine que la lista es grande y cada fila invoca un componente complicado (con muchos ayudantes, propiedades calculadas, subcomponentes, etc.). Imagínese el servicio de noticias de LinkedIn, por ejemplo. Si no hacemos coincidir las filas correctas con los datos correctos, los argumentos de sus componentes pueden potencialmente agitarse mucho y provocar muchas más actualizaciones DOM de las que podría esperar. También hay problemas para hacer coincidir los elementos DOM incorrectos y perder el estado del DOM, como la posición del cursor y el estado de selección de texto.
En general, el costo adicional de comparación y contabilidad vale la pena la mayor parte del tiempo en una aplicación del mundo real. Dado que key="@identity"
es el valor predeterminado en Ember y funciona bien en casi todos los casos, normalmente no tendrá que preocuparse por configurar el argumento key
cuando utilice {{#each}}
.
Pero espere, hay un problema. ¿Y este caso?
const YEHUDA = { first: "Yehuda", last: "Katz" };
const TOM = { first: "Tom", last: "Dale" };
const GODFREY = { first: "Godfrey", last: "Chan" };
this.list = [
YEHUDA,
TOM,
GODFREY,
TOM, // duplicate
YEHUDA, // duplicate
YEHUDA, // duplicate
YEHUDA // duplicate
];
El problema aquí es que el mismo objeto _podría_ aparecer varias veces en la misma lista. Esto rompe nuestro ingenuo algoritmo @identity
, específicamente la parte en la que dijimos "Encuentra una fila existente cuyos datos coincidan ( ===
) ..."; esto solo funciona si la relación de datos a DOM es 1 : 1, que no es cierto en este caso. Esto puede parecer poco probable en la práctica, pero como marco, tenemos que manejarlo.
Para evitar esto, utilizamos una especie de enfoque híbrido para manejar estas colisiones. Internamente, la asignación de claves a DOM se parece a esto:
"YEHUDA" => <li>Yehuda...</li>
"TOM" => <li>Tom...</li>
"GODFREY" => <li>Godfrey...</li>
"TOM-1" => <li>Tom...</li>
"YEHUDA-1" => <li>Yehuda...</li>
"YEHUDA-2" => <li>Yehuda...</li>
"YEHUDA-3" => <li>Yehuda...</li>
En su mayor parte, esto _es_ bastante raro, y cuando aparece, funciona Good Enough ™ la mayor parte del tiempo. Si, por alguna razón, esto no funciona, siempre puede usar una ruta de clave (o el mecanismo de clave aún más avanzado en RFC 321 ).
Después de toda esa charla, ahora estamos listos para ver el escenario en Twiddle.
Básicamente, comenzamos con esta lista: [undefined, undefined, undefined, undefined, undefined]
.
Nota no relacionada:
Array(5)
_no_ es lo mismo que[undefined, undefined, undefined, undefined, undefined]
. Produce una "matriz perforada" que es algo que debe evitar en general. Sin embargo, no está relacionado con este error, porque al acceder a los "agujeros", de hecho, obtiene unundefined
vuelta. Así que para nuestro propósito _muy limitado_ solamente, son lo mismo.
Como no especificamos la clave, Ember usa @identity
por defecto. Además, dado que son colisiones, terminamos con algo como esto:
"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...
Ahora, digamos que hacemos clic en la primera casilla de verificación:
{{action}}
y reenviado al método makeChange
[true, undefined, undefined, undefined, undefined]
.¿Cómo se actualiza el DOM?
===
) con el primer objeto true
. Como no se encontró nada, inserte (anteponga) una nueva fila <input checked=true ...>0: true...
===
) con el segundo objeto undefined
. Encontrado <input ...>0: ...
(anteriormente la PRIMERA fila):{{idx}}
a 1
===
) con el tercer objeto undefined
. Como esta es la segunda vez que vimos undefined
, la clave interna es undefined-1
, por lo que encontramos <input ...>1: ...
(anteriormente la SEGUNDA fila):{{idx}}
a 2
undefined-2
y undefined-3
undefined-4
sin igual (ya que hay un undefined
en la matriz después de la actualización)Así que esto explica cómo obtuvimos el resultado que tenías en el twiddle. Esencialmente, todas las filas DOM se desplazaron hacia abajo en una, y se insertó una nueva en la parte superior, mientras que {{idx}}
se actualiza para el resto.
La parte realmente inesperada es 2.2. Aunque la primera casilla de verificación (en la que se hizo clic) se desplazó una fila hacia abajo hasta la segunda posición, probablemente habría esperado que Ember en su propiedad checked
haya cambiado a true
, y dado que su valor límite no está definido, puede esperar que Ember lo cambie de nuevo a false
, desmarcándolo.
Pero no es así como funciona. Como se mencionó al principio, acceder a DOM es caro. Esto incluye _lectura_ de DOM. Si, en cada actualización, tuviéramos que leer el último valor del DOM para nuestras comparaciones, prácticamente frustraría el propósito de nuestras optimizaciones. Por lo tanto, para evitar eso, recordamos el último valor que habíamos escrito en el DOM y comparamos el valor actual con el valor en caché sin tener que volver a leerlo desde el DOM. Solo cuando hay una diferencia, escribimos el nuevo valor en el DOM (y lo almacenamos en caché para la próxima vez). Este es el sentido en el que compartimos el mismo enfoque de "DOM virtual", pero solo lo hacemos en los nodos hoja, sin virtualizar el "árbol" de todo el DOM.
Entonces, TL; DR, "vincular" la propiedad checked
(o la propiedad value
de un campo de texto, etc.) no funciona realmente de la forma esperada. Imagínese si renderizó <div>{{this.name}}</div>
y actualizó manualmente el textContent
del elemento div
usando jQuery
o con el inspector de Chrome. No habrías esperado que Ember se diera cuenta de eso y actualizara this.name
por ti. Esto es básicamente lo mismo: dado que la actualización de la propiedad checked
ocurrió fuera de Ember (a través del comportamiento predeterminado del navegador para la casilla de verificación), Ember no va a saber nada de eso.
Por eso existe el asistente {{input}}
. Tiene que registrar los oyentes de eventos relevantes en el elemento HTML subyacente y reflejar las operaciones en el cambio de propiedad apropiado, de modo que las partes interesadas (por ejemplo, la capa de representación) puedan ser notificadas.
No estoy seguro de dónde nos deja eso. Entiendo por qué esto es sorprendente, pero me inclino a decir que se trata de una serie de errores de usuario desafortunados. ¿Quizás deberíamos estar enfrascados en no vincular estas propiedades a los elementos de entrada?
Código relevante:
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L390-L391
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L436-L445
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L451-L466
@chancancode : gracias por la increíble explicación. ¿Significa eso que nunca se debe usar <input ... >
sino solo {{input ...}}
para evitar errores como ese?
@ boris-petrov puede haber algunos casos limitados en los que sea aceptable ... como un campo de texto de solo lectura para "copiar esta URL en el portapapeles", o puede_ usar el elemento de entrada + {{action}}
para interceptar el DOM y reflejan las actualizaciones de propiedades manualmente (que es lo que intentó hacer el twiddle, excepto que también se encontró con la colisión @identity
), pero sí, en algún momento, simplemente está reimplementando {{input}}
y manejando todos los casos extremos que ya manejó por usted. Así que creo que es _probablemente_ justo decir que debería usar {{input}}
mayoría de las veces, si no todas.
Sin embargo, eso todavía no habría "arreglado" este caso donde hay colisiones con las llaves. Consulte https://ember-twiddle.com/0f2369021128e2ae0c445155df5bb034?openFiles=templates.application.hbs%2C
Por eso dije, no estoy 100% seguro de qué hacer al respecto. Por un lado, estoy de acuerdo en que es sorprendente e inesperado, por otro lado, este tipo de colisión es bastante raro en aplicaciones reales y es por eso que el argumento "clave" es personalizable (este es un caso en el que la clave predeterminada "@identity" función no es Good Enough ™, por lo que existe esa función).
@chancancode : esto me recuerda otro problema que abrí hace algún tiempo . ¿Crees que hay algo parecido ahí? La respuesta que recibí allí (sobre la necesidad de usar replace
lugar de set
al configurar elementos de matriz) todavía me parece extraña.
@ boris-petrov no creo que esté relacionado
hola, usamos sortablejs para la lista arrastrable con ember. Por favor, consulte esta demostración para reproducir cada problema.
paso:
puede ver que el elemento arrastrado permanece en el árbol dom.
pero, si arrastra el elemento a otra posición (no al último elemento), parece que funciona bien.
Comentario más útil
Creo que sé lo que está pasando aquí. Es un desastre pero intentaré describirlo. A esto contribuyeron muchos casos extremos (error de usuario de la línea límite), y no estoy realmente seguro de qué es / no es un error, qué y cómo solucionarlos.
Mayor 🔑
Primero que nada, necesito describir lo que hace el parámetro
key
en{{#each}}
. TL; DR está tratando de determinar cuándo y si tendría sentido reutilizar el DOM existente, en lugar de simplemente crear el DOM desde cero.Para nuestro propósito, aceptemos como un hecho que "tocar DOM" (por ejemplo, actualizar el contenido de un nodo de texto, un atributo, agregar o eliminar contenido, etc.) es costoso y debe evitarse tanto como sea posible.
Centrémonos en una plantilla bastante simple:
Si
this.names
es ...Entonces obtendrás ...
Hasta aquí todo bien.
Agregar un elemento a la lista
Ahora, ¿qué pasa si agregamos
{ first: "Andrew", last: "Timberlake" }
a la lista? Esperaríamos que la plantilla produjera el siguiente DOM:Pero cómo_?
La forma más ingenua de implementar el asistente
{{#each}}
borraría todo el contenido de la lista cada vez que cambia el contenido de la lista. Para hacer esto, necesitaría realizar _ al menos_ 23 operaciones:<li>
nodos<li>
nodosto-upper-case
4 vecesEsto parece ... muy innecesario y caro. _Sabemos_ que los primeros tres elementos no cambiaron, por lo que sería bueno si pudiéramos omitir el trabajo para esas filas.
🔑 @index
Una mejor implementación sería intentar reutilizar las filas existentes y no hacer actualizaciones innecesarias. Una idea sería simplemente hacer coincidir las filas con sus posiciones en las plantillas. Esto es esencialmente lo que hace
key="@index"
:{ first: "Yehuda", last: "Katz" }
con la primera fila,<li>Yehuda KATZ</li>
:1.1. "Yehuda" === "Yehuda", nada que hacer
1.2. (el espacio no contiene datos dinámicos, por lo que no se necesita comparación)
1.3. "Katz" === "Katz", dado que los ayudantes son "puros", sabemos que no tendremos que volver a invocar el ayudante
to-upper-case
y, por lo tanto, conocemos la salida de ese ayudante ("KATZ" ) _también_ no cambió, así que no hay nada que hacer aquí3.1. Inserte un nodo
<li>
3.2. Insertar un nodo de texto ("Andrew")
3.3. Insertar un nodo de texto (el espacio)
3.4. Invocar el
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")3.5. Insertar un nodo de texto ("TIMBERLAKE")
Entonces, con esta implementación, redujimos el número total de operaciones de 23 a 5 (👋 agitando la mano sobre el costo de las comparaciones, pero para nuestro propósito, asumimos que son relativamente baratas en comparación con el resto). No está mal.
Agregar un elemento a la lista
Pero ahora, ¿qué pasaría si, en lugar de _anexar_
{ first: "Andrew", last: "Timberlake" }
a la lista, lo _prependemos_ en su lugar? Esperaríamos que la plantilla produjera el siguiente DOM:Pero cómo_?
{ first: "Andrew", last: "Timberlake" }
con la primera fila,<li>Yehuda KATZ</li>
:1.1. "Andrew"! == "Yehuda", actualiza el nodo de texto
1.2. (el espacio no contiene datos dinámicos, por lo que no se necesita comparación)
1.3. "Timberlake"! == "Katz", vuelve a invocar el ayudante
to-upper-case
1.4. Actualiza el nodo de texto de "KATZ" a "TIMBERLAKE"
{ first: "Yehuda", last: "Katz" }
con la segunda fila,<li>Tom DALE</li>
, otras 3 operaciones{ first: "Tom", last: "Dale" }
con la segunda fila,<li>Godfrey CHAN</li>
, otras 3 operaciones3.1. Inserte un nodo
<li>
3.2. Insertar un nodo de texto ("Godfrey")
3.3. Insertar un nodo de texto (el espacio)
3.4. Invocar el
to-upper-case
helper ("Chan" -> "CHAN")3.5. Insertar un nodo de texto ("CHAN")
Son 14 operaciones. ¡Ay!
🔑 @identidad
Eso parecía innecesario, porque conceptualmente, ya sea que estemos anteponiendo o agregando, todavía solo estamos cambiando (insertando) un solo objeto en la matriz. De manera óptima, deberíamos poder manejar este caso tan bien como lo hicimos en el escenario de adición.
Aquí es donde entra
key="@identity"
. En lugar de confiar en el _orden_ de los elementos en la matriz, usamos su identidad de objeto JavaScript (===
):===
) con el primer objeto{ first: "Andrew", last: "Timberlake" }
. Como no se encontró nada, inserte (anteponga) una nueva fila:1.1. Inserte un nodo
<li>
1.2. Insertar un nodo de texto ("Andrew")
1.3. Insertar un nodo de texto (el espacio)
1.4. Invocar el
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")1.5. Insertar un nodo de texto ("TIMBERLAKE")
===
) con el segundo objeto{ first: "Yehuda", last: "Katz" }
. Encontrado<li>Yehuda KATZ</li>
:2.1. "Yehuda" === "Yehuda", nada que hacer
2.2. (el espacio no contiene datos dinámicos, por lo que no se necesita comparación)
2.3. "Katz" === "Katz", dado que los ayudantes son "puros", sabemos que no tendremos que volver a invocar el ayudante
to-upper-case
y, por lo tanto, conocemos la salida de ese ayudante ("KATZ" ) _también_ no cambió, así que no hay nada que hacer aquíCon eso, volvemos a las 5 operaciones óptimas.
Ampliar
Una vez más, se trata de hacer un gesto con la mano sobre las comparaciones y los costos de contabilidad. De hecho, esos tampoco son gratuitos y, en este ejemplo muy simple, puede que no valgan la pena. Pero imagine que la lista es grande y cada fila invoca un componente complicado (con muchos ayudantes, propiedades calculadas, subcomponentes, etc.). Imagínese el servicio de noticias de LinkedIn, por ejemplo. Si no hacemos coincidir las filas correctas con los datos correctos, los argumentos de sus componentes pueden potencialmente agitarse mucho y provocar muchas más actualizaciones DOM de las que podría esperar. También hay problemas para hacer coincidir los elementos DOM incorrectos y perder el estado del DOM, como la posición del cursor y el estado de selección de texto.
En general, el costo adicional de comparación y contabilidad vale la pena la mayor parte del tiempo en una aplicación del mundo real. Dado que
key="@identity"
es el valor predeterminado en Ember y funciona bien en casi todos los casos, normalmente no tendrá que preocuparse por configurar el argumentokey
cuando utilice{{#each}}
.Colisiones 💥
Pero espere, hay un problema. ¿Y este caso?
El problema aquí es que el mismo objeto _podría_ aparecer varias veces en la misma lista. Esto rompe nuestro ingenuo algoritmo
@identity
, específicamente la parte en la que dijimos "Encuentra una fila existente cuyos datos coincidan (===
) ..."; esto solo funciona si la relación de datos a DOM es 1 : 1, que no es cierto en este caso. Esto puede parecer poco probable en la práctica, pero como marco, tenemos que manejarlo.Para evitar esto, utilizamos una especie de enfoque híbrido para manejar estas colisiones. Internamente, la asignación de claves a DOM se parece a esto:
En su mayor parte, esto _es_ bastante raro, y cuando aparece, funciona Good Enough ™ la mayor parte del tiempo. Si, por alguna razón, esto no funciona, siempre puede usar una ruta de clave (o el mecanismo de clave aún más avanzado en RFC 321 ).
Volver a la "🐛"
Después de toda esa charla, ahora estamos listos para ver el escenario en Twiddle.
Básicamente, comenzamos con esta lista:
[undefined, undefined, undefined, undefined, undefined]
.Como no especificamos la clave, Ember usa
@identity
por defecto. Además, dado que son colisiones, terminamos con algo como esto:Ahora, digamos que hacemos clic en la primera casilla de verificación:
{{action}}
y reenviado al métodomakeChange
[true, undefined, undefined, undefined, undefined]
.¿Cómo se actualiza el DOM?
===
) con el primer objetotrue
. Como no se encontró nada, inserte (anteponga) una nueva fila<input checked=true ...>0: true...
===
) con el segundo objetoundefined
. Encontrado<input ...>0: ...
(anteriormente la PRIMERA fila):2.1. Actualice el nodo de texto
{{idx}}
a1
2.2. De lo contrario, por lo que Ember puede decir, nada más ha cambiado en esta fila, nada más que hacer.
===
) con el tercer objetoundefined
. Como esta es la segunda vez que vimosundefined
, la clave interna esundefined-1
, por lo que encontramos<input ...>1: ...
(anteriormente la SEGUNDA fila):3.1. Actualice el nodo de texto
{{idx}}
a2
3.2. De lo contrario, por lo que Ember puede decir, nada más ha cambiado en esta fila, nada más que hacer.
undefined-2
yundefined-3
undefined-4
sin igual (ya que hay unundefined
en la matriz después de la actualización)Así que esto explica cómo obtuvimos el resultado que tenías en el twiddle. Esencialmente, todas las filas DOM se desplazaron hacia abajo en una, y se insertó una nueva en la parte superior, mientras que
{{idx}}
se actualiza para el resto.La parte realmente inesperada es 2.2. Aunque la primera casilla de verificación (en la que se hizo clic) se desplazó una fila hacia abajo hasta la segunda posición, probablemente habría esperado que Ember en su propiedad
checked
haya cambiado atrue
, y dado que su valor límite no está definido, puede esperar que Ember lo cambie de nuevo afalse
, desmarcándolo.Pero no es así como funciona. Como se mencionó al principio, acceder a DOM es caro. Esto incluye _lectura_ de DOM. Si, en cada actualización, tuviéramos que leer el último valor del DOM para nuestras comparaciones, prácticamente frustraría el propósito de nuestras optimizaciones. Por lo tanto, para evitar eso, recordamos el último valor que habíamos escrito en el DOM y comparamos el valor actual con el valor en caché sin tener que volver a leerlo desde el DOM. Solo cuando hay una diferencia, escribimos el nuevo valor en el DOM (y lo almacenamos en caché para la próxima vez). Este es el sentido en el que compartimos el mismo enfoque de "DOM virtual", pero solo lo hacemos en los nodos hoja, sin virtualizar el "árbol" de todo el DOM.
Entonces, TL; DR, "vincular" la propiedad
checked
(o la propiedadvalue
de un campo de texto, etc.) no funciona realmente de la forma esperada. Imagínese si renderizó<div>{{this.name}}</div>
y actualizó manualmente eltextContent
del elementodiv
usandojQuery
o con el inspector de Chrome. No habrías esperado que Ember se diera cuenta de eso y actualizarathis.name
por ti. Esto es básicamente lo mismo: dado que la actualización de la propiedadchecked
ocurrió fuera de Ember (a través del comportamiento predeterminado del navegador para la casilla de verificación), Ember no va a saber nada de eso.Por eso existe el asistente
{{input}}
. Tiene que registrar los oyentes de eventos relevantes en el elemento HTML subyacente y reflejar las operaciones en el cambio de propiedad apropiado, de modo que las partes interesadas (por ejemplo, la capa de representación) puedan ser notificadas.No estoy seguro de dónde nos deja eso. Entiendo por qué esto es sorprendente, pero me inclino a decir que se trata de una serie de errores de usuario desafortunados. ¿Quizás deberíamos estar enfrascados en no vincular estas propiedades a los elementos de entrada?