Ember.js: #each重新呈现错误

创建于 2018-09-14  ·  8评论  ·  资料来源: emberjs/ember.js

我有一个true / false / undefined值数组,我将它们渲染为复选框列表。
在将数组元素更改为true或从true更改为数组元素时,将使用以下(index + 1)复选框重新呈现复选框列表,该复选框继承更改以及已更改的复选框。
码:

{{#each range as |value idx|}}
  <label><input type="checkbox" checked={{value}} {{action makeChange idx on="change"}}>{{idx}}: {{value}}</label><br/>
{{/each}}

当我使用{{#each range key="@index" as |value idx|}}它可以正常工作。

扭蛋: https ://ember-twiddle.com/6d63548f35f99da19cee9f58fb64db59

embereach

Bug Has Reproduction Rendering

最有用的评论

我想我知道这是怎么回事。 这真是一团糟,但我会尽力描述。 许多边缘情况(边界线用户错误)导致了这种情况,我不确定是什么/不是错误,什么以及如何修复这些错误。

🔑

首先,我需要描述key参数在{{#each}} 。 TL; DR试图确定何时以及是否应该重用现有DOM,而不是仅仅从头开始创建DOM。

出于我们的目的,让我们接受一个“接触DOM”(例如,更新文本节点的内容,属性,添加或删除内容等)是昂贵的,应尽可能避免。

让我们关注一个相当简单的模板:

<ul>
  {{#each this.names as |name|}}
    <li>{{name.first}} {{to-upper-case name.last}}</li>
  {{/each}}
</ul>

如果this.names是...

[
  { first: "Yehuda", last: "Katz" },
  { first: "Tom", last: "Dale" },
  { first: "Godfrey", last: "Chan" }
]

然后你会得到...

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

到现在为止还挺好。

将项目追加到列表

现在,如果我们将{ first: "Andrew", last: "Timberlake" }追加到列表中怎么办? 我们希望模板产生以下DOM:

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
  <li>Andrew TIMBERLAKE</li>
</ul>

但是_如何_?

实现{{#each}}助手的最幼稚的方法是每次列表内容更改时都清除列表的所有内容。 为此,您至少需要执行23个操作:

  • 删除3个<li>节点
  • 插入4个<li>节点
  • 插入12个文本节点(一个用于名字,一个用于中间的空格,一个用于姓氏,乘以4行)
  • 调用to-upper-case助手4次

这似乎...非常不必要和昂贵。 我们知道前三个项目没有改变,所以如果我们只跳过那些行的工作,那就太好了。

index @index

更好的实现方式是尝试重用现有行,并且不执行任何不必要的更新。 一种想法是简单地将行与其在模板中的位置进行匹配。 这实际上是key="@index"作用:

  1. 将第一个对象{ first: "Yehuda", last: "Katz" }与第一行<li>Yehuda KATZ</li>
    1.1。 “ Yehuda” ===“ Yehuda”,无关
    1.2。 (该空间不包含动态数据,因此无需进行比较)
    1.3。 “ Katz” ===“ Katz”,由于助手是“纯”的,我们知道我们不必重新调用to-upper-case助手,因此我们知道该助手的输出(“ KATZ” )_also_没变,所以这里无事可做
  2. 同样,第2行和第3行也不用做
  3. DOM中没有第四行,因此插入一个新行
    3.1。 插入<li>节点
    3.2。 插入一个文本节点(“ Andrew”)
    3.3。 插入文本节点(空格)
    3.4。 调用to-upper-case助手(“ Timberlake”->“ TIMBERLAKE”)
    3.5。 插入文本节点(“ TIMBERLAKE”)

因此,通过此实现,我们将操作总数从23个减少到了5个(比较费时费力,但是出于我们的目的,我们假设它们与其余的相比是相对便宜的)。 不错。

在列表前添加项目

但是,现在,如果我们而不是将{ first: "Andrew", last: "Timberlake" }附加到列表中,而是将其添加到列表中,将会发生什么情况呢? 我们希望模板产生以下DOM:

<ul>
  <li>Andrew TIMBERLAKE</li>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

但是_如何_?

  1. 将第一个对象{ first: "Andrew", last: "Timberlake" }与第一行<li>Yehuda KATZ</li>
    1.1。 “ Andrew”!==“ Yehuda”,更新文本节点
    1.2。 (该空间不包含动态数据,因此无需进行比较)
    1.3。 “ Timberlake”!==“ Katz”,重新调用to-upper-case帮助器
    1.4。 将文本节点从“ KATZ”更新为“ TIMBERLAKE”
  2. 将第二个对象{ first: "Yehuda", last: "Katz" }与第二行<li>Tom DALE</li> ,另外3个操作
  3. 将第二个对象{ first: "Tom", last: "Dale" }与第二行<li>Godfrey CHAN</li> ,另外3个操作
  4. DOM中没有第四行,因此插入一个新行
    3.1。 插入<li>节点
    3.2。 插入一个文本节点(“ Godfrey”)
    3.3。 插入文本节点(空格)
    3.4。 调用to-upper-case助手(“ Chan”->“ CHAN”)
    3.5。 插入文本节点(“ CHAN”)

那是14次操作。 哎哟!

ident @身份

这似乎没有必要,因为从概念上讲,无论我们是在前面还是在后面,我们仍然只更改(插入)数组中的单个对象。 最佳地,我们应该能够像在追加场景中一样处理这种情况。

这是key="@identity"进入的地方。我们不用依赖数组中元素的_order_,而是使用它们的JavaScript对象标识( === ):

  1. 查找其数据与第一个对象{ first: "Andrew", last: "Timberlake" }匹配( === )的现有行。 由于未找到任何内容,因此插入(添加)新行:
    1.1。 插入<li>节点
    1.2。 插入一个文本节点(“ Andrew”)
    1.3。 插入文本节点(空格)
    1.4。 调用to-upper-case助手(“ Timberlake”->“ TIMBERLAKE”)
    1.5。 插入文本节点(“ TIMBERLAKE”)
  2. 查找数据与第二个对象{ first: "Yehuda", last: "Katz" }匹配( === )的现有行。 找到<li>Yehuda KATZ</li>
    2.1。 “ Yehuda” ===“ Yehuda”,无关
    2.2。 (该空间不包含动态数据,因此无需进行比较)
    2.3。 “ Katz” ===“ Katz”,由于助手是“纯”的,我们知道我们不必重新调用to-upper-case助手,因此我们知道该助手的输出(“ KATZ” )_also_没变,所以这里无事可做
  3. 同样,与汤姆和戈弗雷的行无关
  4. 删除所有具有不匹配对象的行(无,因此在这种情况下无需执行任何操作)

这样,我们回到了最佳的5种操作。

扩大

同样,这是比较和簿记成本的👋手。 确实,它们也不是免费的,在这个非常简单的示例中,它们可能不值得。 但是,想象一下这个列表很大,并且每一行都调用一个复杂的组件(具有很多帮助器,计算属性,子组件等)。 例如,想象一下LinkedIn新闻提要。 如果我们没有用正确的数据来匹配正确的行,则组件的参数可能会产生很大的混乱,并导致DOM更新超出您的预期。 匹配错误的DOM元素并失去DOM状态(例如光标位置和文本选择状态)也存在问题。

总体而言,在现实世界中的大多数时间里,额外的比较和簿记成本很容易就值得。 由于key="@identity"是Ember中的默认设置,并且在几乎所有情况下都能正常使用,因此在使用{{#each}}时,通常不必担心设置key参数。

碰撞💥

但是,等等,这是一个问题。 那这种情况呢?

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
];

这里的问题是同一对象_could_在同一列表中出现多次。 这打破了我们朴素的@identity算法,特别是我们所说的“查找数据匹配的现有行( === )...”的部分–仅在数据与DOM关系为1的情况下有效:1,在这种情况下不正确。 实际上这似乎不太可能,但是作为一个框架,我们必须处理它。

为了避免这种情况,我们使用一种混合方法来处理这些冲突。 在内部,键到DOM的映射如下所示:

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

在大多数情况下,这种情况很少见,当它出现时,大多数时候都可以使用Good Enough™。 如果由于某种原因这不起作用,则可以始终使用密钥路径(或RFC 321中甚至更高级的密钥机制)。

回到“🐛”

讨论完所有内容之后,我们现在就可以看看Twiddle中的情况。

基本上,我们从以下列表开始: [undefined, undefined, undefined, undefined, undefined]

不相关的注释: Array(5)[undefined, undefined, undefined, undefined, undefined]是同一件事。 它会产生一个“多孔数组”,通常应该避免这种情况。 但是,它与该错误无关,因为在访问“漏洞”时您确实会获得undefined返还。 因此,仅出于我们非常狭窄的目的,它们是相同的。

由于我们未指定密钥,因此Ember默认使用@identity 。 此外,由于它们是碰撞,所以我们最终得到这样的结果:

"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...

现在,假设我们单击第一个复选框:

  1. 它触发选择框的默认行为:将选中状态更改为true
  2. 它触发点击事件,该事件被{{action}}修饰符拦截并重新分配给makeChange方法
  3. 它将列表更改为[true, undefined, undefined, undefined, undefined]
  4. 它更新DOM。

DOM如何更新?

  1. 查找其数据与第一个对象true匹配( === )的现有行。 由于未找到任何内容,因此插入(添加)新行<input checked=true ...>0: true...
  2. 查找数据与第二个对象undefined匹配( === )的现有行。 找到<input ...>0: ... (以前是FIRST行):
    2.1。 将{{idx}}文本节点更新为1
    2.2。 否则,就Ember所知,此行中没有其他更改,没有其他操作
  3. 查找一个数据与第三个对象undefined匹配( === )的现有行。 由于这是我们第二次看到undefined ,内部键是undefined-1 ,因此我们找到了<input ...>1: ... (以前是SECOND行):
    3.1。 将{{idx}}文本节点更新为2
    3.2。 否则,就Ember所知,此行中没有其他更改,没有其他操作
  4. 同样,更新undefined-2undefined-3
  5. 最后,删除不匹配的undefined-4行(因为更新后数组中的undefined

因此,这说明了我们如何获得旋转中的输出。 基本上所有DOM行都向下移动了一个,并在顶部插入了一个新行,而其余的{{idx}}被更新了。

真正出乎意料的部分是2.2。 即使第一个复选框(被单击的复选框)向下移动了一行到第二个位置,您也可能希望将Ember的checked属性更改为true ,并且由于其绑定值是不确定的,因此您可能希望Ember将其更改回false ,从而取消选中它。

但这不是它的工作方式。 如开头所述,访问DOM的成本很高。 这包括DOM中的_reading_。 如果在每次更新时我们都必须从DOM中读取最新值以进行比较,那么这将很不利于优化的目的。 因此,为了避免这种情况,我们记住了我们写入DOM的最后一个值,并将当前值与缓存的值进行比较,而不必从DOM中读取回去。 仅当存在差异时,我们才将新值写入DOM(并在下一次缓存它)。 从某种意义上说,我们有点相同的“虚拟DOM”方法,但是我们只在叶节点执行此操作,而不是虚拟整个DOM的“树形”。

因此,TL; DR“绑定” checked属性(或文本字段的value属性,等等)并不能像您期望的那样工作。 想象一下,如果您渲染了<div>{{this.name}}</div>并使用jQuery或chrome检验器手动更新了textContent div元素的jQuery 。 您不会期望Ember注意到这一点并为您更新this.name 。 这基本上是同一件事:由于对checked属性的更新发生在Ember之外(通过浏览器的复选框默认行为),因此Ember不会知道这一点。

这就是{{input}}助手存在的原因。 它必须在基础HTML元素上注册相关的事件侦听器,并将操作反映到适当的属性更改中,以便可以通知感兴趣的各方(例如,呈现层)。

我不确定那会给我们留下什么。 我理解为什么这令人惊讶,但是我倾向于说这是一系列不幸的用户错误。 也许我们应该反对在输入元素上绑定这些属性?

所有8条评论

@andrewtimberlake似乎使用{{#each range key="@index" as |value idx|}}可以解决此问题。

但是确实看起来像个错误, key是出于不同的目的, https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/if?anchor =每

我想我知道这是怎么回事。 这真是一团糟,但我会尽力描述。 许多边缘情况(边界线用户错误)导致了这种情况,我不确定是什么/不是错误,什么以及如何修复这些错误。

🔑

首先,我需要描述key参数在{{#each}} 。 TL; DR试图确定何时以及是否应该重用现有DOM,而不是仅仅从头开始创建DOM。

出于我们的目的,让我们接受一个“接触DOM”(例如,更新文本节点的内容,属性,添加或删除内容等)是昂贵的,应尽可能避免。

让我们关注一个相当简单的模板:

<ul>
  {{#each this.names as |name|}}
    <li>{{name.first}} {{to-upper-case name.last}}</li>
  {{/each}}
</ul>

如果this.names是...

[
  { first: "Yehuda", last: "Katz" },
  { first: "Tom", last: "Dale" },
  { first: "Godfrey", last: "Chan" }
]

然后你会得到...

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

到现在为止还挺好。

将项目追加到列表

现在,如果我们将{ first: "Andrew", last: "Timberlake" }追加到列表中怎么办? 我们希望模板产生以下DOM:

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
  <li>Andrew TIMBERLAKE</li>
</ul>

但是_如何_?

实现{{#each}}助手的最幼稚的方法是每次列表内容更改时都清除列表的所有内容。 为此,您至少需要执行23个操作:

  • 删除3个<li>节点
  • 插入4个<li>节点
  • 插入12个文本节点(一个用于名字,一个用于中间的空格,一个用于姓氏,乘以4行)
  • 调用to-upper-case助手4次

这似乎...非常不必要和昂贵。 我们知道前三个项目没有改变,所以如果我们只跳过那些行的工作,那就太好了。

index @index

更好的实现方式是尝试重用现有行,并且不执行任何不必要的更新。 一种想法是简单地将行与其在模板中的位置进行匹配。 这实际上是key="@index"作用:

  1. 将第一个对象{ first: "Yehuda", last: "Katz" }与第一行<li>Yehuda KATZ</li>
    1.1。 “ Yehuda” ===“ Yehuda”,无关
    1.2。 (该空间不包含动态数据,因此无需进行比较)
    1.3。 “ Katz” ===“ Katz”,由于助手是“纯”的,我们知道我们不必重新调用to-upper-case助手,因此我们知道该助手的输出(“ KATZ” )_also_没变,所以这里无事可做
  2. 同样,第2行和第3行也不用做
  3. DOM中没有第四行,因此插入一个新行
    3.1。 插入<li>节点
    3.2。 插入一个文本节点(“ Andrew”)
    3.3。 插入文本节点(空格)
    3.4。 调用to-upper-case助手(“ Timberlake”->“ TIMBERLAKE”)
    3.5。 插入文本节点(“ TIMBERLAKE”)

因此,通过此实现,我们将操作总数从23个减少到了5个(比较费时费力,但是出于我们的目的,我们假设它们与其余的相比是相对便宜的)。 不错。

在列表前添加项目

但是,现在,如果我们而不是将{ first: "Andrew", last: "Timberlake" }附加到列表中,而是将其添加到列表中,将会发生什么情况呢? 我们希望模板产生以下DOM:

<ul>
  <li>Andrew TIMBERLAKE</li>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

但是_如何_?

  1. 将第一个对象{ first: "Andrew", last: "Timberlake" }与第一行<li>Yehuda KATZ</li>
    1.1。 “ Andrew”!==“ Yehuda”,更新文本节点
    1.2。 (该空间不包含动态数据,因此无需进行比较)
    1.3。 “ Timberlake”!==“ Katz”,重新调用to-upper-case帮助器
    1.4。 将文本节点从“ KATZ”更新为“ TIMBERLAKE”
  2. 将第二个对象{ first: "Yehuda", last: "Katz" }与第二行<li>Tom DALE</li> ,另外3个操作
  3. 将第二个对象{ first: "Tom", last: "Dale" }与第二行<li>Godfrey CHAN</li> ,另外3个操作
  4. DOM中没有第四行,因此插入一个新行
    3.1。 插入<li>节点
    3.2。 插入一个文本节点(“ Godfrey”)
    3.3。 插入文本节点(空格)
    3.4。 调用to-upper-case助手(“ Chan”->“ CHAN”)
    3.5。 插入文本节点(“ CHAN”)

那是14次操作。 哎哟!

ident @身份

这似乎没有必要,因为从概念上讲,无论我们是在前面还是在后面,我们仍然只更改(插入)数组中的单个对象。 最佳地,我们应该能够像在追加场景中一样处理这种情况。

这是key="@identity"进入的地方。我们不用依赖数组中元素的_order_,而是使用它们的JavaScript对象标识( === ):

  1. 查找其数据与第一个对象{ first: "Andrew", last: "Timberlake" }匹配( === )的现有行。 由于未找到任何内容,因此插入(添加)新行:
    1.1。 插入<li>节点
    1.2。 插入一个文本节点(“ Andrew”)
    1.3。 插入文本节点(空格)
    1.4。 调用to-upper-case助手(“ Timberlake”->“ TIMBERLAKE”)
    1.5。 插入文本节点(“ TIMBERLAKE”)
  2. 查找数据与第二个对象{ first: "Yehuda", last: "Katz" }匹配( === )的现有行。 找到<li>Yehuda KATZ</li>
    2.1。 “ Yehuda” ===“ Yehuda”,无关
    2.2。 (该空间不包含动态数据,因此无需进行比较)
    2.3。 “ Katz” ===“ Katz”,由于助手是“纯”的,我们知道我们不必重新调用to-upper-case助手,因此我们知道该助手的输出(“ KATZ” )_also_没变,所以这里无事可做
  3. 同样,与汤姆和戈弗雷的行无关
  4. 删除所有具有不匹配对象的行(无,因此在这种情况下无需执行任何操作)

这样,我们回到了最佳的5种操作。

扩大

同样,这是比较和簿记成本的👋手。 确实,它们也不是免费的,在这个非常简单的示例中,它们可能不值得。 但是,想象一下这个列表很大,并且每一行都调用一个复杂的组件(具有很多帮助器,计算属性,子组件等)。 例如,想象一下LinkedIn新闻提要。 如果我们没有用正确的数据来匹配正确的行,则组件的参数可能会产生很大的混乱,并导致DOM更新超出您的预期。 匹配错误的DOM元素并失去DOM状态(例如光标位置和文本选择状态)也存在问题。

总体而言,在现实世界中的大多数时间里,额外的比较和簿记成本很容易就值得。 由于key="@identity"是Ember中的默认设置,并且在几乎所有情况下都能正常使用,因此在使用{{#each}}时,通常不必担心设置key参数。

碰撞💥

但是,等等,这是一个问题。 那这种情况呢?

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
];

这里的问题是同一对象_could_在同一列表中出现多次。 这打破了我们朴素的@identity算法,特别是我们所说的“查找数据匹配的现有行( === )...”的部分–仅在数据与DOM关系为1的情况下有效:1,在这种情况下不正确。 实际上这似乎不太可能,但是作为一个框架,我们必须处理它。

为了避免这种情况,我们使用一种混合方法来处理这些冲突。 在内部,键到DOM的映射如下所示:

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

在大多数情况下,这种情况很少见,当它出现时,大多数时候都可以使用Good Enough™。 如果由于某种原因这不起作用,则可以始终使用密钥路径(或RFC 321中甚至更高级的密钥机制)。

回到“🐛”

讨论完所有内容之后,我们现在就可以看看Twiddle中的情况。

基本上,我们从以下列表开始: [undefined, undefined, undefined, undefined, undefined]

不相关的注释: Array(5)[undefined, undefined, undefined, undefined, undefined]是同一件事。 它会产生一个“多孔数组”,通常应该避免这种情况。 但是,它与该错误无关,因为在访问“漏洞”时您确实会获得undefined返还。 因此,仅出于我们非常狭窄的目的,它们是相同的。

由于我们未指定密钥,因此Ember默认使用@identity 。 此外,由于它们是碰撞,所以我们最终得到这样的结果:

"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...

现在,假设我们单击第一个复选框:

  1. 它触发选择框的默认行为:将选中状态更改为true
  2. 它触发点击事件,该事件被{{action}}修饰符拦截并重新分配给makeChange方法
  3. 它将列表更改为[true, undefined, undefined, undefined, undefined]
  4. 它更新DOM。

DOM如何更新?

  1. 查找其数据与第一个对象true匹配( === )的现有行。 由于未找到任何内容,因此插入(添加)新行<input checked=true ...>0: true...
  2. 查找数据与第二个对象undefined匹配( === )的现有行。 找到<input ...>0: ... (以前是FIRST行):
    2.1。 将{{idx}}文本节点更新为1
    2.2。 否则,就Ember所知,此行中没有其他更改,没有其他操作
  3. 查找一个数据与第三个对象undefined匹配( === )的现有行。 由于这是我们第二次看到undefined ,内部键是undefined-1 ,因此我们找到了<input ...>1: ... (以前是SECOND行):
    3.1。 将{{idx}}文本节点更新为2
    3.2。 否则,就Ember所知,此行中没有其他更改,没有其他操作
  4. 同样,更新undefined-2undefined-3
  5. 最后,删除不匹配的undefined-4行(因为更新后数组中的undefined

因此,这说明了我们如何获得旋转中的输出。 基本上所有DOM行都向下移动了一个,并在顶部插入了一个新行,而其余的{{idx}}被更新了。

真正出乎意料的部分是2.2。 即使第一个复选框(被单击的复选框)向下移动了一行到第二个位置,您也可能希望将Ember的checked属性更改为true ,并且由于其绑定值是不确定的,因此您可能希望Ember将其更改回false ,从而取消选中它。

但这不是它的工作方式。 如开头所述,访问DOM的成本很高。 这包括DOM中的_reading_。 如果在每次更新时我们都必须从DOM中读取最新值以进行比较,那么这将很不利于优化的目的。 因此,为了避免这种情况,我们记住了我们写入DOM的最后一个值,并将当前值与缓存的值进行比较,而不必从DOM中读取回去。 仅当存在差异时,我们才将新值写入DOM(并在下一次缓存它)。 从某种意义上说,我们有点相同的“虚拟DOM”方法,但是我们只在叶节点执行此操作,而不是虚拟整个DOM的“树形”。

因此,TL; DR“绑定” checked属性(或文本字段的value属性,等等)并不能像您期望的那样工作。 想象一下,如果您渲染了<div>{{this.name}}</div>并使用jQuery或chrome检验器手动更新了textContent div元素的jQuery 。 您不会期望Ember注意到这一点并为您更新this.name 。 这基本上是同一件事:由于对checked属性的更新发生在Ember之外(通过浏览器的复选框默认行为),因此Ember不会知道这一点。

这就是{{input}}助手存在的原因。 它必须在基础HTML元素上注册相关的事件侦听器,并将操作反映到适当的属性更改中,以便可以通知感兴趣的各方(例如,呈现层)。

我不确定那会给我们留下什么。 我理解为什么这令人惊讶,但是我倾向于说这是一系列不幸的用户错误。 也许我们应该反对在输入元素上绑定这些属性?

@chancancode-感谢您的精彩解释。 这是否意味着不应使用<input ... >而只能使用{{input ...}}来防止此类错误?

@ boris-petrov可能在某些有限的情况下是可以接受的。.例如,用于“将此URL复制到剪贴板”的只读文本字段,或者您可以_can_使用输入元素+ {{action}}来拦截DOM事件并手动反映属性更新(这是该方法的尝试,除了它还会遇到@identity冲突),但是是的,在某些时候,您只是在重新实现{{input}}并处理它已经为您处理的所有边缘情况。 因此,我认为您应该最多使用{{input}} ,即使不是全部,也是合理的。

但是,在与键发生冲突的情况下,仍然无法“解决”这种情况。 参见https://ember-twiddle.com/0f2369021128e2ae0c445155df5bb034?openFiles=templates.application.hbs%2C

这就是为什么我说,我100%不知道该怎么办。 一方面,我同意这是令人惊讶且出乎意料的,另一方面,这种冲突在实际应用中很少见,这就是为什么“ key”参数是可自定义的(这是默认“ @identity”键的情况)功能不是Good Enough™,这就​​是该功能存在的原因)。

@chancancode-这使想起了我replace而不是set )对我来说仍然很奇怪。

@ boris-petrov我不认为这是相关的

嗨,我们将sortablejs用于ember的可拖动列表。 请检查此演示以重现每个问题。

步:

  • 将任何一项拖到最后
  • 切换到“ v2”

您可以看到拖动的项目留在dom树中。

但是,如果将项目拖到其他位置(而不是最后一个项目),则似乎效果很好。

此页面是否有帮助?
0 / 5 - 0 等级