Mithril Keys

Keys

什么是 Key

Key 是一种允许对 DOM 元素进行重新排序的机制,把列表中的指定数据项映射到各自对应的 DOM 元素,

换句话说,key 表示 “这个 DOM 元素属于这个数据对象的这个ID”。

通常,key 属性应该数组中的唯一标识字段,即该字段的值不应产生重复。

var users = [
    {id: 1, name: "John"},
    {id: 2, name: "Mary"},
]

function userInputs(users) {
    return users.map(function(u) {
        return m("input", {key: u.id}, u.name)
    })
}

m.render(document.body, userInputs(users))

有 key 意味着,如果 users 数组被打乱,且视图被重新渲染,input 元素将按照和以前一致的排序方式进行排序,以便保持正确的焦点和 DOM 状态。

如何使用

常见的模式是,一个由对象组成的数组,生成一个 vnode 列表。例如下面的代码:

var people = [
    {id: 1, name: "John"},
    {id: 2, name: "Mary"},
]

function userList(users) {
    return users.map(function(u) {
        return m("button", u.name) // <button>John</button>
                                   // <button>Mary</button>
    })
}

m.render(document.body, userList(people))

假设 people 变量被改为:

people = [{id: 2, name: "Mary"}]

问题是,从 userList 函数中,没法得知是第一个对象被删除了,还是第二个对象被删除了。如果第一个按钮处于聚焦状态,且渲染引擎将其删除,则焦点会按预期回到 <body> 元素。但如果渲染引擎删除的是第二个按钮,并修改第一个按钮的文本内容,则焦点会落到错误的按钮上。

更糟糕的是,如果按钮上使用了带有状态的 jQuery 插件,则在更新后可能会导致状态不正确。

因此,从动态的数据数组生成 vnode 时,应该为每一个虚拟节点添加 key 属性,指向数据数组中的唯一标识字段。

function correctUserList(users) {
    return users.map(function(u) {
        return m("button", {key: u.id}, u.name)
    })
}

如果 key 被误用,则可能引起混乱。典型症状是,经过几次用户交互后(通常涉及删除操作),应用的状态被破坏。

避免在含 key 的元素外面再包裹元素

key 必须放在数组的直接子节点上。意味着在上例中,如果你在 button 的外面包裹了一个 div,则必须将 key 移动到 div 上。

// 避免这种用法
users.map(function(u) {
    return m("div", [ // key should be in `div`
        m("button", {key: u.id}, u.name)
    ])
})

避免把 key 放在组件内

如果把按钮放在了组件中,则需要把 key 移到组件外面:

// 避免这种用法
var Button = {
    view: function(vnode) {
        return m("button", {key: vnode.attrs.id}, u.name)
    }
}

// 建议这种用法
users.map(function(u) {
    return m("div", [
        m(Button, {key: u.id}, u.name) // key 应该在这里,而不是组件内
    ])
})

避免在数组中使用含 key 的元素

数组是 vnode,因此可以使用 key。不要把含 key 的元素包裹在数组中:

// 避免这种用法
users.map(function(u) {
    return [ // 片段是 vnode,因此可以使用 key
        m("button", {key: u.id}, u.name)
    ]
})

// 建议这种用法
users.map(function(u) {
    return m("button", {key: u.id}, u.name)
})

// 建议这种用法
users.map(function(u) {
    return m.fragment({key: u.id}, m("button", u.name))
})

避免可变类型

key 必须是字符串,否则会被转换为字符串。因此 "1"(string) 和 1(number) 是同一个 key。

在一个数组中,key 应该只是用数字、或者只是用字符串,避免两者混用。

// AVOID
var things = [
    {id: "1", name: "Book"},
    {id: 1, name: "Cup"},
]

避免在同一个数组中混用含 key 和不含 key 的 vnode

一个 vnode 数组必须只包含含 key 的 vnode、或只包含不含 key 的 vnode,不能两者混用。如果你想要混用,请使用嵌套数组。

// 避免这种用法
m("div", [
    m("div", "a"),
    m("div", {key: 1}, "b"),
])

// 建议这种用法
m("div", [
    m("div", {key: 0}, "a"),
    m("div", {key: 1}, "b"),
])

// 建议这种用法
m("div", [
    m("div", "a"),
    [
        m("div", {key: 1}, "b"),
    ]
])