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