Stores: Externally tracked state and reactive collections (#4483)
* 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 * writable is child owned * box selector * read and write to builtins * re-use the proc macro in the derive macro * Switch to a extension trait based approach * switch to extension traits in the derive macro * transposed method * derive enums * Expose Store::new * remove stores from the prelude * More consistent naming scheme for SelectorScope methods * document hashmap methods * document index extension * document option ext * document result ext * document slice extension * document the vec extension * make more methods borrow the store mutably * Move to impls to have higher priority over the builtin write extension traits * fix deadlock * finish stores documentation * Document the store macro * cleanup docs and organization * finish derive docs * fix typo * fix clippy * fix doc tests * fix derive macro with generics * fix clippy * test and fix empty structs * clean up todomvc store example * default to a boxed store writer to make it easier to type when providing to the context api * fix cargo doc * fix clippy in store example * relax generational box lifetime bounds * relax bounds * relax signal bounds * fix compilation * read store * relax bounds on default store impls * fix enum derive * Convert to readsignal and writesignal automatically * simplify type matching logic * add write store * unsized stores * shrink the path vec type * use more concrete types * remove opaque types * remove copy bounds * use lens as generic name everywhere * more consistent borrow semantics * use a concrete type for the signal metadata and mention references in the map docs * improve todomvc store docs * document store derive macro internals * refactor macro * improve selector node documentation * fix clippy * add btreemap * fix doc tests * more option methods * fix doc * fix clippy * add a bunch more result methods * add a bunch of bounds to the iterators * fix clippy * remove option from subscribers * add store macro to generate extension traits * fix store example * fix deadlock * fix docs * fix doc tests * fix clippy * add stores to public api * add store macro, and then update todomvc_store example --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
5591070e92
commit
58c41f8561
57 files changed
+4958
-436
No files matched your search
@@ -0,0 +1,179 @@
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, DeriveInput, ItemImpl};
|
||||
|
||||
use crate::extend::ExtendArgs;
|
||||
|
||||
mod derive;
|
||||
mod extend;
|
||||
|
||||
/// # `derive(Store)`
|
||||
///
|
||||
/// The `Store` macro is used to create an extension trait for stores that makes it possible to access the fields or variants
|
||||
/// of an item as stores.
|
||||
///
|
||||
/// ## Expansion
|
||||
///
|
||||
/// The macro expands to two different items:
|
||||
/// - An extension trait which is implemented for `Store<YourType, W>` with methods to access fields and variants for your type.
|
||||
/// - A transposed version of your type which contains the fields or variants as stores.
|
||||
///
|
||||
/// ### Structs
|
||||
///
|
||||
/// For structs, the store macro generates methods for each field that returns a store scoped to that field and a `transpose` method that returns a struct with all fields as stores:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_stores::*;
|
||||
///
|
||||
/// #[derive(Store)]
|
||||
/// struct TodoItem {
|
||||
/// checked: bool,
|
||||
/// contents: String,
|
||||
/// }
|
||||
///
|
||||
/// let store = use_store(|| TodoItem {
|
||||
/// checked: false,
|
||||
/// contents: "Learn about stores".to_string(),
|
||||
/// });
|
||||
///
|
||||
/// // The store macro creates an extension trait with methods for each field
|
||||
/// // that returns a store scoped to that field.
|
||||
/// let checked: Store<bool, _> = store.checked();
|
||||
/// let contents: Store<String, _> = store.contents();
|
||||
///
|
||||
/// // It also generates a `transpose` method returns a variant of your structure
|
||||
/// // with stores wrapping each of your data types. This can be very useful when destructuring
|
||||
/// // or matching your type
|
||||
/// let TodoItemStoreTransposed { checked, contents } = store.transpose();
|
||||
/// let checked: bool = checked();
|
||||
/// let contents: String = contents();
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ### Enums
|
||||
///
|
||||
/// For enums, the store macro generates methods for each variant that checks if the store is that variant. It also generates a `transpose` method that returns an enum with all fields as stores.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_stores::*;
|
||||
///
|
||||
/// #[derive(Store, PartialEq, Clone, Debug)]
|
||||
/// enum Enum {
|
||||
/// Foo(String),
|
||||
/// Bar { foo: i32, bar: String },
|
||||
/// }
|
||||
///
|
||||
/// let store = use_store(|| Enum::Foo("Hello".to_string()));
|
||||
/// // The store macro creates an extension trait with methods for each variant to check
|
||||
/// // if the store is that variant.
|
||||
/// let foo: bool = store.is_foo();
|
||||
/// let bar: bool = store.is_bar();
|
||||
///
|
||||
/// // If there is only one field in the variant, it also generates a method to try
|
||||
/// // to downcast the store to that variant.
|
||||
/// let foo: Option<Store<String, _>> = store.foo();
|
||||
/// if let Some(foo) = foo {
|
||||
/// println!("Foo: {foo}");
|
||||
/// }
|
||||
///
|
||||
/// // It also generates a `transpose` method that returns a variant of your enum where all
|
||||
/// // the fields are stores. You can use this to match your enum
|
||||
/// let transposed = store.transpose();
|
||||
/// use EnumStoreTransposed::*;
|
||||
/// match transposed {
|
||||
/// EnumStoreTransposed::Foo(foo) => println!("Foo: {foo}"),
|
||||
/// EnumStoreTransposed::Bar { foo, bar } => {
|
||||
/// let foo: i32 = foo();
|
||||
/// let bar: String = bar();
|
||||
/// println!("Bar: foo = {foo}, bar = {bar}");
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(Store)]
|
||||
pub fn derive_store(input: TokenStream) -> TokenStream {
|
||||
// Parse the input tokens into a syntax tree
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let expanded = match derive::derive_store(input) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(err) => {
|
||||
// If there was an error, return it as a compile error
|
||||
return err.to_compile_error().into();
|
||||
}
|
||||
};
|
||||
|
||||
// Hand the output tokens back to the compiler
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
/// # `#[store]`
|
||||
///
|
||||
/// The `store` attribute macro is used to create an extension trait for store implementations. The extension traits lets you add
|
||||
/// methods to the store even though the type is not defined in your crate.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// - `pub`: Makes the generated extension trait public. If not provided, the trait will be private.
|
||||
/// - `name = YourExtensionName`: The name of the extension trait. If not provided, it will be generated based on the type name.
|
||||
///
|
||||
/// ## Bounds
|
||||
///
|
||||
/// The generated extension trait will have bounds on the lens generic parameter to ensure it implements `Readable` or `Writable` as needed.
|
||||
///
|
||||
/// - If a method accepts `&self`, the lens will require `Readable` which lets you read the value of the store.
|
||||
/// - If a method accepts `&mut self`, the lens will require `Writable` which lets you change the value of the store.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_stores::*;
|
||||
///
|
||||
/// #[derive(Store)]
|
||||
/// struct TodoItem {
|
||||
/// checked: bool,
|
||||
/// contents: String,
|
||||
/// }
|
||||
///
|
||||
/// // You can use the store attribute macro to add methods to your stores
|
||||
/// #[store]
|
||||
/// impl<Lens> Store<TodoItem, Lens> {
|
||||
/// // Since this method takes &mut self, the lens will require Writable automatically. It cannot be used
|
||||
/// // with ReadStore<TodoItem>
|
||||
/// fn toggle_checked(&mut self) {
|
||||
/// self.checked().toggle();
|
||||
/// }
|
||||
///
|
||||
/// // Since this method takes &self, the lens will require Readable automatically
|
||||
/// fn checked_contents(&self) -> Option<String> {
|
||||
/// self.checked().cloned().then(|| self.contents().to_string())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut store = use_store(|| TodoItem {
|
||||
/// checked: false,
|
||||
/// contents: "Learn about stores".to_string(),
|
||||
/// });
|
||||
///
|
||||
/// // You can use the methods defined in the extension trait
|
||||
/// store.toggle_checked();
|
||||
/// let contents: Option<String> = store.checked_contents();
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn store(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
// Parse the input tokens into a syntax tree
|
||||
let args = parse_macro_input!(args as ExtendArgs);
|
||||
let input = parse_macro_input!(input as ItemImpl);
|
||||
|
||||
let expanded = match extend::extend_store(args, input) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(err) => {
|
||||
// If there was an error, return it as a compile error
|
||||
return err.to_compile_error().into();
|
||||
}
|
||||
};
|
||||
|
||||
// Hand the output tokens back to the compiler
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
Reference in new issue
Block a user