mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
move diffing into the global runtime
This commit is contained in:
parent
f42ef3ef9d
commit
c70e2bfcb6
8 changed files with 152 additions and 210 deletions
|
@ -1,14 +1,40 @@
|
|||
use crate::{nodes::RenderReturn, scopes::ScopeState, Element};
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use crate::{nodes::RenderReturn, Element};
|
||||
use std::{ops::Deref, panic::AssertUnwindSafe};
|
||||
|
||||
/// A boxed version of AnyProps that can be cloned
|
||||
pub(crate) struct BoxedAnyProps {
|
||||
inner: Box<dyn AnyProps>,
|
||||
}
|
||||
|
||||
impl BoxedAnyProps {
|
||||
fn new(inner: impl AnyProps + 'static) -> Self {
|
||||
Self {
|
||||
inner: Box::new(inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BoxedAnyProps {
|
||||
type Target = dyn AnyProps;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BoxedAnyProps {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.duplicate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that essentially allows VComponentProps to be used generically
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should not be implemented outside this module
|
||||
pub(crate) trait AnyProps {
|
||||
fn render<'a>(&'a self, bump: &'a ScopeState) -> RenderReturn;
|
||||
fn render<'a>(&'a self) -> RenderReturn;
|
||||
fn memoize(&self, other: &dyn AnyProps) -> bool;
|
||||
fn duplicate(&self) -> Box<dyn AnyProps>;
|
||||
}
|
||||
|
||||
pub(crate) struct VProps<P> {
|
||||
|
@ -36,7 +62,7 @@ impl<P: Clone> AnyProps for VProps<P> {
|
|||
(self.memo)(self, other)
|
||||
}
|
||||
|
||||
fn render(&self, cx: &ScopeState) -> RenderReturn {
|
||||
fn render(&self) -> RenderReturn {
|
||||
let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
||||
// Call the render function directly
|
||||
(self.render_fn)(self.props.clone())
|
||||
|
@ -52,4 +78,12 @@ impl<P: Clone> AnyProps for VProps<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn duplicate(&self) -> Box<dyn AnyProps> {
|
||||
Box::new(Self {
|
||||
render_fn: self.render_fn,
|
||||
memo: self.memo,
|
||||
props: self.props.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::ptr::NonNull;
|
||||
|
||||
use crate::{
|
||||
innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom,
|
||||
AttributeValue, DynamicNode, ScopeId,
|
||||
innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode,
|
||||
ScopeId,
|
||||
};
|
||||
|
||||
/// An Element's unique identifier.
|
||||
|
@ -48,14 +48,6 @@ impl VirtualDom {
|
|||
std::mem::transmute::<NonNull<VNode>, _>(vnode.into())
|
||||
})));
|
||||
|
||||
// Set this id to be dropped when the scope is rerun
|
||||
if let Some(scope) = self.runtime.current_scope_id() {
|
||||
self.scopes[scope.0]
|
||||
.element_refs_to_drop
|
||||
.borrow_mut()
|
||||
.push(new_id);
|
||||
}
|
||||
|
||||
new_id
|
||||
}
|
||||
|
||||
|
@ -89,17 +81,6 @@ impl VirtualDom {
|
|||
id,
|
||||
});
|
||||
|
||||
// Remove all VNode ids from the scope
|
||||
for id in self.scopes[id.0]
|
||||
.element_refs_to_drop
|
||||
.borrow_mut()
|
||||
.drain(..)
|
||||
{
|
||||
self.element_refs.try_remove(id.0);
|
||||
}
|
||||
|
||||
self.ensure_drop_safety(id);
|
||||
|
||||
if recursive {
|
||||
if let Some(root) = self.scopes[id.0].try_root_node() {
|
||||
if let RenderReturn::Ready(node) = root {
|
||||
|
@ -110,18 +91,6 @@ impl VirtualDom {
|
|||
|
||||
let scope = &mut self.scopes[id.0];
|
||||
|
||||
// Drop all the hooks once the children are dropped
|
||||
// this means we'll drop hooks bottom-up
|
||||
scope.hooks.get_mut().clear();
|
||||
{
|
||||
let context = scope.context();
|
||||
|
||||
// Drop all the futures once the hooks are dropped
|
||||
for task_id in context.spawned_tasks.borrow_mut().drain() {
|
||||
context.tasks.remove(task_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.scopes.remove(id.0);
|
||||
}
|
||||
|
||||
|
@ -139,45 +108,6 @@ impl VirtualDom {
|
|||
DynamicNode::Text(_) => {}
|
||||
});
|
||||
}
|
||||
|
||||
/// Descend through the tree, removing any borrowed props and listeners
|
||||
pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) {
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
|
||||
{
|
||||
// Drop all element refs that could be invalidated when the component was rerun
|
||||
let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut();
|
||||
let element_refs_slab = &mut self.element_refs;
|
||||
for element_ref in element_refs.drain(..) {
|
||||
if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) {
|
||||
*element_ref = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||
// run the hooks (which hold an &mut Reference)
|
||||
// recursively call ensure_drop_safety on all children
|
||||
let props = { scope.borrowed_props.borrow_mut().clone() };
|
||||
for comp in props {
|
||||
let comp = unsafe { &*comp };
|
||||
match comp.scope.get() {
|
||||
Some(child) if child != scope_id => self.ensure_drop_safety(child),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
scope.borrowed_props.borrow_mut().clear();
|
||||
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
|
||||
listeners.drain(..).for_each(|listener| {
|
||||
let listener = unsafe { &*listener };
|
||||
if let AttributeValue::Listener(l) = &listener.value {
|
||||
_ = l.take();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementPath {
|
||||
|
|
|
@ -219,13 +219,12 @@ impl VirtualDom {
|
|||
// copy out the box for both
|
||||
let old_scope = &self.scopes[scope_id.0];
|
||||
let old = old_scope.props.as_ref();
|
||||
let new: Box<dyn AnyProps> = right.props.take().unwrap();
|
||||
let new: Box<dyn AnyProps> = unsafe { std::mem::transmute(new) };
|
||||
let new: &dyn AnyProps = right.props.as_ref();
|
||||
|
||||
// If the props are static, then we try to memoize by setting the new with the old
|
||||
// The target scopestate still has the reference to the old props, so there's no need to update anything
|
||||
// This also implicitly drops the new props since they're not used
|
||||
if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
|
||||
if old.memoize(new) {
|
||||
tracing::trace!(
|
||||
"Memoized props for component {:#?} ({})",
|
||||
scope_id,
|
||||
|
@ -235,7 +234,7 @@ impl VirtualDom {
|
|||
}
|
||||
|
||||
// First, move over the props from the old to the new, dropping old props in the process
|
||||
self.scopes[scope_id.0].props = Some(new);
|
||||
self.scopes[scope_id.0].props = new;
|
||||
|
||||
// Now run the component and diff it
|
||||
self.run_scope(scope_id);
|
||||
|
|
|
@ -341,7 +341,7 @@ pub struct VComponent {
|
|||
/// It is possible that components get folded at compile time, so these shouldn't be really used as a key
|
||||
pub(crate) render_fn: *const (),
|
||||
|
||||
pub(crate) props: Box<dyn AnyProps>,
|
||||
pub(crate) props: BoxedAnyProps,
|
||||
}
|
||||
|
||||
impl<'a> VComponent {
|
||||
|
@ -362,7 +362,7 @@ impl<'a> std::fmt::Debug for VComponent {
|
|||
}
|
||||
|
||||
/// An instance of some text, mounted to the DOM
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VText {
|
||||
/// The actual text itself
|
||||
pub value: String,
|
||||
|
@ -436,7 +436,7 @@ pub enum TemplateAttribute {
|
|||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Attribute {
|
||||
/// The name of the attribute.
|
||||
pub name: &'static str,
|
||||
|
@ -483,6 +483,7 @@ impl Attribute {
|
|||
///
|
||||
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
|
||||
/// variant.
|
||||
#[derive(Clone)]
|
||||
pub enum AttributeValue {
|
||||
/// Text attribute
|
||||
Text(String),
|
||||
|
@ -497,10 +498,10 @@ pub enum AttributeValue {
|
|||
Bool(bool),
|
||||
|
||||
/// A listener, like "onclick"
|
||||
Listener(RefCell<Option<ListenerCb>>),
|
||||
Listener(ListenerCb),
|
||||
|
||||
/// An arbitrary value that implements PartialEq and is static
|
||||
Any(RefCell<Option<Box<dyn AnyValue>>>),
|
||||
Any(Box<dyn AnyValue>),
|
||||
|
||||
/// A "none" value, resulting in the removal of an attribute from the dom
|
||||
None,
|
||||
|
|
|
@ -24,15 +24,8 @@ impl VirtualDom {
|
|||
runtime: self.runtime.clone(),
|
||||
context_id: id,
|
||||
|
||||
props: Some(props),
|
||||
|
||||
render_cnt: Default::default(),
|
||||
hooks: Default::default(),
|
||||
hook_idx: Default::default(),
|
||||
|
||||
borrowed_props: Default::default(),
|
||||
attributes_to_drop_before_render: Default::default(),
|
||||
element_refs_to_drop: Default::default(),
|
||||
props,
|
||||
last_rendered_node: Default::default(),
|
||||
}));
|
||||
|
||||
let context =
|
||||
|
@ -42,42 +35,30 @@ impl VirtualDom {
|
|||
scope
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
|
||||
self.runtime.scope_stack.borrow_mut().push(scope_id);
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
self.ensure_drop_safety(scope_id);
|
||||
|
||||
let new_nodes = unsafe {
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
scope.previous_frame().reset();
|
||||
|
||||
scope.context().suspended.set(false);
|
||||
|
||||
scope.hook_idx.set(0);
|
||||
|
||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||
let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref();
|
||||
let props: &dyn AnyProps = std::mem::transmute(props);
|
||||
|
||||
let _span = tracing::trace_span!("render", scope = %scope.context().name);
|
||||
props.render(scope)
|
||||
};
|
||||
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
|
||||
// We write on top of the previous frame and then make it the current by pushing the generation forward
|
||||
let frame = scope.previous_frame();
|
||||
|
||||
// set the new head of the bump frame
|
||||
let allocated = &*frame.bump().alloc(new_nodes);
|
||||
frame.node.set(allocated);
|
||||
|
||||
// And move the render generation forward by one
|
||||
scope.render_cnt.set(scope.render_cnt.get() + 1);
|
||||
|
||||
let context = scope.context();
|
||||
context.suspended.set(false);
|
||||
context.hook_index.set(0);
|
||||
|
||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||
let props: &dyn AnyProps = &*scope.props;
|
||||
|
||||
let _span = tracing::trace_span!("render", scope = %scope.context().name);
|
||||
props.render()
|
||||
};
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
|
||||
let context = scope.context();
|
||||
|
||||
// And move the render generation forward by one
|
||||
context.render_count.set(context.render_count.get() + 1);
|
||||
|
||||
// remove this scope from dirty scopes
|
||||
self.dirty_scopes.remove(&DirtyScope {
|
||||
height: context.height,
|
||||
|
@ -85,18 +66,15 @@ impl VirtualDom {
|
|||
});
|
||||
|
||||
if context.suspended.get() {
|
||||
if matches!(allocated, RenderReturn::Aborted(_)) {
|
||||
if matches!(new_nodes, RenderReturn::Aborted(_)) {
|
||||
self.suspended_scopes.insert(context.id);
|
||||
}
|
||||
} else if !self.suspended_scopes.is_empty() {
|
||||
_ = self.suspended_scopes.remove(&context.id);
|
||||
}
|
||||
|
||||
// rebind the lifetime now that its stored internally
|
||||
let result = unsafe { allocated };
|
||||
|
||||
self.runtime.scope_stack.borrow_mut().pop();
|
||||
|
||||
result
|
||||
new_nodes
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,15 @@ pub(crate) struct ScopeContext {
|
|||
pub(crate) parent_id: Option<ScopeId>,
|
||||
|
||||
pub(crate) height: u32,
|
||||
pub(crate) render_count: Cell<usize>,
|
||||
|
||||
pub(crate) suspended: Cell<bool>,
|
||||
|
||||
pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
|
||||
|
||||
pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,
|
||||
pub(crate) hook_index: Cell<usize>,
|
||||
|
||||
pub(crate) tasks: Rc<Scheduler>,
|
||||
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
|
||||
}
|
||||
|
@ -43,10 +48,13 @@ impl ScopeContext {
|
|||
id,
|
||||
parent_id,
|
||||
height,
|
||||
render_count: Cell::new(0),
|
||||
suspended: Cell::new(false),
|
||||
shared_contexts: RefCell::new(vec![]),
|
||||
tasks,
|
||||
spawned_tasks: RefCell::new(FxHashSet::default()),
|
||||
hooks: RefCell::new(vec![]),
|
||||
hook_index: Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,6 +252,65 @@ impl ScopeContext {
|
|||
self.suspended.set(true);
|
||||
None
|
||||
}
|
||||
|
||||
/// Store a value between renders. The foundational hook for all other hooks.
|
||||
///
|
||||
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
||||
///
|
||||
/// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use dioxus_core::ScopeState;
|
||||
///
|
||||
/// // prints a greeting on the initial render
|
||||
/// pub fn use_hello_world(cx: &ScopeState) {
|
||||
/// cx.use_hook(|| println!("Hello, world!"));
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
||||
let cur_hook = self.hook_index.get();
|
||||
let mut hooks = self.hooks.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
|
||||
|
||||
if cur_hook >= hooks.len() {
|
||||
hooks.push(Box::new(initializer()));
|
||||
}
|
||||
|
||||
hooks
|
||||
.get(cur_hook)
|
||||
.and_then(|inn| {
|
||||
self.hook_index.set(cur_hook + 1);
|
||||
let raw_ref: &mut dyn Any = inn.as_mut();
|
||||
raw_ref.downcast_mut::<State>()
|
||||
})
|
||||
.expect(
|
||||
r#"
|
||||
Unable to retrieve the hook that was initialized at this index.
|
||||
Consult the `rules of hooks` to understand how to use hooks properly.
|
||||
|
||||
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
|
||||
Functions prefixed with "use" should never be called conditionally.
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the current render since the inception of this component
|
||||
///
|
||||
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
||||
pub fn generation(&self) -> usize {
|
||||
self.render_count.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScopeContext {
|
||||
fn drop(&mut self) {
|
||||
// Drop all spawned tasks
|
||||
for id in self.spawned_tasks.borrow().iter() {
|
||||
self.tasks.remove(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule an update for any component given its [`ScopeId`].
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
any_props::AnyProps,
|
||||
any_props::VProps,
|
||||
innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText},
|
||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
||||
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||
runtime::Runtime,
|
||||
scope_context::ScopeContext,
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||
};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, Ref, RefCell, UnsafeCell},
|
||||
cell::{Ref, RefCell},
|
||||
fmt::{Arguments, Debug},
|
||||
future::Future,
|
||||
rc::Rc,
|
||||
|
@ -47,16 +47,9 @@ pub struct ScopeState {
|
|||
pub(crate) runtime: Rc<Runtime>,
|
||||
pub(crate) context_id: ScopeId,
|
||||
|
||||
pub(crate) render_cnt: Cell<usize>,
|
||||
pub(crate) last_rendered_node: Option<RenderReturn>,
|
||||
|
||||
pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
|
||||
pub(crate) hook_idx: Cell<usize>,
|
||||
|
||||
pub(crate) borrowed_props: RefCell<Vec<*const VComponent>>,
|
||||
pub(crate) element_refs_to_drop: RefCell<Vec<VNodeId>>,
|
||||
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute>>,
|
||||
|
||||
pub(crate) props: Option<Box<dyn AnyProps>>,
|
||||
pub(crate) props: Box<dyn AnyProps>,
|
||||
}
|
||||
|
||||
impl Drop for ScopeState {
|
||||
|
@ -75,13 +68,6 @@ impl<'src> ScopeState {
|
|||
self.context().name
|
||||
}
|
||||
|
||||
/// Get the current render since the inception of this component
|
||||
///
|
||||
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
||||
pub fn generation(&self) -> usize {
|
||||
self.render_cnt.get()
|
||||
}
|
||||
|
||||
/// Get a handle to the currently active head node arena for this Scope
|
||||
///
|
||||
/// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
|
||||
|
@ -98,15 +84,7 @@ impl<'src> ScopeState {
|
|||
///
|
||||
/// Returns [`None`] if the tree has not been built yet.
|
||||
pub fn try_root_node(&self) -> Option<&RenderReturn> {
|
||||
let ptr = self.current_frame().node.get();
|
||||
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let r: &RenderReturn = unsafe { &*ptr };
|
||||
|
||||
unsafe { std::mem::transmute(r) }
|
||||
self.last_rendered_node.as_ref()
|
||||
}
|
||||
|
||||
/// Get the height of this Scope - IE the number of scopes above it.
|
||||
|
@ -354,21 +332,19 @@ impl<'src> ScopeState {
|
|||
&'src self,
|
||||
mut callback: impl FnMut(Event<T>) + 'src,
|
||||
) -> AttributeValue {
|
||||
AttributeValue::Listener(RefCell::new(Some(Box::new(
|
||||
move |event: Event<dyn Any>| {
|
||||
AttributeValue::Listener(Box::new(move |event: Event<dyn Any>| {
|
||||
if let Ok(data) = event.data.downcast::<T>() {
|
||||
callback(Event {
|
||||
propagates: event.propagates,
|
||||
data,
|
||||
});
|
||||
}
|
||||
},
|
||||
))))
|
||||
}))
|
||||
}
|
||||
|
||||
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
|
||||
pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue {
|
||||
AttributeValue::Any(RefCell::new(Some(Box::new(value))))
|
||||
AttributeValue::Any(Box::new(value))
|
||||
}
|
||||
|
||||
/// Mark this component as suspended and then return None
|
||||
|
@ -377,47 +353,4 @@ impl<'src> ScopeState {
|
|||
cx.suspend();
|
||||
None
|
||||
}
|
||||
|
||||
/// Store a value between renders. The foundational hook for all other hooks.
|
||||
///
|
||||
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
||||
///
|
||||
/// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use dioxus_core::ScopeState;
|
||||
///
|
||||
/// // prints a greeting on the initial render
|
||||
/// pub fn use_hello_world(cx: &ScopeState) {
|
||||
/// cx.use_hook(|| println!("Hello, world!"));
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
||||
let cur_hook = self.hook_idx.get();
|
||||
let mut hooks = self.hooks.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
|
||||
|
||||
if cur_hook >= hooks.len() {
|
||||
hooks.push(Box::new(UnsafeCell::new(initializer())));
|
||||
}
|
||||
|
||||
hooks
|
||||
.get(cur_hook)
|
||||
.and_then(|inn| {
|
||||
self.hook_idx.set(cur_hook + 1);
|
||||
let raw_ref = unsafe { &mut *inn.get() };
|
||||
raw_ref.downcast_mut::<State>()
|
||||
})
|
||||
.expect(
|
||||
r#"
|
||||
Unable to retrieve the hook that was initialized at this index.
|
||||
Consult the `rules of hooks` to understand how to use hooks properly.
|
||||
|
||||
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
|
||||
Functions prefixed with "use" should never be called conditionally.
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,7 +224,7 @@ impl VirtualDom {
|
|||
/// ```
|
||||
///
|
||||
/// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
|
||||
pub fn new(app: fn() -> Element) -> Self {
|
||||
pub fn new(app: fn(()) -> Element) -> Self {
|
||||
Self::new_with_props(app, ())
|
||||
}
|
||||
|
||||
|
@ -258,7 +258,7 @@ impl VirtualDom {
|
|||
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||
/// let mutations = dom.rebuild();
|
||||
/// ```
|
||||
pub fn new_with_props<P: 'static>(root: fn(P) -> Element, root_props: P) -> Self {
|
||||
pub fn new_with_props<P: Clone + 'static>(root: fn(P) -> Element, root_props: P) -> Self {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
let scheduler = Scheduler::new(tx);
|
||||
let mut dom = Self {
|
||||
|
@ -450,7 +450,7 @@ impl VirtualDom {
|
|||
let origin = path.scope;
|
||||
self.runtime.scope_stack.borrow_mut().push(origin);
|
||||
self.runtime.rendering.set(false);
|
||||
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
|
||||
if let Some(cb) = listener.as_deref_mut() {
|
||||
cb(uievent.clone());
|
||||
}
|
||||
self.runtime.scope_stack.borrow_mut().pop();
|
||||
|
|
Loading…
Reference in a new issue