Boxed and mapped signals preparing for stores (#4413)
This PR contains just the breaking changes required for the upcoming store implementation. It adds two new primitives: Mutable mapped signals (without a different subscriber list). This lets you scope a writable value and pass it to a child component. It requires two closures to map the immutable and mutable references to the inner signal. With stores, these closures will be derived Boxed readable and writable values (ReadSignal, and WriteSignal). Any readable or writable types can be converted to a read signal or write signal with the boxed method or built in types are automatically converted into the boxed variants when passing into props * map signals mutably * boxed signals * remove mutable borrow type from boxed writable * improve map mut example * restore boxed_mut helper * make boxed readable/writable types copy * Move away from the ReadOnlySignal alias in examples * automatically convert Write * Fix some tests * ReadSignal and WriteSignal * move read only signal alias * make maps copy * store prototype * implement derive macro * Handle generics in the derive macro * Make Selector boxable * derive boxed conversion * Get rid of store type * Remove hardcoded type from derive macro * selector -> store * Refactor store implementation * more vec methods * todo mvc store example * Implement Storable for HashMap * fix write * restore sorting logic in todomvc * derive readable and writable * improve todomvc store example * Result store * Option store * collect dead subscribers * fix clippy and formatting * recognize some common foreign types automatically * fix foreign stores in props * slice and array selectors * clean up store example * remove stores * writable is child owned * fix typo * fix clippy * fix doc tests
This commit is contained in:
1 parent
6919f72abc
commit
d8d11db403
64 files changed
+1307
-726
No files matched your search
@@ -51,7 +51,7 @@ You can use the `#[props()]` attribute to modify the behavior of the props the c
|
||||
Props also act slightly differently when used with:
|
||||
|
||||
- [`Option<T>`](#optional-props) - The field is automatically optional with a default value of `None`.
|
||||
- [`ReadOnlySignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadOnlySignal<T>` when it is passed as a prop.
|
||||
- [`ReadSignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadSignal<T>` when it is passed as a prop.
|
||||
- [`String`](#formatted-props) - The props macro will accept formatted strings for any prop field with the type `String`.
|
||||
- [`children`](#children-props) - The props macro will accept child elements if you include the `children` prop.
|
||||
|
||||
@@ -257,14 +257,14 @@ fn Counter(count: i32) -> Element {
|
||||
|
||||
To fix this issue you can either:
|
||||
|
||||
1. Make the prop reactive by wrapping it in `ReadOnlySignal` (recommended):
|
||||
1. Make the prop reactive by wrapping it in `ReadSignal` (recommended):
|
||||
|
||||
`ReadOnlySignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadOnlySignal` when it is passed as a prop.
|
||||
`ReadSignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadSignal` when it is passed as a prop.
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn Counter(count: ReadOnlySignal<i32>) -> Element {
|
||||
fn Counter(count: ReadSignal<i32>) -> Element {
|
||||
// Since we made count reactive, the memo will automatically rerun when count changes.
|
||||
let doubled_count = use_memo(move || count() * 2);
|
||||
rsx! {
|
||||
|
||||
@@ -47,7 +47,7 @@ You can use the `#[props()]` attribute to modify the behavior of the props deriv
|
||||
Props also act slightly differently when used with:
|
||||
|
||||
- [`Option<T>`](#optional-props) - The field is automatically optional with a default value of `None`.
|
||||
- [`ReadOnlySignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadOnlySignal<T>` when it is passed as a prop.
|
||||
- [`ReadSignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadSignal<T>` when it is passed as a prop.
|
||||
- [`String`](#formatted-props) - The props macro will accept formatted strings for any prop field with the type `String`.
|
||||
- [`children`](#children-props) - The props macro will accept child elements if you include the `children` prop.
|
||||
|
||||
@@ -270,14 +270,14 @@ fn Counter(count: i32) -> Element {
|
||||
|
||||
To fix this issue you can either:
|
||||
|
||||
1. Make the prop reactive by wrapping it in `ReadOnlySignal` (recommended):
|
||||
1. Make the prop reactive by wrapping it in `ReadSignal` (recommended):
|
||||
|
||||
`ReadOnlySignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadOnlySignal` when it is passed as a prop.
|
||||
`ReadSignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadSignal` when it is passed as a prop.
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn Counter(count: ReadOnlySignal<i32>) -> Element {
|
||||
fn Counter(count: ReadSignal<i32>) -> Element {
|
||||
// Since we made count reactive, the memo will automatically rerun when count changes.
|
||||
let doubled_count = use_memo(move || count() * 2);
|
||||
rsx! {
|
||||
|
||||
@@ -172,7 +172,7 @@ mod util {
|
||||
}
|
||||
|
||||
mod field_info {
|
||||
use crate::props::type_from_inside_option;
|
||||
use crate::props::{looks_like_write_type, type_from_inside_option};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::spanned::Spanned;
|
||||
@@ -221,6 +221,11 @@ mod field_info {
|
||||
builder_attr.auto_into = false;
|
||||
}
|
||||
|
||||
// Write fields automatically use impl Into
|
||||
if looks_like_write_type(&field.ty) {
|
||||
builder_attr.auto_into = true;
|
||||
}
|
||||
|
||||
// extended field is automatically empty
|
||||
if !builder_attr.extends.is_empty() {
|
||||
builder_attr.default = Some(
|
||||
@@ -503,9 +508,9 @@ fn type_from_inside_option(ty: &Type) -> Option<&Type> {
|
||||
let seg = path.segments.last()?;
|
||||
|
||||
// If the segment is a supported optional type, provide the inner type.
|
||||
// Return the inner type if the pattern is `Option<T>` or `ReadOnlySignal<Option<T>>``
|
||||
if seg.ident == "ReadOnlySignal" {
|
||||
// Get the inner type. E.g. the `u16` in `ReadOnlySignal<u16>` or `Option` in `ReadOnlySignal<Option<bool>>`
|
||||
// Return the inner type if the pattern is `Option<T>` or `ReadSignal<Option<T>>``
|
||||
if seg.ident == "ReadOnlySignal" || seg.ident == "ReadSignal" {
|
||||
// Get the inner type. E.g. the `u16` in `ReadSignal<u16>` or `Option` in `ReadSignal<Option<bool>>`
|
||||
let inner_type = extract_inner_type_from_segment(seg)?;
|
||||
let Type::Path(inner_path) = inner_type else {
|
||||
// If it isn't a path, the inner type isn't option
|
||||
@@ -614,13 +619,13 @@ mod struct_info {
|
||||
generics
|
||||
}
|
||||
|
||||
/// Checks if the props have any fields that should be owned by the child. For example, when converting T to `ReadOnlySignal<T>`, the new signal should be owned by the child
|
||||
/// Checks if the props have any fields that should be owned by the child. For example, when converting T to `ReadSignal<T>`, the new signal should be owned by the child
|
||||
fn has_child_owned_fields(&self) -> bool {
|
||||
self.fields.iter().any(|f| child_owned_type(f.ty))
|
||||
}
|
||||
|
||||
fn memoize_impl(&self) -> Result<TokenStream, Error> {
|
||||
// First check if there are any ReadOnlySignal fields, if there are not, we can just use the partialEq impl
|
||||
// First check if there are any ReadSignal fields, if there are not, we can just use the partialEq impl
|
||||
let signal_fields: Vec<_> = self
|
||||
.included_fields()
|
||||
.filter(|f| looks_like_signal_type(f.ty))
|
||||
@@ -1736,7 +1741,7 @@ fn remove_option_wrapper(type_: Type) -> Type {
|
||||
|
||||
/// Check if a type should be owned by the child component after conversion
|
||||
fn child_owned_type(ty: &Type) -> bool {
|
||||
looks_like_signal_type(ty) || looks_like_callback_type(ty)
|
||||
looks_like_signal_type(ty) || looks_like_write_type(ty) || looks_like_callback_type(ty)
|
||||
}
|
||||
|
||||
fn looks_like_signal_type(ty: &Type) -> bool {
|
||||
@@ -1745,6 +1750,20 @@ fn looks_like_signal_type(ty: &Type) -> bool {
|
||||
path_without_generics == parse_quote!(dioxus_core::ReadOnlySignal)
|
||||
|| path_without_generics == parse_quote!(prelude::ReadOnlySignal)
|
||||
|| path_without_generics == parse_quote!(ReadOnlySignal)
|
||||
|| path_without_generics == parse_quote!(dioxus_core::prelude::ReadSignal)
|
||||
|| path_without_generics == parse_quote!(prelude::ReadSignal)
|
||||
|| path_without_generics == parse_quote!(ReadSignal)
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn looks_like_write_type(ty: &Type) -> bool {
|
||||
match extract_base_type_without_generics(ty) {
|
||||
Some(path_without_generics) => {
|
||||
path_without_generics == parse_quote!(dioxus_core::prelude::WriteSignal)
|
||||
|| path_without_generics == parse_quote!(prelude::WriteSignal)
|
||||
|| path_without_generics == parse_quote!(WriteSignal)
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
@@ -1778,6 +1797,17 @@ fn test_looks_like_type() {
|
||||
ReadOnlySignal<Option<i32>, UnsyncStorage>
|
||||
)));
|
||||
|
||||
assert!(!looks_like_signal_type(&parse_quote!(
|
||||
Option<ReadSignal<i32>>
|
||||
)));
|
||||
assert!(looks_like_signal_type(&parse_quote!(ReadSignal<i32>)));
|
||||
assert!(looks_like_signal_type(
|
||||
&parse_quote!(ReadSignal<i32, SyncStorage>)
|
||||
));
|
||||
assert!(looks_like_signal_type(&parse_quote!(
|
||||
ReadSignal<Option<i32>, UnsyncStorage>
|
||||
)));
|
||||
|
||||
assert!(looks_like_callback_type(&parse_quote!(
|
||||
Option<EventHandler>
|
||||
)));
|
||||
|
||||
@@ -52,13 +52,13 @@ mod test_default_into {
|
||||
|
||||
// Test default values for signals
|
||||
#[props(default)]
|
||||
read_only_w_default: ReadOnlySignal<bool>,
|
||||
read_only_w_default: ReadSignal<bool>,
|
||||
|
||||
#[props(default = true)]
|
||||
read_only_w_default_val: ReadOnlySignal<bool>,
|
||||
read_only_w_default_val: ReadSignal<bool>,
|
||||
|
||||
#[props(default = ReadOnlySignal::new(Signal::new(true)))]
|
||||
read_only_w_default_val_explicit: ReadOnlySignal<bool>,
|
||||
#[props(default = ReadSignal::new(Signal::new(true)))]
|
||||
read_only_w_default_val_explicit: ReadSignal<bool>,
|
||||
|
||||
// Test default values for callbacks/event handlers
|
||||
#[props(default)]
|
||||
@@ -108,7 +108,7 @@ mod test_optional_signals {
|
||||
fn UsesComponents() -> Element {
|
||||
rsx! {
|
||||
PropsStruct {
|
||||
regular_read_signal: ReadOnlySignal::new(Signal::new(1234)),
|
||||
regular_read_signal: ReadSignal::new(Signal::new(1234)),
|
||||
}
|
||||
PropsStruct {
|
||||
optional_read_signal: 1234,
|
||||
@@ -126,8 +126,8 @@ mod test_optional_signals {
|
||||
// Test props as struct param.
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
struct MyTestProps {
|
||||
pub optional_read_signal: ReadOnlySignal<Option<u16>>,
|
||||
pub regular_read_signal: ReadOnlySignal<u16>,
|
||||
pub optional_read_signal: ReadSignal<Option<u16>>,
|
||||
pub regular_read_signal: ReadSignal<u16>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
@@ -137,7 +137,7 @@ mod test_optional_signals {
|
||||
|
||||
// Test props as params.
|
||||
#[component]
|
||||
fn PropParams(opt_read_sig: ReadOnlySignal<Option<u16>>) -> Element {
|
||||
fn PropParams(opt_read_sig: ReadSignal<Option<u16>>) -> Element {
|
||||
rsx! { "hi!" }
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ fn TakesEventHandler(click: EventHandler<usize>, number: usize) -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TakesSignal(sig: ReadOnlySignal<usize>, number: usize) -> Element {
|
||||
fn TakesSignal(sig: ReadSignal<usize>, number: usize) -> Element {
|
||||
let first_render_sig = use_hook(move || sig);
|
||||
if generation() > 0 {
|
||||
// Make sure the signal is memoized in place and never gets dropped
|
||||
@@ -190,12 +190,12 @@ fn cloning_read_only_signal_components_work() {
|
||||
struct NonCloneable<T>(T);
|
||||
|
||||
#[component]
|
||||
fn TakesReadOnlySignalNum(sig: ReadOnlySignal<i32>) -> Element {
|
||||
fn TakesReadOnlySignalNum(sig: ReadSignal<i32>) -> Element {
|
||||
rsx! {}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TakesReadOnlySignalNonClone(sig: ReadOnlySignal<NonCloneable<i32>>) -> Element {
|
||||
fn TakesReadOnlySignalNonClone(sig: ReadSignal<NonCloneable<i32>>) -> Element {
|
||||
rsx! {}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user