Move fragment type check to normalizer (#2462)
Should result in more informative stack traces. Fixes #2461
This commit is contained in:
parent
b2f82e3abc
commit
db277217f8
3 changed files with 53 additions and 27 deletions
|
|
@ -44,24 +44,8 @@ module.exports = function($window) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function validateKeys(vnodes, isKeyed) {
|
|
||||||
// Note: this is a *very* perf-sensitive check.
|
|
||||||
// Fun fact: merging the loop like this is somehow faster than splitting
|
|
||||||
// it, noticeably so.
|
|
||||||
for (var i = 1; i < vnodes.length; i++) {
|
|
||||||
if ((vnodes[i] != null && vnodes[i].key != null) !== isKeyed) {
|
|
||||||
throw new TypeError("Vnodes must either always have keys or never have keys!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//create
|
//create
|
||||||
function createNodesChecked(parent, vnodes, hooks, nextSibling, ns) {
|
function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
|
||||||
if (vnodes.length) {
|
|
||||||
validateKeys(vnodes, vnodes[0] != null && vnodes[0].key != null)
|
|
||||||
createNodesUnchecked(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function createNodesUnchecked(parent, vnodes, start, end, hooks, nextSibling, ns) {
|
|
||||||
for (var i = start; i < end; i++) {
|
for (var i = start; i < end; i++) {
|
||||||
var vnode = vnodes[i]
|
var vnode = vnodes[i]
|
||||||
if (vnode != null) {
|
if (vnode != null) {
|
||||||
|
|
@ -115,7 +99,7 @@ module.exports = function($window) {
|
||||||
var fragment = $doc.createDocumentFragment()
|
var fragment = $doc.createDocumentFragment()
|
||||||
if (vnode.children != null) {
|
if (vnode.children != null) {
|
||||||
var children = vnode.children
|
var children = vnode.children
|
||||||
createNodesChecked(fragment, children, hooks, null, ns)
|
createNodes(fragment, children, 0, children.length, hooks, null, ns)
|
||||||
}
|
}
|
||||||
vnode.dom = fragment.firstChild
|
vnode.dom = fragment.firstChild
|
||||||
vnode.domSize = fragment.childNodes.length
|
vnode.domSize = fragment.childNodes.length
|
||||||
|
|
@ -146,7 +130,7 @@ module.exports = function($window) {
|
||||||
}
|
}
|
||||||
if (vnode.children != null) {
|
if (vnode.children != null) {
|
||||||
var children = vnode.children
|
var children = vnode.children
|
||||||
createNodesChecked(element, children, hooks, null, ns)
|
createNodes(element, children, 0, children.length, hooks, null, ns)
|
||||||
if (vnode.tag === "select" && attrs != null) setLateSelectAttrs(vnode, attrs)
|
if (vnode.tag === "select" && attrs != null) setLateSelectAttrs(vnode, attrs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -289,19 +273,18 @@ module.exports = function($window) {
|
||||||
|
|
||||||
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
|
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
|
||||||
if (old === vnodes || old == null && vnodes == null) return
|
if (old === vnodes || old == null && vnodes == null) return
|
||||||
else if (old == null || old.length === 0) createNodesChecked(parent, vnodes, hooks, nextSibling, ns)
|
else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
||||||
else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length)
|
else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length)
|
||||||
else {
|
else {
|
||||||
var isOldKeyed = old[0] != null && old[0].key != null
|
var isOldKeyed = old[0] != null && old[0].key != null
|
||||||
var isKeyed = vnodes[0] != null && vnodes[0].key != null
|
var isKeyed = vnodes[0] != null && vnodes[0].key != null
|
||||||
var start = 0, oldStart = 0
|
var start = 0, oldStart = 0
|
||||||
validateKeys(vnodes, isKeyed)
|
|
||||||
if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++
|
if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++
|
||||||
if (!isKeyed) while (start < vnodes.length && vnodes[start] == null) start++
|
if (!isKeyed) while (start < vnodes.length && vnodes[start] == null) start++
|
||||||
if (isKeyed === null && isOldKeyed == null) return // both lists are full of nulls
|
if (isKeyed === null && isOldKeyed == null) return // both lists are full of nulls
|
||||||
if (isOldKeyed !== isKeyed) {
|
if (isOldKeyed !== isKeyed) {
|
||||||
removeNodes(old, oldStart, old.length)
|
removeNodes(old, oldStart, old.length)
|
||||||
createNodesUnchecked(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
||||||
} else if (!isKeyed) {
|
} else if (!isKeyed) {
|
||||||
// Don't index past the end of either list (causes deopts).
|
// Don't index past the end of either list (causes deopts).
|
||||||
var commonLength = old.length < vnodes.length ? old.length : vnodes.length
|
var commonLength = old.length < vnodes.length ? old.length : vnodes.length
|
||||||
|
|
@ -318,7 +301,7 @@ module.exports = function($window) {
|
||||||
else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns)
|
else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns)
|
||||||
}
|
}
|
||||||
if (old.length > commonLength) removeNodes(old, start, old.length)
|
if (old.length > commonLength) removeNodes(old, start, old.length)
|
||||||
if (vnodes.length > commonLength) createNodesUnchecked(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
||||||
} else {
|
} else {
|
||||||
// keyed diff
|
// keyed diff
|
||||||
var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling
|
var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling
|
||||||
|
|
@ -366,7 +349,7 @@ module.exports = function($window) {
|
||||||
ve = vnodes[end]
|
ve = vnodes[end]
|
||||||
}
|
}
|
||||||
if (start > end) removeNodes(old, oldStart, oldEnd + 1)
|
if (start > end) removeNodes(old, oldStart, oldEnd + 1)
|
||||||
else if (oldStart > oldEnd) createNodesUnchecked(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
||||||
else {
|
else {
|
||||||
// inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
|
// inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
|
||||||
var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices
|
var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices
|
||||||
|
|
@ -387,7 +370,7 @@ module.exports = function($window) {
|
||||||
}
|
}
|
||||||
nextSibling = originalNextSibling
|
nextSibling = originalNextSibling
|
||||||
if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1)
|
if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1)
|
||||||
if (matched === 0) createNodesUnchecked(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
||||||
else {
|
else {
|
||||||
if (pos === -1) {
|
if (pos === -1) {
|
||||||
// the indices of the indices of the items that are part of the
|
// the indices of the indices of the items that are part of the
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,36 @@ o.spec("normalizeChildren", function() {
|
||||||
|
|
||||||
o(children[0]).equals(null)
|
o(children[0]).equals(null)
|
||||||
})
|
})
|
||||||
|
o("allows all keys", function() {
|
||||||
|
var children = Vnode.normalizeChildren([
|
||||||
|
{key: 1},
|
||||||
|
{key: 2},
|
||||||
|
])
|
||||||
|
|
||||||
|
o(children).deepEquals([{key: 1}, {key: 2}])
|
||||||
|
})
|
||||||
|
o("allows no keys", function() {
|
||||||
|
var children = Vnode.normalizeChildren([
|
||||||
|
{data: 1},
|
||||||
|
{data: 2},
|
||||||
|
])
|
||||||
|
|
||||||
|
o(children).deepEquals([{data: 1}, {data: 2}])
|
||||||
|
})
|
||||||
|
o("disallows mixed keys, starting with key", function() {
|
||||||
|
o(function() {
|
||||||
|
Vnode.normalizeChildren([
|
||||||
|
{key: 1},
|
||||||
|
{data: 2},
|
||||||
|
])
|
||||||
|
}).throws(TypeError)
|
||||||
|
})
|
||||||
|
o("disallows mixed keys, starting with no key", function() {
|
||||||
|
o(function() {
|
||||||
|
Vnode.normalizeChildren([
|
||||||
|
{data: 1},
|
||||||
|
{key: 2},
|
||||||
|
])
|
||||||
|
}).throws(TypeError)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,19 @@ Vnode.normalize = function(node) {
|
||||||
}
|
}
|
||||||
Vnode.normalizeChildren = function(input) {
|
Vnode.normalizeChildren = function(input) {
|
||||||
var children = []
|
var children = []
|
||||||
for (var i = 0; i < input.length; i++) {
|
if (input.length) {
|
||||||
children[i] = Vnode.normalize(input[i])
|
var isKeyed = input[0] != null && input[0].key != null
|
||||||
|
// Note: this is a *very* perf-sensitive check.
|
||||||
|
// Fun fact: merging the loop like this is somehow faster than splitting
|
||||||
|
// it, noticeably so.
|
||||||
|
for (var i = 1; i < input.length; i++) {
|
||||||
|
if ((input[i] != null && input[i].key != null) !== isKeyed) {
|
||||||
|
throw new TypeError("Vnodes must either always have keys or never have keys!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < input.length; i++) {
|
||||||
|
children[i] = Vnode.normalize(input[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue