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

yew/virtual_dom/
vcomp.rs

1//! This module contains the implementation of a virtual component (`VComp`).
2
3use std::any::{Any, TypeId};
4use std::fmt;
5use std::rc::Rc;
6
7#[cfg(feature = "ssr")]
8use futures::future::{FutureExt, LocalBoxFuture};
9#[cfg(feature = "csr")]
10use web_sys::Element;
11
12use super::Key;
13#[cfg(feature = "hydration")]
14use crate::dom_bundle::Fragment;
15#[cfg(feature = "csr")]
16use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
17use crate::html::BaseComponent;
18#[cfg(feature = "csr")]
19use crate::html::Scoped;
20#[cfg(any(feature = "ssr", feature = "csr"))]
21use crate::html::{AnyScope, Scope};
22#[cfg(feature = "ssr")]
23use crate::{feat_ssr::VTagKind, platform::fmt::BufWriter};
24
25/// A virtual component.
26pub struct VComp {
27    pub(crate) type_id: TypeId,
28    pub(crate) mountable: Box<dyn Mountable>,
29    pub(crate) key: Option<Key>,
30    // for some reason, this reduces the bundle size by ~2-3 KBs
31    _marker: u32,
32}
33
34impl fmt::Debug for VComp {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("VComp")
37            .field("type_id", &self.type_id)
38            .field("mountable", &"..")
39            .field("key", &self.key)
40            .finish()
41    }
42}
43
44impl Clone for VComp {
45    fn clone(&self) -> Self {
46        Self {
47            type_id: self.type_id,
48            mountable: self.mountable.copy(),
49            key: self.key.clone(),
50            _marker: 0,
51        }
52    }
53}
54
55pub(crate) trait Mountable {
56    fn copy(&self) -> Box<dyn Mountable>;
57
58    fn mountable_eq(&self, rhs: &dyn Mountable) -> bool;
59    fn as_any(&self) -> &dyn Any;
60
61    #[cfg(feature = "csr")]
62    fn mount(
63        self: Box<Self>,
64        root: &BSubtree,
65        parent_scope: &AnyScope,
66        parent: Element,
67        slot: DomSlot,
68    ) -> (Box<dyn Scoped>, DynamicDomSlot);
69
70    #[cfg(feature = "csr")]
71    fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot);
72
73    #[cfg(feature = "ssr")]
74    fn render_into_stream<'a>(
75        &'a self,
76        w: &'a mut BufWriter,
77        parent_scope: &'a AnyScope,
78        hydratable: bool,
79        parent_vtag_kind: VTagKind,
80    ) -> LocalBoxFuture<'a, ()>;
81
82    #[cfg(feature = "hydration")]
83    fn hydrate(
84        self: Box<Self>,
85        root: BSubtree,
86        parent_scope: &AnyScope,
87        parent: Element,
88        fragment: &mut Fragment,
89        prev_next_sibling: &mut Option<DynamicDomSlot>,
90    ) -> (Box<dyn Scoped>, DynamicDomSlot);
91}
92
93pub(crate) struct PropsWrapper<COMP: BaseComponent> {
94    props: Rc<COMP::Properties>,
95}
96
97impl<COMP: BaseComponent> PropsWrapper<COMP> {
98    pub fn new(props: Rc<COMP::Properties>) -> Self {
99        Self { props }
100    }
101}
102
103impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
104    fn copy(&self) -> Box<dyn Mountable> {
105        let wrapper: PropsWrapper<COMP> = PropsWrapper {
106            props: Rc::clone(&self.props),
107        };
108        Box::new(wrapper)
109    }
110
111    fn as_any(&self) -> &dyn Any {
112        self
113    }
114
115    fn mountable_eq(&self, rhs: &dyn Mountable) -> bool {
116        rhs.as_any()
117            .downcast_ref::<Self>()
118            .map(|rhs| self.props == rhs.props)
119            .unwrap_or(false)
120    }
121
122    #[cfg(feature = "csr")]
123    fn mount(
124        self: Box<Self>,
125        root: &BSubtree,
126        parent_scope: &AnyScope,
127        parent: Element,
128        slot: DomSlot,
129    ) -> (Box<dyn Scoped>, DynamicDomSlot) {
130        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
131        let own_slot = scope.mount_in_place(root.clone(), parent, slot, self.props);
132
133        (Box::new(scope), own_slot)
134    }
135
136    #[cfg(feature = "csr")]
137    fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot) {
138        let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
139        scope.reuse(self.props, slot);
140    }
141
142    #[cfg(feature = "ssr")]
143    fn render_into_stream<'a>(
144        &'a self,
145        w: &'a mut BufWriter,
146        parent_scope: &'a AnyScope,
147        hydratable: bool,
148        parent_vtag_kind: VTagKind,
149    ) -> LocalBoxFuture<'a, ()> {
150        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
151
152        async move {
153            scope
154                .render_into_stream(w, self.props.clone(), hydratable, parent_vtag_kind)
155                .await;
156        }
157        .boxed_local()
158    }
159
160    #[cfg(feature = "hydration")]
161    fn hydrate(
162        self: Box<Self>,
163        root: BSubtree,
164        parent_scope: &AnyScope,
165        parent: Element,
166        fragment: &mut Fragment,
167        prev_next_sibling: &mut Option<DynamicDomSlot>,
168    ) -> (Box<dyn Scoped>, DynamicDomSlot) {
169        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
170        let own_slot =
171            scope.hydrate_in_place(root, parent, fragment, self.props, prev_next_sibling);
172
173        (Box::new(scope), own_slot)
174    }
175}
176
177/// A virtual child component.
178pub struct VChild<COMP: BaseComponent> {
179    /// The component properties
180    pub props: Rc<COMP::Properties>,
181    /// Reference to the mounted node
182    key: Option<Key>,
183}
184
185impl<COMP: BaseComponent> implicit_clone::ImplicitClone for VChild<COMP> {}
186
187impl<COMP: BaseComponent> Clone for VChild<COMP> {
188    fn clone(&self) -> Self {
189        VChild {
190            props: Rc::clone(&self.props),
191            key: self.key.clone(),
192        }
193    }
194}
195
196impl<COMP: BaseComponent> PartialEq for VChild<COMP>
197where
198    COMP::Properties: PartialEq,
199{
200    fn eq(&self, other: &VChild<COMP>) -> bool {
201        self.props == other.props
202    }
203}
204
205impl<COMP> VChild<COMP>
206where
207    COMP: BaseComponent,
208{
209    /// Creates a child component that can be accessed and modified by its parent.
210    pub fn new(props: COMP::Properties, key: Option<Key>) -> Self {
211        Self {
212            props: Rc::new(props),
213            key,
214        }
215    }
216}
217
218impl<COMP> VChild<COMP>
219where
220    COMP: BaseComponent,
221    COMP::Properties: Clone,
222{
223    /// Get a mutable reference to the underlying properties.
224    pub fn get_mut(&mut self) -> &mut COMP::Properties {
225        Rc::make_mut(&mut self.props)
226    }
227}
228
229impl<COMP> From<VChild<COMP>> for VComp
230where
231    COMP: BaseComponent,
232{
233    fn from(vchild: VChild<COMP>) -> Self {
234        VComp::new::<COMP>(vchild.props, vchild.key)
235    }
236}
237
238impl VComp {
239    /// Creates a new `VComp` instance.
240    pub fn new<COMP>(props: Rc<COMP::Properties>, key: Option<Key>) -> Self
241    where
242        COMP: BaseComponent,
243    {
244        VComp {
245            type_id: TypeId::of::<COMP>(),
246            mountable: Box::new(PropsWrapper::<COMP>::new(props)),
247            key,
248            _marker: 0,
249        }
250    }
251}
252
253impl PartialEq for VComp {
254    fn eq(&self, other: &VComp) -> bool {
255        self.key == other.key
256            && self.type_id == other.type_id
257            && self.mountable.mountable_eq(other.mountable.as_ref())
258    }
259}
260
261impl<COMP: BaseComponent> fmt::Debug for VChild<COMP> {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        f.write_str("VChild<_>")
264    }
265}
266
267#[cfg(feature = "ssr")]
268mod feat_ssr {
269    use super::*;
270    use crate::html::AnyScope;
271
272    impl VComp {
273        #[inline]
274        pub(crate) async fn render_into_stream(
275            &self,
276            w: &mut BufWriter,
277            parent_scope: &AnyScope,
278            hydratable: bool,
279            parent_vtag_kind: VTagKind,
280        ) {
281            self.mountable
282                .as_ref()
283                .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
284                .await;
285        }
286    }
287}
288
289#[cfg(all(test, not(target_arch = "wasm32"), feature = "ssr"))]
290mod ssr_tests {
291    use tokio::test;
292
293    use crate::prelude::*;
294    use crate::ServerRenderer;
295
296    #[test]
297    async fn test_props() {
298        #[derive(PartialEq, Properties, Debug)]
299        struct ChildProps {
300            name: String,
301        }
302
303        #[component]
304        fn Child(props: &ChildProps) -> Html {
305            html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
306        }
307
308        #[component]
309        fn Comp() -> Html {
310            html! {
311                <div>
312                    <Child name="Jane" />
313                    <Child name="John" />
314                    <Child name="Josh" />
315                </div>
316            }
317        }
318
319        let s = ServerRenderer::<Comp>::new()
320            .hydratable(false)
321            .render()
322            .await;
323
324        assert_eq!(
325            s,
326            "<div><div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div></div>"
327        );
328    }
329}