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> VChild<COMP>
220where
221    COMP: BaseComponent,
222    COMP::Properties: Clone,
223{
224    /// Get a mutable reference to the underlying properties.
225    pub fn get_mut(&mut self) -> &mut COMP::Properties {
226        Rc::make_mut(&mut self.props)
227    }
228}
229
230impl<COMP> From<VChild<COMP>> for VComp
231where
232    COMP: BaseComponent,
233{
234    fn from(vchild: VChild<COMP>) -> Self {
235        VComp::new::<COMP>(vchild.props, vchild.key)
236    }
237}
238
239impl VComp {
240    /// Creates a new `VComp` instance.
241    pub fn new<COMP>(props: Rc<COMP::Properties>, key: Option<Key>) -> Self
242    where
243        COMP: BaseComponent,
244    {
245        VComp {
246            type_id: TypeId::of::<COMP>(),
247            mountable: Box::new(PropsWrapper::<COMP>::new(props)),
248            key,
249            _marker: 0,
250        }
251    }
252}
253
254impl PartialEq for VComp {
255    fn eq(&self, other: &VComp) -> bool {
256        self.key == other.key
257            && self.type_id == other.type_id
258            && self.mountable.mountable_eq(other.mountable.as_ref())
259    }
260}
261
262impl<COMP: BaseComponent> fmt::Debug for VChild<COMP> {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        f.write_str("VChild<_>")
265    }
266}
267
268#[cfg(feature = "ssr")]
269mod feat_ssr {
270    use super::*;
271    use crate::html::AnyScope;
272
273    impl VComp {
274        #[inline]
275        pub(crate) async fn render_into_stream(
276            &self,
277            w: &mut BufWriter,
278            parent_scope: &AnyScope,
279            hydratable: bool,
280            parent_vtag_kind: VTagKind,
281        ) {
282            self.mountable
283                .as_ref()
284                .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
285                .await;
286        }
287    }
288}
289
290#[cfg(all(test, not(target_arch = "wasm32"), feature = "ssr"))]
291mod ssr_tests {
292    use tokio::test;
293
294    use crate::prelude::*;
295    use crate::ServerRenderer;
296
297    #[test]
298    async fn test_props() {
299        #[derive(PartialEq, Properties, Debug)]
300        struct ChildProps {
301            name: String,
302        }
303
304        #[component]
305        fn Child(props: &ChildProps) -> Html {
306            html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
307        }
308
309        #[component]
310        fn Comp() -> Html {
311            html! {
312                <div>
313                    <Child name="Jane" />
314                    <Child name="John" />
315                    <Child name="Josh" />
316                </div>
317            }
318        }
319
320        let s = ServerRenderer::<Comp>::new()
321            .hydratable(false)
322            .render()
323            .await;
324
325        assert_eq!(
326            s,
327            "<div><div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div></div>"
328        );
329    }
330}