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