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