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

yew/dom_bundle/
bcomp.rs

1//! This module contains the bundle implementation of a virtual component [BComp].
2
3use std::any::TypeId;
4use std::borrow::Borrow;
5use std::fmt;
6
7use web_sys::Element;
8
9use super::{BNode, BSubtree, DomSlot, DynamicDomSlot, Reconcilable, ReconcileTarget};
10use crate::html::{AnyScope, Scoped};
11use crate::virtual_dom::{Key, VComp};
12
13/// A virtual component. Compare with [VComp].
14pub(super) struct BComp {
15    type_id: TypeId,
16    scope: Box<dyn Scoped>,
17    /// An internal [`DomSlot`] passed around to track this components position. This
18    /// will dynamically adjust when a lifecycle changes the render state of this component.
19    own_position: DynamicDomSlot,
20    key: Option<Key>,
21}
22
23impl BComp {
24    /// Get the key of the underlying component
25    pub fn key(&self) -> Option<&Key> {
26        self.key.as_ref()
27    }
28}
29
30impl fmt::Debug for BComp {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("BComp")
33            .field("root", &self.scope.as_ref().render_state())
34            .finish()
35    }
36}
37
38impl ReconcileTarget for BComp {
39    fn detach(self, _root: &BSubtree, _parent: &Element, parent_to_detach: bool) {
40        self.scope.destroy_boxed(parent_to_detach);
41    }
42
43    fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
44        self.scope.shift_node(next_parent.clone(), slot);
45
46        self.own_position.to_position()
47    }
48}
49
50impl Reconcilable for VComp {
51    type Bundle = BComp;
52
53    fn attach(
54        self,
55        root: &BSubtree,
56        parent_scope: &AnyScope,
57        parent: &Element,
58        slot: DomSlot,
59    ) -> (DomSlot, Self::Bundle) {
60        let VComp {
61            type_id,
62            mountable,
63            key,
64            ..
65        } = self;
66        let internal_ref = DynamicDomSlot::new_debug_trapped();
67
68        let scope = mountable.mount(
69            root,
70            parent_scope,
71            parent.to_owned(),
72            slot,
73            internal_ref.clone(),
74        );
75
76        (
77            internal_ref.to_position(),
78            BComp {
79                type_id,
80                own_position: internal_ref,
81                key,
82                scope,
83            },
84        )
85    }
86
87    fn reconcile_node(
88        self,
89        root: &BSubtree,
90        parent_scope: &AnyScope,
91        parent: &Element,
92        slot: DomSlot,
93        bundle: &mut BNode,
94    ) -> DomSlot {
95        match bundle {
96            // If the existing bundle is the same type, reuse it and update its properties
97            BNode::Comp(ref mut bcomp)
98                if self.type_id == bcomp.type_id && self.key == bcomp.key =>
99            {
100                self.reconcile(root, parent_scope, parent, slot, bcomp)
101            }
102            _ => self.replace(root, parent_scope, parent, slot, bundle),
103        }
104    }
105
106    fn reconcile(
107        self,
108        _root: &BSubtree,
109        _parent_scope: &AnyScope,
110        _parent: &Element,
111        slot: DomSlot,
112        bcomp: &mut Self::Bundle,
113    ) -> DomSlot {
114        let VComp { mountable, key, .. } = self;
115
116        bcomp.key = key;
117        mountable.reuse(bcomp.scope.borrow(), slot);
118        bcomp.own_position.to_position()
119    }
120}
121
122#[cfg(feature = "hydration")]
123mod feat_hydration {
124    use super::*;
125    use crate::dom_bundle::{Fragment, Hydratable};
126
127    impl Hydratable for VComp {
128        fn hydrate(
129            self,
130            root: &BSubtree,
131            parent_scope: &AnyScope,
132            parent: &Element,
133            fragment: &mut Fragment,
134        ) -> Self::Bundle {
135            let VComp {
136                type_id,
137                mountable,
138                key,
139                ..
140            } = self;
141            let internal_ref = DynamicDomSlot::new_debug_trapped();
142
143            let scoped = mountable.hydrate(
144                root.clone(),
145                parent_scope,
146                parent.clone(),
147                internal_ref.clone(),
148                fragment,
149            );
150
151            BComp {
152                type_id,
153                scope: scoped,
154                own_position: internal_ref,
155                key,
156            }
157        }
158    }
159}
160
161#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
162#[cfg(test)]
163mod tests {
164    use gloo::utils::document;
165    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
166    use web_sys::Element;
167
168    use super::*;
169    use crate::dom_bundle::Reconcilable;
170    use crate::virtual_dom::{Key, VChild, VNode};
171    use crate::{html, scheduler, Children, Component, Context, Html, Properties};
172
173    wasm_bindgen_test_configure!(run_in_browser);
174
175    struct Comp;
176
177    #[derive(Clone, PartialEq, Properties)]
178    struct Props {
179        #[prop_or_default]
180        field_1: u32,
181        #[prop_or_default]
182        field_2: u32,
183    }
184
185    impl Component for Comp {
186        type Message = ();
187        type Properties = Props;
188
189        fn create(_: &Context<Self>) -> Self {
190            Comp
191        }
192
193        fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
194            unimplemented!();
195        }
196
197        fn view(&self, _ctx: &Context<Self>) -> Html {
198            html! { <div/> }
199        }
200    }
201
202    #[test]
203    fn update_loop() {
204        let (root, scope, parent) = setup_parent();
205
206        let comp = html! { <Comp></Comp> };
207        let (_, mut bundle) = comp.attach(&root, &scope, &parent, DomSlot::at_end());
208        scheduler::start_now();
209
210        for _ in 0..10000 {
211            let node = html! { <Comp></Comp> };
212            node.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut bundle);
213            scheduler::start_now();
214        }
215    }
216
217    #[test]
218    fn set_properties_to_component() {
219        html! {
220            <Comp />
221        };
222
223        html! {
224            <Comp field_1=1 />
225        };
226
227        html! {
228            <Comp field_2=2 />
229        };
230
231        html! {
232            <Comp field_1=1 field_2=2 />
233        };
234
235        let props = Props {
236            field_1: 1,
237            field_2: 1,
238        };
239
240        html! {
241            <Comp ..props />
242        };
243    }
244
245    #[test]
246    fn set_component_key() {
247        let test_key: Key = "test".to_string().into();
248        let check_key = |vnode: VNode| {
249            assert_eq!(vnode.key(), Some(&test_key));
250        };
251
252        let props = Props {
253            field_1: 1,
254            field_2: 1,
255        };
256        let props_2 = props.clone();
257
258        check_key(html! { <Comp key={test_key.clone()} /> });
259        check_key(html! { <Comp key={test_key.clone()} field_1=1 /> });
260        check_key(html! { <Comp field_1=1 key={test_key.clone()} /> });
261        check_key(html! { <Comp key={test_key.clone()} ..props /> });
262        check_key(html! { <Comp key={test_key.clone()} ..props_2 /> });
263    }
264
265    #[test]
266    fn vchild_partialeq() {
267        let vchild1: VChild<Comp> = VChild::new(
268            Props {
269                field_1: 1,
270                field_2: 1,
271            },
272            None,
273        );
274
275        let vchild2: VChild<Comp> = VChild::new(
276            Props {
277                field_1: 1,
278                field_2: 1,
279            },
280            None,
281        );
282
283        let vchild3: VChild<Comp> = VChild::new(
284            Props {
285                field_1: 2,
286                field_2: 2,
287            },
288            None,
289        );
290
291        assert_eq!(vchild1, vchild2);
292        assert_ne!(vchild1, vchild3);
293        assert_ne!(vchild2, vchild3);
294    }
295
296    #[derive(Clone, Properties, PartialEq)]
297    pub struct ListProps {
298        pub children: Children,
299    }
300    pub struct List;
301    impl Component for List {
302        type Message = ();
303        type Properties = ListProps;
304
305        fn create(_: &Context<Self>) -> Self {
306            Self
307        }
308
309        fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
310            unimplemented!();
311        }
312
313        fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
314            unimplemented!();
315        }
316
317        fn view(&self, ctx: &Context<Self>) -> Html {
318            let item_iter = ctx
319                .props()
320                .children
321                .iter()
322                .map(|item| html! {<li>{ item }</li>});
323            html! {
324                <ul>{ for item_iter }</ul>
325            }
326        }
327    }
328
329    fn setup_parent() -> (BSubtree, AnyScope, Element) {
330        let scope = AnyScope::test();
331        let parent = document().create_element("div").unwrap();
332        let root = BSubtree::create_root(&parent);
333
334        document().body().unwrap().append_child(&parent).unwrap();
335
336        (root, scope, parent)
337    }
338
339    fn get_html(node: Html, root: &BSubtree, scope: &AnyScope, parent: &Element) -> String {
340        // clear parent
341        parent.set_inner_html("");
342
343        node.attach(root, scope, parent, DomSlot::at_end());
344        scheduler::start_now();
345        parent.inner_html()
346    }
347
348    #[test]
349    fn all_ways_of_passing_children_work() {
350        let (root, scope, parent) = setup_parent();
351
352        let children: Vec<_> = vec!["a", "b", "c"]
353            .drain(..)
354            .map(|text| html! {<span>{ text }</span>})
355            .collect();
356        let children_renderer = Children::new(children.clone());
357        let expected_html = "\
358        <ul><li><span>a</span></li><li><span>b</span></li><li><span>c</span></li></ul>";
359
360        let prop_method = html! {
361            <List children={children_renderer.clone()} />
362        };
363        assert_eq!(get_html(prop_method, &root, &scope, &parent), expected_html);
364
365        let children_renderer_method = html! {
366            <List>
367                { children_renderer }
368            </List>
369        };
370        assert_eq!(
371            get_html(children_renderer_method, &root, &scope, &parent),
372            expected_html
373        );
374
375        let direct_method = html! {
376            <List>
377                { children.clone() }
378            </List>
379        };
380        assert_eq!(
381            get_html(direct_method, &root, &scope, &parent),
382            expected_html
383        );
384
385        let for_method = html! {
386            <List>
387                { for children }
388            </List>
389        };
390        assert_eq!(get_html(for_method, &root, &scope, &parent), expected_html);
391    }
392}
393
394#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
395#[cfg(test)]
396mod layout_tests {
397    extern crate self as yew;
398
399    use std::marker::PhantomData;
400
401    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
402
403    use crate::tests::layout_tests::{diff_layouts, TestLayout};
404    use crate::{html, Children, Component, Context, Html, Properties};
405
406    wasm_bindgen_test_configure!(run_in_browser);
407
408    struct Comp<T> {
409        _marker: PhantomData<T>,
410    }
411
412    #[derive(Properties, Clone, PartialEq)]
413    struct CompProps {
414        #[prop_or_default]
415        children: Children,
416    }
417
418    impl<T: 'static> Component for Comp<T> {
419        type Message = ();
420        type Properties = CompProps;
421
422        fn create(_: &Context<Self>) -> Self {
423            Comp {
424                _marker: PhantomData::default(),
425            }
426        }
427
428        fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
429            unimplemented!();
430        }
431
432        fn view(&self, ctx: &Context<Self>) -> Html {
433            html! {
434                <>{ ctx.props().children.clone() }</>
435            }
436        }
437    }
438
439    struct A;
440    struct B;
441
442    #[test]
443    fn diff() {
444        let layout1 = TestLayout {
445            name: "1",
446            node: html! {
447                <Comp<A>>
448                    <Comp<B>></Comp<B>>
449                    {"C"}
450                </Comp<A>>
451            },
452            expected: "C",
453        };
454
455        let layout2 = TestLayout {
456            name: "2",
457            node: html! {
458                <Comp<A>>
459                    {"A"}
460                </Comp<A>>
461            },
462            expected: "A",
463        };
464
465        let layout3 = TestLayout {
466            name: "3",
467            node: html! {
468                <Comp<B>>
469                    <Comp<A>></Comp<A>>
470                    {"B"}
471                </Comp<B>>
472            },
473            expected: "B",
474        };
475
476        let layout4 = TestLayout {
477            name: "4",
478            node: html! {
479                <Comp<B>>
480                    <Comp<A>>{"A"}</Comp<A>>
481                    {"B"}
482                </Comp<B>>
483            },
484            expected: "AB",
485        };
486
487        let layout5 = TestLayout {
488            name: "5",
489            node: html! {
490                <Comp<B>>
491                    <>
492                        <Comp<A>>
493                            {"A"}
494                        </Comp<A>>
495                    </>
496                    {"B"}
497                </Comp<B>>
498            },
499            expected: "AB",
500        };
501
502        let layout6 = TestLayout {
503            name: "6",
504            node: html! {
505                <Comp<B>>
506                    <>
507                        <Comp<A>>
508                            {"A"}
509                        </Comp<A>>
510                        {"B"}
511                    </>
512                    {"C"}
513                </Comp<B>>
514            },
515            expected: "ABC",
516        };
517
518        let layout7 = TestLayout {
519            name: "7",
520            node: html! {
521                <Comp<B>>
522                    <>
523                        <Comp<A>>
524                            {"A"}
525                        </Comp<A>>
526                        <Comp<A>>
527                            {"B"}
528                        </Comp<A>>
529                    </>
530                    {"C"}
531                </Comp<B>>
532            },
533            expected: "ABC",
534        };
535
536        let layout8 = TestLayout {
537            name: "8",
538            node: html! {
539                <Comp<B>>
540                    <>
541                        <Comp<A>>
542                            {"A"}
543                        </Comp<A>>
544                        <Comp<A>>
545                            <Comp<A>>
546                                {"B"}
547                            </Comp<A>>
548                        </Comp<A>>
549                    </>
550                    {"C"}
551                </Comp<B>>
552            },
553            expected: "ABC",
554        };
555
556        let layout9 = TestLayout {
557            name: "9",
558            node: html! {
559                <Comp<B>>
560                    <>
561                        <>
562                            {"A"}
563                        </>
564                        <Comp<A>>
565                            <Comp<A>>
566                                {"B"}
567                            </Comp<A>>
568                        </Comp<A>>
569                    </>
570                    {"C"}
571                </Comp<B>>
572            },
573            expected: "ABC",
574        };
575
576        let layout10 = TestLayout {
577            name: "10",
578            node: html! {
579                <Comp<B>>
580                    <>
581                        <Comp<A>>
582                            <Comp<A>>
583                                {"A"}
584                            </Comp<A>>
585                        </Comp<A>>
586                        <>
587                            {"B"}
588                        </>
589                    </>
590                    {"C"}
591                </Comp<B>>
592            },
593            expected: "ABC",
594        };
595
596        let layout11 = TestLayout {
597            name: "11",
598            node: html! {
599                <Comp<B>>
600                    <>
601                        <>
602                            <Comp<A>>
603                                <Comp<A>>
604                                    {"A"}
605                                </Comp<A>>
606                                {"B"}
607                            </Comp<A>>
608                        </>
609                    </>
610                    {"C"}
611                </Comp<B>>
612            },
613            expected: "ABC",
614        };
615
616        let layout12 = TestLayout {
617            name: "12",
618            node: html! {
619                <Comp<B>>
620                    <>
621                        <Comp<A>></Comp<A>>
622                        <>
623                            <Comp<A>>
624                                <>
625                                    <Comp<A>>
626                                        {"A"}
627                                    </Comp<A>>
628                                    <></>
629                                    <Comp<A>>
630                                        <Comp<A>></Comp<A>>
631                                        <></>
632                                        {"B"}
633                                        <></>
634                                        <Comp<A>></Comp<A>>
635                                    </Comp<A>>
636                                </>
637                            </Comp<A>>
638                            <></>
639                        </>
640                        <Comp<A>></Comp<A>>
641                    </>
642                    {"C"}
643                    <Comp<A>></Comp<A>>
644                    <></>
645                </Comp<B>>
646            },
647            expected: "ABC",
648        };
649
650        diff_layouts(vec![
651            layout1, layout2, layout3, layout4, layout5, layout6, layout7, layout8, layout9,
652            layout10, layout11, layout12,
653        ]);
654    }
655
656    #[test]
657    fn component_with_children() {
658        #[derive(Properties, PartialEq)]
659        struct Props {
660            children: Children,
661        }
662
663        struct ComponentWithChildren;
664
665        impl Component for ComponentWithChildren {
666            type Message = ();
667            type Properties = Props;
668
669            fn create(_ctx: &Context<Self>) -> Self {
670                Self
671            }
672
673            fn view(&self, ctx: &Context<Self>) -> Html {
674                html! {
675                  <ul>
676                    { for ctx.props().children.iter().map(|child| html! { <li>{ child }</li> }) }
677                  </ul>
678                }
679            }
680        }
681
682        let layout = TestLayout {
683            name: "13",
684            node: html! {
685                <ComponentWithChildren>
686                    if true {
687                        <span>{ "hello" }</span>
688                        <span>{ "world" }</span>
689                    }  else {
690                        <span>{ "goodbye" }</span>
691                        <span>{ "world" }</span>
692                    }
693                </ComponentWithChildren>
694            },
695            expected: "<ul><li><span>hello</span><span>world</span></li></ul>",
696        };
697
698        diff_layouts(vec![layout]);
699    }
700}