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        internal_ref: DynamicDomSlot,
69    ) -> Box<dyn Scoped>;
70
71    #[cfg(feature = "csr")]
72    fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot);
73
74    #[cfg(feature = "ssr")]
75    fn render_into_stream<'a>(
76        &'a self,
77        w: &'a mut BufWriter,
78        parent_scope: &'a AnyScope,
79        hydratable: bool,
80        parent_vtag_kind: VTagKind,
81    ) -> LocalBoxFuture<'a, ()>;
82
83    #[cfg(feature = "hydration")]
84    fn hydrate(
85        self: Box<Self>,
86        root: BSubtree,
87        parent_scope: &AnyScope,
88        parent: Element,
89        internal_ref: DynamicDomSlot,
90        fragment: &mut Fragment,
91    ) -> Box<dyn Scoped>;
92}
93
94pub(crate) struct PropsWrapper<COMP: BaseComponent> {
95    props: Rc<COMP::Properties>,
96}
97
98impl<COMP: BaseComponent> PropsWrapper<COMP> {
99    pub fn new(props: Rc<COMP::Properties>) -> Self {
100        Self { props }
101    }
102}
103
104impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
105    fn copy(&self) -> Box<dyn Mountable> {
106        let wrapper: PropsWrapper<COMP> = PropsWrapper {
107            props: Rc::clone(&self.props),
108        };
109        Box::new(wrapper)
110    }
111
112    fn as_any(&self) -> &dyn Any {
113        self
114    }
115
116    fn mountable_eq(&self, rhs: &dyn Mountable) -> bool {
117        rhs.as_any()
118            .downcast_ref::<Self>()
119            .map(|rhs| self.props == rhs.props)
120            .unwrap_or(false)
121    }
122
123    #[cfg(feature = "csr")]
124    fn mount(
125        self: Box<Self>,
126        root: &BSubtree,
127        parent_scope: &AnyScope,
128        parent: Element,
129        slot: DomSlot,
130        internal_ref: DynamicDomSlot,
131    ) -> Box<dyn Scoped> {
132        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
133        scope.mount_in_place(root.clone(), parent, slot, internal_ref, self.props);
134
135        Box::new(scope)
136    }
137
138    #[cfg(feature = "csr")]
139    fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot) {
140        let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
141        scope.reuse(self.props, slot);
142    }
143
144    #[cfg(feature = "ssr")]
145    fn render_into_stream<'a>(
146        &'a self,
147        w: &'a mut BufWriter,
148        parent_scope: &'a AnyScope,
149        hydratable: bool,
150        parent_vtag_kind: VTagKind,
151    ) -> LocalBoxFuture<'a, ()> {
152        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
153
154        async move {
155            scope
156                .render_into_stream(w, self.props.clone(), hydratable, parent_vtag_kind)
157                .await;
158        }
159        .boxed_local()
160    }
161
162    #[cfg(feature = "hydration")]
163    fn hydrate(
164        self: Box<Self>,
165        root: BSubtree,
166        parent_scope: &AnyScope,
167        parent: Element,
168        internal_ref: DynamicDomSlot,
169        fragment: &mut Fragment,
170    ) -> Box<dyn Scoped> {
171        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
172        scope.hydrate_in_place(root, parent, fragment, internal_ref, self.props);
173
174        Box::new(scope)
175    }
176}
177
178/// A virtual child component.
179pub struct VChild<COMP: BaseComponent> {
180    /// The component properties
181    pub props: Rc<COMP::Properties>,
182    /// Reference to the mounted node
183    key: Option<Key>,
184}
185
186impl<COMP: BaseComponent> implicit_clone::ImplicitClone for VChild<COMP> {}
187
188impl<COMP: BaseComponent> Clone for VChild<COMP> {
189    fn clone(&self) -> Self {
190        VChild {
191            props: Rc::clone(&self.props),
192            key: self.key.clone(),
193        }
194    }
195}
196
197impl<COMP: BaseComponent> PartialEq for VChild<COMP>
198where
199    COMP::Properties: PartialEq,
200{
201    fn eq(&self, other: &VChild<COMP>) -> bool {
202        self.props == other.props
203    }
204}
205
206impl<COMP> VChild<COMP>
207where
208    COMP: BaseComponent,
209{
210    /// Creates a child component that can be accessed and modified by its parent.
211    pub fn new(props: COMP::Properties, key: Option<Key>) -> Self {
212        Self {
213            props: Rc::new(props),
214            key,
215        }
216    }
217}
218
219impl<COMP> From<VChild<COMP>> for VComp
220where
221    COMP: BaseComponent,
222{
223    fn from(vchild: VChild<COMP>) -> Self {
224        VComp::new::<COMP>(vchild.props, vchild.key)
225    }
226}
227
228impl VComp {
229    /// Creates a new `VComp` instance.
230    pub fn new<COMP>(props: Rc<COMP::Properties>, key: Option<Key>) -> Self
231    where
232        COMP: BaseComponent,
233    {
234        VComp {
235            type_id: TypeId::of::<COMP>(),
236            mountable: Box::new(PropsWrapper::<COMP>::new(props)),
237            key,
238            _marker: 0,
239        }
240    }
241}
242
243impl PartialEq for VComp {
244    fn eq(&self, other: &VComp) -> bool {
245        self.key == other.key
246            && self.type_id == other.type_id
247            && self.mountable.mountable_eq(other.mountable.as_ref())
248    }
249}
250
251impl<COMP: BaseComponent> fmt::Debug for VChild<COMP> {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        f.write_str("VChild<_>")
254    }
255}
256
257#[cfg(feature = "ssr")]
258mod feat_ssr {
259    use super::*;
260    use crate::html::AnyScope;
261
262    impl VComp {
263        #[inline]
264        pub(crate) async fn render_into_stream(
265            &self,
266            w: &mut BufWriter,
267            parent_scope: &AnyScope,
268            hydratable: bool,
269            parent_vtag_kind: VTagKind,
270        ) {
271            self.mountable
272                .as_ref()
273                .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
274                .await;
275        }
276    }
277}
278
279#[cfg(all(test, not(target_arch = "wasm32"), feature = "ssr"))]
280mod ssr_tests {
281    use tokio::test;
282
283    use crate::prelude::*;
284    use crate::ServerRenderer;
285
286    #[test]
287    async fn test_props() {
288        #[derive(PartialEq, Properties, Debug)]
289        struct ChildProps {
290            name: String,
291        }
292
293        #[function_component]
294        fn Child(props: &ChildProps) -> Html {
295            html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
296        }
297
298        #[function_component]
299        fn Comp() -> Html {
300            html! {
301                <div>
302                    <Child name="Jane" />
303                    <Child name="John" />
304                    <Child name="Josh" />
305                </div>
306            }
307        }
308
309        let s = ServerRenderer::<Comp>::new()
310            .hydratable(false)
311            .render()
312            .await;
313
314        assert_eq!(
315            s,
316            "<div><div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div></div>"
317        );
318    }
319}