Skip to content

Commit a40faf4

Browse files
committed
vlist: store on the list, if all the children are keyed
1 parent 357f5b4 commit a40faf4

File tree

2 files changed

+79
-77
lines changed

2 files changed

+79
-77
lines changed

yew/src/virtual_dom/vlist.rs

+67-71
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,27 @@ cfg_if! {
1313
}
1414

1515
/// This struct represents a fragment of the Virtual DOM tree.
16-
#[derive(Clone, Debug, PartialEq, Default)]
16+
#[derive(Clone, Debug, PartialEq)]
1717
pub struct VList {
1818
/// The list of children nodes.
19-
pub children: Vec<VNode>,
19+
children: Vec<VNode>,
20+
21+
// All Nodes in the VList have keys
22+
fully_keyed: bool,
23+
2024
pub key: Option<Key>,
2125
}
2226

27+
impl Default for VList {
28+
fn default() -> Self {
29+
Self {
30+
children: Default::default(),
31+
key: None,
32+
fully_keyed: true,
33+
}
34+
}
35+
}
36+
2337
impl Deref for VList {
2438
type Target = Vec<VNode>;
2539

@@ -30,10 +44,13 @@ impl Deref for VList {
3044

3145
impl DerefMut for VList {
3246
fn deref_mut(&mut self) -> &mut Self::Target {
47+
// Caller might change the keys of the VList or add unkeyed children.
48+
// Defensively assume they will.
49+
self.fully_keyed = false;
50+
3351
&mut self.children
3452
}
3553
}
36-
3754
/// Log an operation during tests for debugging purposes
3855
/// Set RUSTFLAGS="--cfg verbose_tests" environment variable to activate.
3956
macro_rules! test_log {
@@ -63,17 +80,37 @@ impl VList {
6380

6481
/// Creates a new `VList` instance with children.
6582
pub fn new_with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
66-
VList { children, key }
83+
VList {
84+
fully_keyed: children.iter().all(|ch| ch.has_key()),
85+
children,
86+
key,
87+
}
6788
}
6889

6990
/// Add `VNode` child.
7091
pub fn add_child(&mut self, child: VNode) {
92+
if self.fully_keyed && !child.has_key() {
93+
self.fully_keyed = false;
94+
}
7195
self.children.push(child);
7296
}
7397

7498
/// Add multiple `VNode` children.
7599
pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
76-
self.children.extend(children);
100+
let it = children.into_iter();
101+
let bound = it.size_hint();
102+
self.children.reserve(bound.1.unwrap_or(bound.0));
103+
for ch in it {
104+
self.add_child(ch);
105+
}
106+
}
107+
108+
/// Recheck, if the all the children have keys.
109+
///
110+
/// Run this, after modifying the child list that contained only keyed children prior to the
111+
/// mutable dereference.
112+
pub fn recheck_fully_keyed(&mut self) {
113+
self.fully_keyed = self.children.iter().all(|ch| ch.has_key());
77114
}
78115

79116
/// Diff and patch unkeyed child lists
@@ -132,10 +169,18 @@ impl VList {
132169
parent: &Element,
133170
mut next_sibling: NodeRef,
134171
lefts: &mut [VNode],
135-
lefts_keys: &[Key],
136172
mut rights: Vec<VNode>,
137-
rights_keys: &[Key],
138173
) -> NodeRef {
174+
macro_rules! map_keys {
175+
($src:expr) => {
176+
$src.iter()
177+
.map(|v| v.key().expect("unkeyed child in fully keyed list"))
178+
.collect::<Vec<Key>>()
179+
};
180+
}
181+
let lefts_keys = map_keys!(lefts);
182+
let rights_keys = map_keys!(rights);
183+
139184
/// Find the first differing key in 2 iterators
140185
fn diff_i<'a, 'b>(
141186
a: impl Iterator<Item = &'a Key>,
@@ -229,59 +274,6 @@ impl VList {
229274

230275
next_sibling
231276
}
232-
233-
/// Diff and patch child lists, if the are fully keyed.
234-
/// Returns Err(rights, next_sibling) back, if diff and patch not performed.
235-
fn try_apply_keyed(
236-
parent_scope: &AnyScope,
237-
parent: &Element,
238-
next_sibling: NodeRef,
239-
lefts: &mut [VNode],
240-
rights: Vec<VNode>,
241-
) -> Result<NodeRef, (Vec<VNode>, NodeRef)> {
242-
macro_rules! fail {
243-
() => {{
244-
return Err((rights, next_sibling));
245-
}};
246-
}
247-
248-
/// First perform a cheap check on the first nodes to avoid allocations
249-
macro_rules! no_key {
250-
($list:expr) => {
251-
$list.get(0).map(|n| n.key().is_none()).unwrap_or(false)
252-
};
253-
}
254-
if no_key!(lefts) || no_key!(rights) {
255-
fail!();
256-
}
257-
258-
// Build key vectors ahead of time to prevent heavy branching in keyed diffing, in case
259-
// the child lists are not fully keyed, and to memoize key lookup
260-
macro_rules! map_keys {
261-
($src:expr) => {
262-
match $src
263-
.iter()
264-
.map(|v| v.key().ok_or(()))
265-
.collect::<Result<Vec<Key>, ()>>()
266-
{
267-
Ok(vec) => vec,
268-
Err(_) => fail!(),
269-
}
270-
};
271-
}
272-
let lefts_keys = map_keys!(lefts);
273-
let rights_keys = map_keys!(rights);
274-
275-
Ok(Self::apply_keyed(
276-
parent_scope,
277-
parent,
278-
next_sibling,
279-
lefts,
280-
&lefts_keys,
281-
rights,
282-
&rights_keys,
283-
))
284-
}
285277
}
286278

287279
impl VDiff for VList {
@@ -311,28 +303,32 @@ impl VDiff for VList {
311303
// Without a placeholder the next element becomes first
312304
// and corrupts the order of rendering
313305
// We use empty text element to stake out a place
314-
let placeholder = VText::new("");
315-
self.children.push(placeholder.into());
306+
self.add_child(VText::new("").into());
316307
}
317308

318309
let lefts = &mut self.children;
319-
let rights = match ancestor {
310+
let (rights, rights_fully_keyed) = match ancestor {
320311
// If the ancestor is also a VList, then the "right" list is the previously
321312
// rendered items.
322-
Some(VNode::VList(v)) => v.children,
313+
Some(VNode::VList(v)) => (v.children, v.fully_keyed),
314+
323315
// If the ancestor was not a VList, then the "right" list is a single node
324-
Some(v) => vec![v],
325-
_ => vec![],
316+
Some(v) => {
317+
let has_key = v.has_key();
318+
(vec![v], has_key)
319+
}
320+
321+
// No unkeyed nodes in an empty VList
322+
_ => (vec![], true),
326323
};
327324
test_log!("lefts: {:?}", lefts);
328325
test_log!("rights: {:?}", rights);
329326

330327
#[allow(clippy::let_and_return)]
331-
let first = match Self::try_apply_keyed(parent_scope, parent, next_sibling, lefts, rights) {
332-
Ok(n) => n,
333-
Err((rights, next_sibling)) => {
334-
Self::apply_unkeyed(parent_scope, parent, next_sibling, lefts, rights)
335-
}
328+
let first = if self.fully_keyed && rights_fully_keyed {
329+
Self::apply_keyed(parent_scope, parent, next_sibling, lefts, rights)
330+
} else {
331+
Self::apply_unkeyed(parent_scope, parent, next_sibling, lefts, rights)
336332
};
337333
test_log!("result: {:?}", lefts);
338334
first

yew/src/virtual_dom/vnode.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ impl VNode {
4343
}
4444
}
4545

46+
/// Returns, if the VNode has a key without needlessly cloning the key
47+
pub fn has_key(&self) -> bool {
48+
match self {
49+
VNode::VComp(vcomp) => vcomp.key.is_some(),
50+
VNode::VList(vlist) => vlist.key.is_some(),
51+
VNode::VRef(_) | VNode::VText(_) => false,
52+
VNode::VTag(vtag) => vtag.key.is_some(),
53+
}
54+
}
55+
4656
/// Returns the first DOM node that is used to designate the position of the virtual DOM node.
4757
pub(crate) fn first_node(&self) -> Node {
4858
match self {
@@ -60,19 +70,15 @@ impl VNode {
6070
}
6171
}
6272
VNode::VComp(vcomp) => vcomp.node_ref.get().expect("VComp is not mounted"),
63-
VNode::VList(vlist) => vlist
64-
.children
65-
.get(0)
66-
.expect("VList is not mounted")
67-
.first_node(),
73+
VNode::VList(vlist) => vlist.get(0).expect("VList is not mounted").first_node(),
6874
VNode::VRef(node) => node.clone(),
6975
}
7076
}
7177

7278
pub(crate) fn move_before(&self, parent: &Element, next_sibling: &Option<Node>) {
7379
match self {
7480
VNode::VList(vlist) => {
75-
for node in vlist.children.iter() {
81+
for node in vlist.iter() {
7682
node.move_before(parent, next_sibling);
7783
}
7884
}

0 commit comments

Comments
 (0)