move ScopeId methods to the runtime (#4723)
* move `ScopeId` methods to runtime methods * cargo check unused * wip... * wip... * fix consume context * fix test * guard hoisted * fix doc comment
This commit is contained in:
1 parent
c56bac04f4
commit
c29cef4ea3
40 files changed
+466
-697
No files matched your search
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
innerlude::{CapturedError, SchedulerMsg, SuspenseContext},
|
||||
runtime::RuntimeError,
|
||||
innerlude::{SchedulerMsg, SuspenseContext},
|
||||
Runtime, ScopeId, Task,
|
||||
};
|
||||
use generational_box::{AnyStorage, Owner};
|
||||
@@ -96,7 +95,7 @@ impl Scope {
|
||||
}
|
||||
|
||||
fn sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
|
||||
Runtime::with(|rt| rt.sender.clone()).unwrap_or_else(|e| panic!("{}", e))
|
||||
Runtime::current().sender.clone()
|
||||
}
|
||||
|
||||
/// Mount the scope and queue any pending effects if it is not already mounted
|
||||
@@ -133,12 +132,12 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// Mark this scope as dirty, and schedule a render for it.
|
||||
pub fn needs_update(&self) {
|
||||
pub(crate) fn needs_update(&self) {
|
||||
self.needs_update_any(self.id)
|
||||
}
|
||||
|
||||
/// Mark this scope as dirty, and schedule a render for it.
|
||||
pub fn needs_update_any(&self, id: ScopeId) {
|
||||
pub(crate) fn needs_update_any(&self, id: ScopeId) {
|
||||
self.sender()
|
||||
.unbounded_send(SchedulerMsg::Immediate(id))
|
||||
.expect("Scheduler to exist if scope exists");
|
||||
@@ -151,7 +150,7 @@ impl Scope {
|
||||
/// Note: The function returned by this method will schedule an update for the current component even if it has already updated between when `schedule_update` was called and when the returned function is called.
|
||||
/// If the desired behavior is to invalidate the current rendering of the current component (and no-op if already invalidated)
|
||||
/// [`subscribe`](crate::reactive_context::ReactiveContext::subscribe) to the [`current`](crate::reactive_context::ReactiveContext::current) [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
|
||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||
pub(crate) fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||
let (chan, id) = (self.sender(), self.id);
|
||||
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||
}
|
||||
@@ -164,7 +163,7 @@ impl Scope {
|
||||
///
|
||||
/// Note: It does not matter when `schedule_update_any` is called: the returned function will invalidate what ever generation of the specified component is current when returned function is called.
|
||||
/// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
|
||||
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
||||
pub(crate) fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
||||
let chan = self.sender();
|
||||
Arc::new(move |id| {
|
||||
_ = chan.unbounded_send(SchedulerMsg::Immediate(id));
|
||||
@@ -172,7 +171,7 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// Get the owner for the current scope.
|
||||
pub fn owner<S: AnyStorage>(&self) -> Owner<S> {
|
||||
pub(crate) fn owner<S: AnyStorage>(&self) -> Owner<S> {
|
||||
match self.has_context() {
|
||||
Some(rt) => rt,
|
||||
None => {
|
||||
@@ -183,7 +182,7 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// Return any context of type T if it exists on this scope
|
||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
pub(crate) fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
self.shared_contexts
|
||||
.borrow()
|
||||
.iter()
|
||||
@@ -194,49 +193,23 @@ impl Scope {
|
||||
/// Try to retrieve a shared state with type `T` from any parent scope.
|
||||
///
|
||||
/// Clones the state if it exists.
|
||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
tracing::trace!(
|
||||
"looking for context {} ({:?}) in {:?}",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>(),
|
||||
self.id
|
||||
);
|
||||
if let Some(this_ctx) = self.has_context() {
|
||||
pub(crate) fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
if let Some(this_ctx) = self.has_context::<T>() {
|
||||
return Some(this_ctx);
|
||||
}
|
||||
|
||||
let mut search_parent = self.parent_id;
|
||||
let cur_runtime = Runtime::with(|runtime| {
|
||||
|
||||
Runtime::with(|runtime| {
|
||||
while let Some(parent_id) = search_parent {
|
||||
let Some(parent) = runtime.get_state(parent_id) else {
|
||||
tracing::error!("Parent scope {:?} not found", parent_id);
|
||||
return None;
|
||||
};
|
||||
tracing::trace!(
|
||||
"looking for context {} ({:?}) in {:?}",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>(),
|
||||
parent.id
|
||||
);
|
||||
if let Some(shared) = parent.has_context() {
|
||||
let parent = runtime.try_get_state(parent_id)?;
|
||||
if let Some(shared) = parent.has_context::<T>() {
|
||||
return Some(shared);
|
||||
}
|
||||
search_parent = parent.parent_id;
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
match cur_runtime.ok().flatten() {
|
||||
Some(ctx) => Some(ctx),
|
||||
None => {
|
||||
tracing::trace!(
|
||||
"context {} ({:?}) not found",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Inject a `Box<dyn Any>` into the context of this scope
|
||||
@@ -281,13 +254,7 @@ impl Scope {
|
||||
/// rsx!(div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
tracing::trace!(
|
||||
"providing context {} ({:?}) in {:?}",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::TypeId::of::<T>(),
|
||||
self.id
|
||||
);
|
||||
pub(crate) fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
let mut contexts = self.shared_contexts.borrow_mut();
|
||||
|
||||
// If the context exists, swap it out for the new value
|
||||
@@ -312,14 +279,8 @@ impl Scope {
|
||||
///
|
||||
/// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
|
||||
/// when a context already exists will swap the context out for the new one, which may not be what you want.
|
||||
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
||||
Runtime::with(|runtime| {
|
||||
runtime
|
||||
.get_state(ScopeId::ROOT)
|
||||
.unwrap()
|
||||
.provide_context(context)
|
||||
})
|
||||
.expect("Runtime to exist")
|
||||
pub(crate) fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
||||
Runtime::with(|runtime| runtime.get_state(ScopeId::ROOT).provide_context(context))
|
||||
}
|
||||
|
||||
/// Start a new future on the same thread as the rest of the VirtualDom.
|
||||
@@ -348,108 +309,29 @@ impl Scope {
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut)).expect("Runtime to exist");
|
||||
pub(crate) fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut));
|
||||
self.spawned_tasks.borrow_mut().insert(id);
|
||||
id
|
||||
}
|
||||
|
||||
/// Spawns the future and returns the [`Task`]
|
||||
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist");
|
||||
pub(crate) fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
let id = Runtime::with(|rt| rt.spawn(self.id, fut));
|
||||
self.spawned_tasks.borrow_mut().insert(id);
|
||||
id
|
||||
}
|
||||
|
||||
/// Queue an effect to run after the next render
|
||||
pub fn queue_effect(&self, f: impl FnOnce() + 'static) {
|
||||
Runtime::with(|rt| rt.queue_effect(self.id, f)).expect("Runtime to exist");
|
||||
pub(crate) fn queue_effect(&self, f: impl FnOnce() + 'static) {
|
||||
Runtime::with(|rt| rt.queue_effect(self.id, f));
|
||||
}
|
||||
|
||||
/// 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).
|
||||
/// `use_hook` will return a clone of the value on every render.
|
||||
///
|
||||
/// In order to clean up resources you would need to implement the [`Drop`] trait for an inner value stored in a RC or similar (Signals for instance),
|
||||
/// as these only drop their inner value once all references have been dropped, which only happens when the component is dropped.
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// `use_hook` is not reactive. It just returns the value on every render. If you need state that will track changes, use [`use_signal`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) instead.
|
||||
///
|
||||
/// ❌ Don't use `use_hook` with `Rc<RefCell<T>>` for state. It will not update the UI and other hooks when the state changes.
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
/// use std::rc::Rc;
|
||||
/// use std::cell::RefCell;
|
||||
///
|
||||
/// pub fn Comp() -> Element {
|
||||
/// let count = use_hook(|| Rc::new(RefCell::new(0)));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| *count.borrow_mut() += 1,
|
||||
/// "{count.borrow()}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ✅ Use `use_signal` instead.
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// pub fn Comp() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| count += 1,
|
||||
/// "{count}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// </div>
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// // prints a greeting on the initial render
|
||||
/// pub fn use_hello_world() {
|
||||
/// use_hook(|| println!("Hello, world!"));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Custom Hook Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// pub struct InnerCustomState(usize);
|
||||
///
|
||||
/// impl Drop for InnerCustomState {
|
||||
/// fn drop(&mut self){
|
||||
/// println!("Component has been dropped.");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Clone, Copy)]
|
||||
/// pub struct CustomState {
|
||||
/// inner: Signal<InnerCustomState>
|
||||
/// }
|
||||
///
|
||||
/// pub fn use_custom_state() -> CustomState {
|
||||
/// use_hook(|| CustomState {
|
||||
/// inner: Signal::new(InnerCustomState(0))
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
|
||||
/// Store a value in the hook list, returning the value.
|
||||
pub(crate) fn use_hook<State: Clone + 'static>(
|
||||
&self,
|
||||
initializer: impl FnOnce() -> State,
|
||||
) -> State {
|
||||
let cur_hook = self.hook_index.get();
|
||||
|
||||
// The hook list works by keeping track of the current hook index and pushing the index forward
|
||||
@@ -513,155 +395,23 @@ impl Scope {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn push_before_render(&self, f: impl FnMut() + 'static) {
|
||||
pub(crate) fn push_before_render(&self, f: impl FnMut() + 'static) {
|
||||
self.before_render.borrow_mut().push(Box::new(f));
|
||||
}
|
||||
|
||||
pub fn push_after_render(&self, f: impl FnMut() + 'static) {
|
||||
pub(crate) fn push_after_render(&self, f: impl FnMut() + 'static) {
|
||||
self.after_render.borrow_mut().push(Box::new(f));
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
pub(crate) fn generation(&self) -> usize {
|
||||
self.render_count.get()
|
||||
}
|
||||
|
||||
/// Get the height of this scope
|
||||
pub fn height(&self) -> u32 {
|
||||
pub(crate) fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
impl ScopeId {
|
||||
/// Get the current scope id
|
||||
pub fn current_scope_id(self) -> Result<ScopeId, RuntimeError> {
|
||||
Runtime::with(|rt| rt.current_scope_id().ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or(RuntimeError::new())
|
||||
}
|
||||
|
||||
/// Consume context from the current scope
|
||||
pub fn consume_context<T: 'static + Clone>(self) -> Option<T> {
|
||||
Runtime::with_scope(self, |cx| cx.consume_context::<T>()).expect("Runtime to exist")
|
||||
}
|
||||
|
||||
/// Consume context from the current scope
|
||||
pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
|
||||
Runtime::with(|rt| {
|
||||
rt.get_state(scope_id)
|
||||
.and_then(|cx| cx.consume_context::<T>())
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Check if the current scope has a context
|
||||
pub fn has_context<T: 'static + Clone>(self) -> Option<T> {
|
||||
Runtime::with_scope(self, |cx| cx.has_context::<T>())
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Provide context to the current scope
|
||||
pub fn provide_context<T: 'static + Clone>(self, value: T) -> T {
|
||||
Runtime::with_scope(self, |cx| cx.provide_context(value)).unwrap()
|
||||
}
|
||||
|
||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||
pub fn push_future(self, fut: impl Future<Output = ()> + 'static) -> Option<Task> {
|
||||
Runtime::with_scope(self, |cx| cx.spawn(fut)).ok()
|
||||
}
|
||||
|
||||
/// Spawns the future but does not return the [`Task`]
|
||||
pub fn spawn(self, fut: impl Future<Output = ()> + 'static) {
|
||||
Runtime::with_scope(self, |cx| cx.spawn(fut)).unwrap();
|
||||
}
|
||||
|
||||
/// 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) -> Option<usize> {
|
||||
Runtime::with_scope(self, |cx| Some(cx.generation())).unwrap()
|
||||
}
|
||||
|
||||
/// Get the parent of the current scope if it exists
|
||||
pub fn parent_scope(self) -> Option<ScopeId> {
|
||||
Runtime::with_scope(self, |cx| cx.parent_id())
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Check if the current scope is a descendant of the given scope
|
||||
pub fn is_descendant_of(self, other: ScopeId) -> bool {
|
||||
let mut current = self;
|
||||
while let Some(parent) = current.parent_scope() {
|
||||
if parent == other {
|
||||
return true;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Mark the current scope as dirty, causing it to re-render
|
||||
pub fn needs_update(self) {
|
||||
Runtime::with_scope(self, |cx| cx.needs_update()).unwrap();
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component. Unlike [`Self::needs_update`], this function will work outside of the dioxus runtime.
|
||||
///
|
||||
/// ## Notice: you should prefer using [`crate::schedule_update_any`]
|
||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||
Runtime::with_scope(*self, |cx| cx.schedule_update()).unwrap()
|
||||
}
|
||||
|
||||
/// Get the height of the current scope
|
||||
pub fn height(self) -> u32 {
|
||||
Runtime::with_scope(self, |cx| cx.height()).unwrap()
|
||||
}
|
||||
|
||||
/// Run a closure inside of scope's runtime
|
||||
#[track_caller]
|
||||
pub fn in_runtime<T>(self, f: impl FnOnce() -> T) -> T {
|
||||
Runtime::current()
|
||||
.unwrap_or_else(|e| panic!("{}", e))
|
||||
.on_scope(self, f)
|
||||
}
|
||||
|
||||
/// Throw a [`CapturedError`] into a scope. The error will bubble up to the nearest [`ErrorBoundary`](crate::ErrorBoundary) or the root of the app.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn Component() -> Element {
|
||||
/// let request = spawn(async move {
|
||||
/// match reqwest::get("https://api.example.com").await {
|
||||
/// Ok(_) => unimplemented!(),
|
||||
/// // You can explicitly throw an error into a scope with throw_error
|
||||
/// Err(err) => ScopeId::APP.throw_error(err)
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn throw_error(self, error: impl Into<CapturedError> + 'static) {
|
||||
let error = error.into();
|
||||
if let Some(cx) = self.consume_context::<crate::ErrorContext>() {
|
||||
cx.insert_error(error)
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the suspense context the current scope is in
|
||||
pub fn suspense_context(&self) -> Option<SuspenseContext> {
|
||||
Runtime::with_scope(*self, |cx| cx.suspense_boundary.suspense_context().cloned()).unwrap()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user