Eu tenho uma matriz de valores verdadeiro / falso / indefinido que estou processando como uma lista de caixas de seleção.
Ao alterar um elemento da matriz para ou de verdadeiro, a lista de caixas de seleção é renderizada novamente com a seguinte caixa de seleção (índice + 1), herdando a alteração junto com a caixa de seleção alterada.
Código:
{{#each range as |value idx|}}
<label><input type="checkbox" checked={{value}} {{action makeChange idx on="change"}}>{{idx}}: {{value}}</label><br/>
{{/each}}
Quando eu uso {{#each range key="@index" as |value idx|}}
ele funciona corretamente.
Twiddle: https://ember-twiddle.com/6d63548f35f99da19cee9f58fb64db59
@andrewtimberlake parece que usar {{#each range key="@index" as |value idx|}}
contorna o problema.
Mas parece um bug, o key
tem um propósito diferente, https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/if?anchor= cada
Acho que sei o que está acontecendo aqui. É uma bagunça, mas vou tentar descrever. Muitos casos extremos (erro do usuário na fronteira) contribuíram para isso, e não tenho certeza do que é / não é um bug, o que e como consertar qualquer um deles.
Em primeiro lugar, preciso descrever o que o parâmetro key
faz em {{#each}}
. TL; DR está tentando determinar quando e se faria sentido reutilizar o DOM existente, em vez de apenas criar o DOM do zero.
Para nosso propósito, vamos aceitar que "tocar no DOM" (por exemplo, atualizar o conteúdo de um nó de texto, um atributo, adicionar ou remover conteúdo etc.) é caro e deve ser evitado tanto quanto possível.
Vamos nos concentrar em um modelo bastante simples:
<ul>
{{#each this.names as |name|}}
<li>{{name.first}} {{to-upper-case name.last}}</li>
{{/each}}
</ul>
Se this.names
for ...
[
{ first: "Yehuda", last: "Katz" },
{ first: "Tom", last: "Dale" },
{ first: "Godfrey", last: "Chan" }
]
Então você vai conseguir ...
<ul>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
</ul>
Por enquanto, tudo bem.
Agora, e se adicionarmos { first: "Andrew", last: "Timberlake" }
à lista? Esperamos que o modelo produza o seguinte DOM:
<ul>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
<li>Andrew TIMBERLAKE</li>
</ul>
Mas como_?
A maneira mais ingênua de implementar o auxiliar {{#each}}
limparia todo o conteúdo da lista sempre que o conteúdo da lista mudasse. Para fazer isso, você precisaria realizar _pelo menos_ 23 operações:
<li>
nodes<li>
nodesto-upper-case
4 vezesIsso parece ... muito desnecessário e caro. _Sabemos_ que os três primeiros itens não mudaram, então seria bom se pudéssemos simplesmente pular o trabalho para essas linhas.
Uma implementação melhor seria tentar reutilizar as linhas existentes e não fazer nenhuma atualização desnecessária. Uma ideia seria simplesmente combinar as linhas com suas posições nos modelos. Isso é essencialmente o que key="@index"
faz:
{ first: "Yehuda", last: "Katz" }
com a primeira linha, <li>Yehuda KATZ</li>
:to-upper-case
e, portanto, sabemos a saída desse ajudante ("KATZ" ) _também_ não mudou, então nada a fazer aqui<li>
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")Portanto, com esta implementação, reduzimos o número total de operações de 23 para 5 (👋 acenando com o custo das comparações, mas para nosso propósito, estamos assumindo que elas são relativamente baratas em comparação com o resto). Não é ruim.
Mas agora, o que aconteceria se, em vez de _appender_ { first: "Andrew", last: "Timberlake" }
à lista, nós o _prendêssemos_? Esperamos que o modelo produza o seguinte DOM:
<ul>
<li>Andrew TIMBERLAKE</li>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
</ul>
Mas como_?
{ first: "Andrew", last: "Timberlake" }
com a primeira linha, <li>Yehuda KATZ</li>
:to-upper-case
{ first: "Yehuda", last: "Katz" }
com a segunda linha, <li>Tom DALE</li>
, outra operação 3{ first: "Tom", last: "Dale" }
com a segunda linha, <li>Godfrey CHAN</li>
, outra 3 operação<li>
to-upper-case
("Chan" -> "CHAN")São 14 operações. Ai!
Isso parecia desnecessário, porque conceitualmente, quer estejamos prefixando ou acrescentando, ainda estamos apenas alterando (inserindo) um único objeto no array. Idealmente, devemos ser capazes de lidar com este caso tão bem como fizemos no cenário de acréscimo.
É aqui que entra key="@identity"
. Em vez de depender da _ordem_ dos elementos no array, usamos sua identidade de objeto JavaScript ( ===
):
===
) ao primeiro objeto { first: "Andrew", last: "Timberlake" }
. Como nada foi encontrado, insira (prefixe) uma nova linha:<li>
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")===
) ao segundo objeto { first: "Yehuda", last: "Katz" }
. Encontrado <li>Yehuda KATZ</li>
:to-upper-case
e, portanto, sabemos a saída desse ajudante ("KATZ" ) _também_ não mudou, então nada a fazer aquiCom isso, estamos de volta às 5 operações ideais.
Novamente, isso é 👋 acenando as comparações e os custos de contabilidade. Na verdade, eles também não são gratuitos e, neste exemplo muito simples, podem não valer a pena. Mas imagine que a lista é grande e cada linha invoca um componente complicado (com muitos auxiliares, propriedades computadas, subcomponentes, etc). Imagine o feed de notícias do LinkedIn, por exemplo. Se não combinarmos as linhas corretas com os dados corretos, os argumentos dos seus componentes podem se alterar muito e causar muito mais atualizações de DOM do que você poderia esperar. Também há problemas em combinar os elementos DOM errados e perder o estado DOM, como a posição do cursor e o estado de seleção de texto.
No geral, o custo extra de comparação e contabilidade vale facilmente a maior parte do tempo em um aplicativo do mundo real. Visto que key="@identity"
é o padrão no Ember e funciona bem para quase todos os casos, você normalmente não precisa se preocupar em definir o argumento key
ao usar {{#each}}
.
Mas espere, há um problema. Que tal 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
];
O problema aqui é que o mesmo objeto _pode_ aparecer várias vezes na mesma lista. Isso quebra nosso algoritmo @identity
ingênuo, especificamente a parte em que dissemos "Encontre uma linha existente cujos dados correspondam ( ===
) ..." - isso só funciona se os dados para o relacionamento DOM forem 1 : 1, o que não é verdade neste caso. Isso pode parecer improvável na prática, mas como estrutura, temos que lidar com isso.
Para evitar isso, usamos uma abordagem híbrida para lidar com essas colisões. Internamente, o mapeamento de chaves para DOM se parece com isto:
"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>
Na maioria das vezes, isso _é_ muito raro e, quando surge, funciona Good Enough ™ na maioria das vezes. Se, por algum motivo, isso não funcionar, você sempre pode usar um caminho de chave (ou o mecanismo de codificação ainda mais avançado no RFC 321 ).
Depois de toda essa conversa, agora estamos prontos para examinar o cenário do Twiddle.
Essencialmente, começamos com esta lista: [undefined, undefined, undefined, undefined, undefined]
.
Nota não relacionada:
Array(5)
_não_ é a mesma coisa que[undefined, undefined, undefined, undefined, undefined]
. Ele produz uma "matriz furada" que é algo que você deve evitar em geral. No entanto, não está relacionado a este bug, porque ao acessar os "buracos" você realmente recebeundefined
volta. Portanto, para o nosso propósito _muito estreito_ apenas, eles são os mesmos.
Como não especificamos a chave, o Ember usa @identity
por padrão. Além disso, como são colisões, acabamos com algo assim:
"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...
Agora, digamos que clicamos na primeira caixa de seleção:
{{action}}
e reenviado para o método makeChange
[true, undefined, undefined, undefined, undefined]
.Como o DOM é atualizado?
===
) ao primeiro objeto true
. Uma vez que nada foi encontrado, insira (prefixe) uma nova linha <input checked=true ...>0: true...
===
) ao segundo objeto undefined
. Encontrado <input ...>0: ...
(anteriormente a PRIMEIRA linha):{{idx}}
para 1
===
) ao terceiro objeto undefined
. Como esta é a segunda vez que vimos undefined
, a chave interna é undefined-1
, então encontramos <input ...>1: ...
(anteriormente a SEGUNDA linha):{{idx}}
para 2
undefined-2
e undefined-3
undefined-4
(uma vez que há um undefined
na matriz após a atualização)Isso explica como obtivemos a saída que você obteve no twiddle. Essencialmente, todas as linhas do DOM foram reduzidas em um, e um novo foi inserido no topo, enquanto {{idx}}
é atualizado para o resto.
A parte realmente inesperada é 2.2. Mesmo que a primeira caixa de seleção (aquela que foi clicada) tenha sido deslocada para baixo em uma linha para a segunda posição, você provavelmente esperaria que Ember sua propriedade checked
mudou para true
, visto que seu valor limite é indefinido, você pode esperar que o Ember altere-o de volta para false
, desmarcando-o.
Mas não é assim que funciona. Como mencionado no início, acessar o DOM é caro. Isso inclui _reading_ de DOM. Se, em cada atualização, tivéssemos que ler o valor mais recente do DOM para nossas comparações, isso praticamente anularia o propósito de nossas otimizações. Portanto, para evitar isso, lembramos o último valor que gravamos no DOM e comparamos o valor atual com o valor armazenado em cache sem ter que lê-lo de volta no DOM. Somente quando há uma diferença, gravamos o novo valor no DOM (e o armazenamos em cache da próxima vez). Este é o sentido em que compartilhamos a mesma abordagem de "DOM virtual", mas apenas o fazemos nos nós folha, não virtualizando a "árvore" de todo o DOM.
Portanto, TL; DR, "vincular" a propriedade checked
(ou a propriedade value
de um campo de texto, etc) realmente não funciona da maneira que você espera. Imagine se você renderizou <div>{{this.name}}</div>
e atualizou manualmente o textContent
do elemento div
usando jQuery
ou com o inspetor de cromo. Você não esperaria que o Ember notasse isso e atualizasse this.name
para você. Basicamente, é a mesma coisa: como a atualização da propriedade checked
aconteceu fora do Ember (por meio do comportamento padrão do navegador para a caixa de seleção), o Ember não saberá disso.
É por isso que o ajudante {{input}}
existe. Ele deve registrar os ouvintes de eventos relevantes no elemento HTML subjacente e refletir as operações na mudança de propriedade apropriada, para que as partes interessadas (por exemplo, a camada de renderização) possam ser notificadas.
Não tenho certeza de onde isso nos deixa. Eu entendo por que isso é surpreendente, mas estou inclinado a dizer que essa é uma série de erros de usuários infelizes. Talvez devêssemos evitar vincular essas propriedades aos 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 - obrigado pela explicação incrível. Isso significa que <input ... >
nunca deve ser usado, mas apenas {{input ...}}
, a fim de evitar todos os erros como esse?
@ boris-petrov pode haver alguns casos limitados em que é aceitável .. como um campo de texto somente leitura para "copiar este url para sua área de transferência", ou você _pode_ usar o elemento de entrada + {{action}}
para interceptar o Evento DOM e refletem as atualizações de propriedade manualmente (que é o que o twiddle tentou fazer, exceto que também gerou a colisão de @identity
), mas sim, em algum momento você está apenas reimplementando {{input}}
e lidar com todos os casos extremos que já tratou para você. Então eu acho que é _provavelmente_ justo dizer que você deve apenas usar {{input}}
maior parte, senão todo, do tempo.
No entanto, isso ainda não teria "consertado" este caso em que há colisões com as chaves. Consulte https://ember-twiddle.com/0f2369021128e2ae0c445155df5bb034?openFiles=templates.application.hbs%2C
É por isso que eu disse que não estou 100% certo do que fazer a respeito. Por um lado, concordo que é surpreendente e inesperado, por outro lado, esse tipo de colisão é muito raro em aplicativos reais e é por isso que o argumento "chave" é personalizável (este é um caso em que a chave "@identity" padrão não é Good Enough ™, é por isso que esse recurso existe).
@chancancode - isso me lembra de outro problema que abri há algum tempo . Você acha que há algo semelhante aí? A resposta que recebi lá (sobre a necessidade de usar replace
vez de set
ao definir elementos de array) ainda parece estranha para mim.
@ boris-petrov não acho que esteja relacionado
oi, usamos sortablejs para lista arrastável com ember. pls verifique esta demonstração para reproduzir cada problema.
degrau:
você pode ver o item arrastado ficar na árvore dom.
mas, se arrastar o item para outra posição (não o último item), parece que funciona bem.
Comentários muito úteis
Acho que sei o que está acontecendo aqui. É uma bagunça, mas vou tentar descrever. Muitos casos extremos (erro do usuário na fronteira) contribuíram para isso, e não tenho certeza do que é / não é um bug, o que e como consertar qualquer um deles.
Maior 🔑
Em primeiro lugar, preciso descrever o que o parâmetro
key
faz em{{#each}}
. TL; DR está tentando determinar quando e se faria sentido reutilizar o DOM existente, em vez de apenas criar o DOM do zero.Para nosso propósito, vamos aceitar que "tocar no DOM" (por exemplo, atualizar o conteúdo de um nó de texto, um atributo, adicionar ou remover conteúdo etc.) é caro e deve ser evitado tanto quanto possível.
Vamos nos concentrar em um modelo bastante simples:
Se
this.names
for ...Então você vai conseguir ...
Por enquanto, tudo bem.
Anexando um item à lista
Agora, e se adicionarmos
{ first: "Andrew", last: "Timberlake" }
à lista? Esperamos que o modelo produza o seguinte DOM:Mas como_?
A maneira mais ingênua de implementar o auxiliar
{{#each}}
limparia todo o conteúdo da lista sempre que o conteúdo da lista mudasse. Para fazer isso, você precisaria realizar _pelo menos_ 23 operações:<li>
nodes<li>
nodesto-upper-case
4 vezesIsso parece ... muito desnecessário e caro. _Sabemos_ que os três primeiros itens não mudaram, então seria bom se pudéssemos simplesmente pular o trabalho para essas linhas.
🔑 @index
Uma implementação melhor seria tentar reutilizar as linhas existentes e não fazer nenhuma atualização desnecessária. Uma ideia seria simplesmente combinar as linhas com suas posições nos modelos. Isso é essencialmente o que
key="@index"
faz:{ first: "Yehuda", last: "Katz" }
com a primeira linha,<li>Yehuda KATZ</li>
:1.1. "Yehuda" === "Yehuda", nada a fazer
1.2. (o espaço não contém dados dinâmicos, portanto, nenhuma comparação necessária)
1.3. "Katz" === "Katz", uma vez que os ajudantes são "puros", sabemos que não teremos que invocar novamente o ajudante
to-upper-case
e, portanto, sabemos a saída desse ajudante ("KATZ" ) _também_ não mudou, então nada a fazer aqui3.1. Insira um nó
<li>
3.2. Insira um nó de texto ("Andrew")
3.3. Insira um nó de texto (o espaço)
3.4. Invoque o
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")3,5. Insira um nó de texto ("TIMBERLAKE")
Portanto, com esta implementação, reduzimos o número total de operações de 23 para 5 (👋 acenando com o custo das comparações, mas para nosso propósito, estamos assumindo que elas são relativamente baratas em comparação com o resto). Não é ruim.
Incluindo um item na lista
Mas agora, o que aconteceria se, em vez de _appender_
{ first: "Andrew", last: "Timberlake" }
à lista, nós o _prendêssemos_? Esperamos que o modelo produza o seguinte DOM:Mas como_?
{ first: "Andrew", last: "Timberlake" }
com a primeira linha,<li>Yehuda KATZ</li>
:1.1. "Andrew"! == "Yehuda", atualize o nó de texto
1.2. (o espaço não contém dados dinâmicos, portanto, nenhuma comparação necessária)
1.3. "Timberlake"! == "Katz", invoque novamente o ajudante
to-upper-case
1.4. Atualize o nó de texto de "KATZ" para "TIMBERLAKE"
{ first: "Yehuda", last: "Katz" }
com a segunda linha,<li>Tom DALE</li>
, outra operação 3{ first: "Tom", last: "Dale" }
com a segunda linha,<li>Godfrey CHAN</li>
, outra 3 operação3.1. Insira um nó
<li>
3.2. Insira um nó de texto ("Godfrey")
3.3. Insira um nó de texto (o espaço)
3.4. Invoque o ajudante
to-upper-case
("Chan" -> "CHAN")3,5. Insira um nó de texto ("CHAN")
São 14 operações. Ai!
🔑 @identity
Isso parecia desnecessário, porque conceitualmente, quer estejamos prefixando ou acrescentando, ainda estamos apenas alterando (inserindo) um único objeto no array. Idealmente, devemos ser capazes de lidar com este caso tão bem como fizemos no cenário de acréscimo.
É aqui que entra
key="@identity"
. Em vez de depender da _ordem_ dos elementos no array, usamos sua identidade de objeto JavaScript (===
):===
) ao primeiro objeto{ first: "Andrew", last: "Timberlake" }
. Como nada foi encontrado, insira (prefixe) uma nova linha:1.1. Insira um nó
<li>
1.2. Insira um nó de texto ("Andrew")
1.3. Insira um nó de texto (o espaço)
1.4. Invoque o
to-upper-case
helper ("Timberlake" -> "TIMBERLAKE")1,5. Insira um nó de texto ("TIMBERLAKE")
===
) ao segundo objeto{ first: "Yehuda", last: "Katz" }
. Encontrado<li>Yehuda KATZ</li>
:2.1. "Yehuda" === "Yehuda", nada a fazer
2.2. (o espaço não contém dados dinâmicos, portanto, nenhuma comparação necessária)
2.3. "Katz" === "Katz", uma vez que os ajudantes são "puros", sabemos que não teremos que invocar novamente o ajudante
to-upper-case
e, portanto, sabemos a saída desse ajudante ("KATZ" ) _também_ não mudou, então nada a fazer aquiCom isso, estamos de volta às 5 operações ideais.
Ampliando
Novamente, isso é 👋 acenando as comparações e os custos de contabilidade. Na verdade, eles também não são gratuitos e, neste exemplo muito simples, podem não valer a pena. Mas imagine que a lista é grande e cada linha invoca um componente complicado (com muitos auxiliares, propriedades computadas, subcomponentes, etc). Imagine o feed de notícias do LinkedIn, por exemplo. Se não combinarmos as linhas corretas com os dados corretos, os argumentos dos seus componentes podem se alterar muito e causar muito mais atualizações de DOM do que você poderia esperar. Também há problemas em combinar os elementos DOM errados e perder o estado DOM, como a posição do cursor e o estado de seleção de texto.
No geral, o custo extra de comparação e contabilidade vale facilmente a maior parte do tempo em um aplicativo do mundo real. Visto que
key="@identity"
é o padrão no Ember e funciona bem para quase todos os casos, você normalmente não precisa se preocupar em definir o argumentokey
ao usar{{#each}}
.Colisões 💥
Mas espere, há um problema. Que tal este caso?
O problema aqui é que o mesmo objeto _pode_ aparecer várias vezes na mesma lista. Isso quebra nosso algoritmo
@identity
ingênuo, especificamente a parte em que dissemos "Encontre uma linha existente cujos dados correspondam (===
) ..." - isso só funciona se os dados para o relacionamento DOM forem 1 : 1, o que não é verdade neste caso. Isso pode parecer improvável na prática, mas como estrutura, temos que lidar com isso.Para evitar isso, usamos uma abordagem híbrida para lidar com essas colisões. Internamente, o mapeamento de chaves para DOM se parece com isto:
Na maioria das vezes, isso _é_ muito raro e, quando surge, funciona Good Enough ™ na maioria das vezes. Se, por algum motivo, isso não funcionar, você sempre pode usar um caminho de chave (ou o mecanismo de codificação ainda mais avançado no RFC 321 ).
Voltar para o "🐛"
Depois de toda essa conversa, agora estamos prontos para examinar o cenário do Twiddle.
Essencialmente, começamos com esta lista:
[undefined, undefined, undefined, undefined, undefined]
.Como não especificamos a chave, o Ember usa
@identity
por padrão. Além disso, como são colisões, acabamos com algo assim:Agora, digamos que clicamos na primeira caixa de seleção:
{{action}}
e reenviado para o métodomakeChange
[true, undefined, undefined, undefined, undefined]
.Como o DOM é atualizado?
===
) ao primeiro objetotrue
. Uma vez que nada foi encontrado, insira (prefixe) uma nova linha<input checked=true ...>0: true...
===
) ao segundo objetoundefined
. Encontrado<input ...>0: ...
(anteriormente a PRIMEIRA linha):2.1. Atualize o nó de texto
{{idx}}
para1
2.2. Do contrário, pelo que Ember sabe, nada mais mudou nesta linha, nada mais a fazer
===
) ao terceiro objetoundefined
. Como esta é a segunda vez que vimosundefined
, a chave interna éundefined-1
, então encontramos<input ...>1: ...
(anteriormente a SEGUNDA linha):3.1. Atualize o nó de texto
{{idx}}
para2
3.2. Do contrário, pelo que Ember sabe, nada mais mudou nesta linha, nada mais a fazer
undefined-2
eundefined-3
undefined-4
(uma vez que há umundefined
na matriz após a atualização)Isso explica como obtivemos a saída que você obteve no twiddle. Essencialmente, todas as linhas do DOM foram reduzidas em um, e um novo foi inserido no topo, enquanto
{{idx}}
é atualizado para o resto.A parte realmente inesperada é 2.2. Mesmo que a primeira caixa de seleção (aquela que foi clicada) tenha sido deslocada para baixo em uma linha para a segunda posição, você provavelmente esperaria que Ember sua propriedade
checked
mudou paratrue
, visto que seu valor limite é indefinido, você pode esperar que o Ember altere-o de volta parafalse
, desmarcando-o.Mas não é assim que funciona. Como mencionado no início, acessar o DOM é caro. Isso inclui _reading_ de DOM. Se, em cada atualização, tivéssemos que ler o valor mais recente do DOM para nossas comparações, isso praticamente anularia o propósito de nossas otimizações. Portanto, para evitar isso, lembramos o último valor que gravamos no DOM e comparamos o valor atual com o valor armazenado em cache sem ter que lê-lo de volta no DOM. Somente quando há uma diferença, gravamos o novo valor no DOM (e o armazenamos em cache da próxima vez). Este é o sentido em que compartilhamos a mesma abordagem de "DOM virtual", mas apenas o fazemos nos nós folha, não virtualizando a "árvore" de todo o DOM.
Portanto, TL; DR, "vincular" a propriedade
checked
(ou a propriedadevalue
de um campo de texto, etc) realmente não funciona da maneira que você espera. Imagine se você renderizou<div>{{this.name}}</div>
e atualizou manualmente otextContent
do elementodiv
usandojQuery
ou com o inspetor de cromo. Você não esperaria que o Ember notasse isso e atualizassethis.name
para você. Basicamente, é a mesma coisa: como a atualização da propriedadechecked
aconteceu fora do Ember (por meio do comportamento padrão do navegador para a caixa de seleção), o Ember não saberá disso.É por isso que o ajudante
{{input}}
existe. Ele deve registrar os ouvintes de eventos relevantes no elemento HTML subjacente e refletir as operações na mudança de propriedade apropriada, para que as partes interessadas (por exemplo, a camada de renderização) possam ser notificadas.Não tenho certeza de onde isso nos deixa. Eu entendo por que isso é surpreendente, mas estou inclinado a dizer que essa é uma série de erros de usuários infelizes. Talvez devêssemos evitar vincular essas propriedades aos elementos de entrada?