Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/virtual_dom/
vlist.rs

1//! This module contains fragments implementation.
2use std::ops::{Deref, DerefMut};
3use std::rc::Rc;
4
5use super::{Key, VNode};
6
7#[doc(hidden)]
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub enum FullyKeyedState {
10    KnownFullyKeyed,
11    KnownMissingKeys,
12    Unknown,
13}
14
15/// This struct represents a fragment of the Virtual DOM tree.
16#[derive(Clone, Debug)]
17pub struct VList {
18    /// The list of child [VNode]s
19    pub(crate) children: Option<Rc<Vec<VNode>>>,
20
21    /// All [VNode]s in the VList have keys
22    fully_keyed: FullyKeyedState,
23
24    pub key: Option<Key>,
25}
26
27impl PartialEq for VList {
28    fn eq(&self, other: &Self) -> bool {
29        if self.key != other.key {
30            return false;
31        }
32
33        match (self.children.as_ref(), other.children.as_ref()) {
34            (Some(a), Some(b)) => a == b,
35            (Some(a), None) => a.is_empty(),
36            (None, Some(b)) => b.is_empty(),
37            (None, None) => true,
38        }
39    }
40}
41
42impl Default for VList {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl Deref for VList {
49    type Target = Vec<VNode>;
50
51    fn deref(&self) -> &Self::Target {
52        match self.children {
53            Some(ref m) => m,
54            None => const { &Vec::new() },
55        }
56    }
57}
58
59impl DerefMut for VList {
60    fn deref_mut(&mut self) -> &mut Self::Target {
61        self.fully_keyed = FullyKeyedState::Unknown;
62        self.children_mut()
63    }
64}
65
66impl<A: Into<VNode>> FromIterator<A> for VList {
67    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
68        let children = iter.into_iter().map(|n| n.into()).collect::<Vec<_>>();
69        if children.is_empty() {
70            VList::new()
71        } else {
72            VList {
73                children: Some(Rc::new(children)),
74                fully_keyed: FullyKeyedState::Unknown,
75                key: None,
76            }
77        }
78    }
79}
80
81impl From<Option<Rc<Vec<VNode>>>> for VList {
82    fn from(children: Option<Rc<Vec<VNode>>>) -> Self {
83        if children.as_ref().is_none_or(|x| x.is_empty()) {
84            VList::new()
85        } else {
86            let mut vlist = VList {
87                children,
88                fully_keyed: FullyKeyedState::Unknown,
89                key: None,
90            };
91            vlist.recheck_fully_keyed();
92            vlist
93        }
94    }
95}
96
97impl From<Vec<VNode>> for VList {
98    fn from(children: Vec<VNode>) -> Self {
99        if children.is_empty() {
100            VList::new()
101        } else {
102            let mut vlist = VList {
103                children: Some(Rc::new(children)),
104                fully_keyed: FullyKeyedState::Unknown,
105                key: None,
106            };
107            vlist.recheck_fully_keyed();
108            vlist
109        }
110    }
111}
112
113impl From<VNode> for VList {
114    fn from(child: VNode) -> Self {
115        let mut vlist = VList {
116            children: Some(Rc::new(vec![child])),
117            fully_keyed: FullyKeyedState::Unknown,
118            key: None,
119        };
120        vlist.recheck_fully_keyed();
121        vlist
122    }
123}
124
125impl VList {
126    /// Creates a new empty [VList] instance.
127    pub const fn new() -> Self {
128        Self {
129            children: None,
130            key: None,
131            fully_keyed: FullyKeyedState::KnownFullyKeyed,
132        }
133    }
134
135    /// Creates a new [VList] instance with children.
136    pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
137        let mut vlist = VList::from(children);
138        vlist.key = key;
139        vlist
140    }
141
142    #[doc(hidden)]
143    /// Used by `html!` to avoid calling `.recheck_fully_keyed()` when possible.
144    pub fn __macro_new(
145        children: Vec<VNode>,
146        key: Option<Key>,
147        fully_keyed: FullyKeyedState,
148    ) -> Self {
149        VList {
150            children: Some(Rc::new(children)),
151            fully_keyed,
152            key,
153        }
154    }
155
156    // Returns a mutable reference to children, allocates the children if it hasn't been done.
157    //
158    // This method does not reassign key state. So it should only be used internally.
159    fn children_mut(&mut self) -> &mut Vec<VNode> {
160        loop {
161            match self.children {
162                Some(ref mut m) => return Rc::make_mut(m),
163                None => {
164                    self.children = Some(Rc::new(Vec::new()));
165                }
166            }
167        }
168    }
169
170    /// Add [VNode] child.
171    pub fn add_child(&mut self, child: VNode) {
172        if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
173            self.fully_keyed = FullyKeyedState::KnownMissingKeys;
174        }
175        self.children_mut().push(child);
176    }
177
178    /// Add multiple [VNode] children.
179    pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
180        let it = children.into_iter();
181        let bound = it.size_hint();
182        self.children_mut().reserve(bound.1.unwrap_or(bound.0));
183        for ch in it {
184            self.add_child(ch);
185        }
186    }
187
188    /// Recheck, if the all the children have keys.
189    ///
190    /// You can run this, after modifying the child list through the [DerefMut] implementation of
191    /// [VList], to precompute an internally kept flag, which speeds up reconciliation later.
192    pub fn recheck_fully_keyed(&mut self) {
193        self.fully_keyed = if self.fully_keyed() {
194            FullyKeyedState::KnownFullyKeyed
195        } else {
196            FullyKeyedState::KnownMissingKeys
197        };
198    }
199
200    pub(crate) fn fully_keyed(&self) -> bool {
201        match self.fully_keyed {
202            FullyKeyedState::KnownFullyKeyed => true,
203            FullyKeyedState::KnownMissingKeys => false,
204            FullyKeyedState::Unknown => self.iter().all(|c| c.has_key()),
205        }
206    }
207}
208
209#[cfg(test)]
210mod test {
211    use super::*;
212    use crate::virtual_dom::{VTag, VText};
213
214    #[test]
215    fn mutably_change_children() {
216        let mut vlist = VList::new();
217        assert_eq!(
218            vlist.fully_keyed,
219            FullyKeyedState::KnownFullyKeyed,
220            "should start fully keyed"
221        );
222        // add a child that is keyed
223        vlist.add_child(VNode::VTag({
224            let mut tag = VTag::new("a");
225            tag.key = Some(42u32.into());
226            tag.into()
227        }));
228        assert_eq!(
229            vlist.fully_keyed,
230            FullyKeyedState::KnownFullyKeyed,
231            "should still be fully keyed"
232        );
233        assert_eq!(vlist.len(), 1, "should contain 1 child");
234        // now add a child that is not keyed
235        vlist.add_child(VNode::VText(VText::new("lorem ipsum")));
236        assert_eq!(
237            vlist.fully_keyed,
238            FullyKeyedState::KnownMissingKeys,
239            "should not be fully keyed, text tags have no key"
240        );
241        let _: &mut [VNode] = &mut vlist; // Use deref mut
242        assert_eq!(
243            vlist.fully_keyed,
244            FullyKeyedState::Unknown,
245            "key state should be unknown, since it was potentially modified through children"
246        );
247    }
248}
249
250#[cfg(feature = "ssr")]
251mod feat_ssr {
252    use std::fmt::Write;
253    use std::task::Poll;
254
255    use futures::stream::StreamExt;
256    use futures::{join, pin_mut, poll, FutureExt};
257
258    use super::*;
259    use crate::feat_ssr::VTagKind;
260    use crate::html::AnyScope;
261    use crate::platform::fmt::{self, BufWriter};
262
263    impl VList {
264        pub(crate) async fn render_into_stream(
265            &self,
266            w: &mut BufWriter,
267            parent_scope: &AnyScope,
268            hydratable: bool,
269            parent_vtag_kind: VTagKind,
270        ) {
271            match &self[..] {
272                [] => {}
273                [child] => {
274                    child
275                        .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
276                        .await;
277                }
278                _ => {
279                    async fn render_child_iter<'a, I>(
280                        mut children: I,
281                        w: &mut BufWriter,
282                        parent_scope: &AnyScope,
283                        hydratable: bool,
284                        parent_vtag_kind: VTagKind,
285                    ) where
286                        I: Iterator<Item = &'a VNode>,
287                    {
288                        let mut w = w;
289                        while let Some(m) = children.next() {
290                            let child_fur = async move {
291                                // Rust's Compiler does not release the mutable reference to
292                                // BufWriter until the end of the loop, regardless of whether an
293                                // await statement has dropped the child_fur.
294                                //
295                                // We capture and return the mutable reference to avoid this.
296
297                                m.render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
298                                    .await;
299                                w
300                            };
301                            pin_mut!(child_fur);
302
303                            match poll!(child_fur.as_mut()) {
304                                Poll::Pending => {
305                                    let (mut next_w, next_r) = fmt::buffer();
306                                    // Move buf writer into an async block for it to be dropped at
307                                    // the end of the future.
308                                    let rest_render_fur = async move {
309                                        render_child_iter(
310                                            children,
311                                            &mut next_w,
312                                            parent_scope,
313                                            hydratable,
314                                            parent_vtag_kind,
315                                        )
316                                        .await;
317                                    }
318                                    // boxing to avoid recursion
319                                    .boxed_local();
320
321                                    let transfer_fur = async move {
322                                        let w = child_fur.await;
323
324                                        pin_mut!(next_r);
325                                        while let Some(m) = next_r.next().await {
326                                            let _ = w.write_str(m.as_str());
327                                        }
328                                    };
329
330                                    join!(rest_render_fur, transfer_fur);
331                                    break;
332                                }
333                                Poll::Ready(w_) => {
334                                    w = w_;
335                                }
336                            }
337                        }
338                    }
339
340                    let children = self.iter();
341                    render_child_iter(children, w, parent_scope, hydratable, parent_vtag_kind)
342                        .await;
343                }
344            }
345        }
346    }
347}
348
349#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
350#[cfg(feature = "ssr")]
351#[cfg(test)]
352mod ssr_tests {
353    use tokio::test;
354
355    use crate::prelude::*;
356    use crate::LocalServerRenderer as ServerRenderer;
357
358    #[cfg_attr(not(target_os = "wasi"), test)]
359    #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))]
360    async fn test_text_back_to_back() {
361        #[component]
362        fn Comp() -> Html {
363            let s = "world";
364
365            html! { <div>{"Hello "}{s}{"!"}</div> }
366        }
367
368        let s = ServerRenderer::<Comp>::new()
369            .hydratable(false)
370            .render()
371            .await;
372
373        assert_eq!(s, "<div>Hello world!</div>");
374    }
375
376    #[cfg_attr(not(target_os = "wasi"), test)]
377    #[cfg_attr(target_os = "wasi", test(flavor = "current_thread"))]
378    async fn test_fragment() {
379        #[derive(PartialEq, Properties, Debug)]
380        struct ChildProps {
381            name: String,
382        }
383
384        #[component]
385        fn Child(props: &ChildProps) -> Html {
386            html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
387        }
388
389        #[component]
390        fn Comp() -> Html {
391            html! {
392                <>
393                    <Child name="Jane" />
394                    <Child name="John" />
395                    <Child name="Josh" />
396                </>
397            }
398        }
399
400        let s = ServerRenderer::<Comp>::new()
401            .hydratable(false)
402            .render()
403            .await;
404
405        assert_eq!(
406            s,
407            "<div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div>"
408        );
409    }
410}