Small updates to book
This commit is contained in:
parent
6d7c36655c
commit
56c04e3772
14
README.md
14
README.md
|
@ -13,7 +13,7 @@ use leptos::*;
|
|||
#[component]
|
||||
pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
|
||||
// create a reactive signal with the initial value
|
||||
let (value, set_value) = create_signal(cx, inital_value);
|
||||
let (value, set_value) = create_signal(cx, initial_value);
|
||||
|
||||
// create event handlers for our buttons
|
||||
// note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
|
||||
|
@ -46,7 +46,7 @@ Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained re
|
|||
|
||||
## What does that mean?
|
||||
|
||||
- **Full-stack**: Leptos can be used to build apps that run in the browser (_client-side rendering_), on the server (_server-side rendering_), or by rendering HTML on the server and then adding interactivity in the browser (_hydration_). This includes support for *HTTP streaming* of both data (`Resource`s) and HTML (out-of-order streaming of `<Suspense/>` components.)
|
||||
- **Full-stack**: Leptos can be used to build apps that run in the browser (_client-side rendering_), on the server (_server-side rendering_), or by rendering HTML on the server and then adding interactivity in the browser (_hydration_). This includes support for _HTTP streaming_ of both data (`Resource`s) and HTML (out-of-order streaming of `<Suspense/>` components.)
|
||||
- **Isomorphic**: The same application code and business logic are compiled to run on the client and server, with seamless integration. You can write your server-only logic (database requests, authentication etc.) alongside the client-side components that will consume it, and let Leptos manage the data loading without the need to manually create APIs to consume.
|
||||
- **Web**: Leptos is built on the Web platform and Web standards. Whenever possible, we use Web essentials (like links and forms) and build on top of them rather than trying to replace them.
|
||||
- **Framework**: Leptos provides most of what you need to build a modern web app: a reactive system, templating library, and a router that works on both the server and client side.
|
||||
|
@ -64,20 +64,25 @@ Here are some resources for learning more about Leptos:
|
|||
## FAQs
|
||||
|
||||
### How is this different from Yew?
|
||||
|
||||
On the surface level, these two libraries may seem similar. Yew is, of course, the most mature Rust library for web UI development and has a huge ecosystem. Here are some conceptual differences:
|
||||
|
||||
- **VDOM vs. fine-grained:** Yew is built on the virtual DOM (VDOM) model: state changes cause components to re-render, generating a new virtual DOM tree. Yew diffs this against the previous VDOM, and applies those patches to the actual DOM. Component functions rerun whenever state changes. Leptos takes an entirely different approach. Components run once, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
|
||||
- **Performance:** This has huge performance implications: Leptos is simply *much* faster at both creating and updating the UI than Yew is.
|
||||
- **Performance:** This has huge performance implications: Leptos is simply _much_ faster at both creating and updating the UI than Yew is.
|
||||
- **Mental model:** Adopting fine-grained reactivity also tends to simplify the mental model. There are no surprising components re-renders because there are no re-renders. Your app can be divided into components based on what makes sense for your app, because they have no performance implications.
|
||||
|
||||
### How is this different from Sycamore?
|
||||
|
||||
Conceptually, these two frameworks are very similar: because both are built on fine-grained reactivity, most apps will end up looking very similar between the two, and Sycamore or Leptos apps will both look a lot like SolidJS apps, in the same way that Yew or Dioxus can look a lot like React.
|
||||
|
||||
There are some practical differences that make a significant difference:
|
||||
|
||||
- **Maturity:** Sycamore is obviously a much more mature and stable library with a larger ecosystem.
|
||||
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
|
||||
- **Template node cloning:** Leptos's `view` macro compiles to a static HTML string and a set of instructions of how to assign its reactive values. This means that at runtime, Leptos can clone a `<template>` node rather than calling `document.createElement()` to create DOM nodes. This is a *significantly* faster way of rendering components.
|
||||
- **Template node cloning:** Leptos's `view` macro compiles to a static HTML string and a set of instructions of how to assign its reactive values. This means that at runtime, Leptos can clone a `<template>` node rather than calling `document.createElement()` to create DOM nodes. This is a _significantly_ faster way of rendering components.
|
||||
- **Read-write segregation:** Leptos, like Sycamore, enforces read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);`
|
||||
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
|
||||
|
||||
```rust
|
||||
let (count, set_count) = create_signal(cx, 0); // a signal
|
||||
let double_count = move || count() * 2; // a derived signal
|
||||
|
@ -90,4 +95,5 @@ assert_eq!(memoized_count(), 0);
|
|||
// this function can accept any of those signals
|
||||
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
|
||||
```
|
||||
|
||||
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrapper for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Templating: Building User Interfaces
|
||||
|
||||
## Views
|
||||
|
||||
Leptos uses a simple `view` macro to create the user interface. If you’re familiar with JSX, then
|
||||
|
||||
## Components
|
||||
|
||||
**Components** are the basic building blocks of your application. Each component is simply a function that creates DOM nodes and sets up the reactive system that will update them. The component function runs exactly once per instance of the component.
|
||||
|
|
|
@ -25,26 +25,13 @@ leptos = { version = "0.0", features = ["csr"] }
|
|||
You’ll want to set up a basic `index.html` with the following content:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Leptos • Todos</title>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
{{#include ../project/ch01_getting_started/index.html}}
|
||||
```
|
||||
|
||||
Let’s start with a very simple `main.rs`
|
||||
|
||||
```rust
|
||||
use leptos::*;
|
||||
|
||||
fn main() {
|
||||
mount_to_body(|cx| view! { cx, <p>"Hello, world!"</p> })
|
||||
}
|
||||
{{#include ../project/ch01_getting_started/src/main.rs}}
|
||||
```
|
||||
|
||||
Now run `trunk serve --open`. Trunk should automatically compile your app and open it in your default browser.
|
||||
|
|
|
@ -57,10 +57,10 @@ pub struct Story {
|
|||
pub struct Comment {
|
||||
pub id: usize,
|
||||
pub level: usize,
|
||||
pub user: String,
|
||||
pub user: Option<String>,
|
||||
pub time: usize,
|
||||
pub time_ago: String,
|
||||
pub content: String,
|
||||
pub content: Option<String>,
|
||||
pub comments: Vec<Comment>,
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
|
|||
view! { cx,
|
||||
<li class="comment">
|
||||
<div class="by">
|
||||
<A href=format!("/users/{}", comment.user)>{comment.user.clone()}</A>
|
||||
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
|
||||
{format!(" {}", comment.time_ago)}
|
||||
</div>
|
||||
<div class="text" inner_html=comment.content></div>
|
||||
|
|
|
@ -1,37 +1,57 @@
|
|||
#![feature(stmt_expr_attributes)]
|
||||
|
||||
//! **Please note:** This framework is in active development. I'm keeping it in a cycle of 0.0.x releases at the moment to indicate that it’s not even ready for its 0.1.0. Active work is being done on documentation and features, and APIs should not necessarily be considered stable. At the same time, it is more than a toy project or proof of concept, and I am actively using it for my own application development.
|
||||
//! # About Leptos
|
||||
//!
|
||||
//! ## Fine-Grained Reactivity
|
||||
//! Leptos is a full-stack framework for building web applications in Rust. You can use it to build
|
||||
//! - single-page apps (SPAs) rendered entirely in the browser, using client-side routing and loading
|
||||
//! or mutating data via async requests to the server
|
||||
//! - multi-page apps (MPAs) rendered on the server, managing navigation, data, and mutations via
|
||||
//! web-standard `<a>` and `<form>` tags
|
||||
//! - progressively-enhanced multi-page apps ([PEMPAs](https://www.epicweb.dev/the-webs-next-transition)?)
|
||||
//! that are rendered on the server and then hydrated on the client, enhancing your `<a>` and `<form>`
|
||||
//! navigations and mutations seamlessly when WASM is available.
|
||||
//!
|
||||
//! Leptos is built on a fine-grained reactive system, which means that individual reactive values
|
||||
//! (“signals,” sometimes known as observables) trigger the code that reacts to them (“effects,”
|
||||
//! sometimes known as observers) to re-run. These two halves of the reactive system are inter-dependent.
|
||||
//! Without effects, signals can change within the reactive system but never be observed in a way
|
||||
//! that interacts with the outside world. Without signals, effects run once but never again, as
|
||||
//! there’s no observable value to subscribe to.
|
||||
//! And you can do all three of these **using the same Leptos code.**
|
||||
//!
|
||||
//! Here are the most commonly-used functions and types you'll need to build a reactive system:
|
||||
//! # Learning by Example
|
||||
//!
|
||||
//! These docs are a work in progress. If you want to see what Leptos is capable of, check out
|
||||
//! the [examples](https://github.com/gbj/leptos/tree/main/examples):
|
||||
//! - [`counter`](https://github.com/gbj/leptos/tree/main/examples/counter) is the classic
|
||||
//! counter example, showing the basics of client-side rendering and reactive DOM updates
|
||||
//! - [`counters`](https://github.com/gbj/leptos/tree/main/examples/counter) introduces parent-child
|
||||
//! communication via contexts, and the `<For/>` component for efficient keyed list updates.
|
||||
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) implements the classic to-do
|
||||
//! app in Leptos. This is a good example of a complete, simple app. In particular, you might want to
|
||||
//! see how we use [create_effect] to [serialize JSON to `localStorage`](https://github.com/gbj/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L164)
|
||||
//! and [reactively call DOM methods](https://github.com/gbj/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L291)
|
||||
//! on [references to elements](https://github.com/gbj/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L254).
|
||||
//! - [`fetch`](https://github.com/gbj/leptos/tree/main/examples/fetch) introduces
|
||||
//! [Resource](leptos_reactive::Resource)s, which allow you to integrate arbitrary `async` code like an
|
||||
//! HTTP request within your reactive code.
|
||||
//! - [`router`](https://github.com/gbj/leptos/tree/main/examples/router) shows how to use Leptos’s nested router
|
||||
//! to enable client-side navigation and route-specific, reactive data loading.
|
||||
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) shows the basics of building an
|
||||
//! isomorphic web app. Both the server and the client import the same app code from the `todomvc` example.
|
||||
//! The server renders the app directly to an HTML string, and the client hydrates that HTML to make it interactive.
|
||||
//! - [`hackernews`](https://github.com/gbj/leptos/tree/main/examples/hackernews) pulls everything together.
|
||||
//! It integrates calls to a real external REST API, routing, server-side rendering and hydration to create
|
||||
//! a fully-functional PEMPA that works as intended even before WASM has loaded and begun to run.
|
||||
//!
|
||||
//! ### Signals
|
||||
//! 1. *Signals:* [create_signal](crate::create_signal), which returns a ([ReadSignal](crate::ReadSignal),
|
||||
//! [WriteSignal](crate::WriteSignal)) tuple.
|
||||
//! 2. *Derived Signals:* any function that relies on another signal.
|
||||
//! 3. *Memos:* [create_memo](crate::create_memo), which returns a [Memo](crate::Memo).
|
||||
//! 4. *Resources:* [create_resource], which converts an `async` [Future](std::future::Future)
|
||||
//! into a synchronous [Resource](crate::Resource) signal.
|
||||
//! (The SPA examples can be run using `trunk serve`. For information about Trunk,
|
||||
//! [see here]((https://trunkrs.dev/)).)
|
||||
//!
|
||||
//! ### Effects
|
||||
//! 1. Use [create_effect](crate::create_effect) when you need to synchronize the reactive system
|
||||
//! with something outside it (for example: logging to the console, writing to a file or local storage)
|
||||
//! 2. The Leptos DOM renderer wraps any [Fn] in your template with [create_effect](crate::create_effect), so
|
||||
//! components you write do *not* need explicit effects to synchronize with the DOM.
|
||||
//! # Quick Links
|
||||
//!
|
||||
//! Here are links to the most important sections of the docs:
|
||||
//! - **Reactivity**: the [leptos_reactive] overview, and more details in
|
||||
//! - [create_signal], [ReadSignal], and [WriteSignal]
|
||||
//! - [create_memo] and [Memo]
|
||||
//! - [create_resource] and [Resource]
|
||||
//! - [create_effect]
|
||||
//! - **Templating/Views**: the [view] macro
|
||||
//! - **Routing**: the [leptos_router](https://docs.rs/leptos_router/latest/leptos_router/) crate
|
||||
//!
|
||||
//! ## Views and Components
|
||||
//!
|
||||
//! More docs are needed for the UI system, and an in-depth guide is in progress. Here’s a simple example
|
||||
//! of how the reactive system feeds into UI.
|
||||
//! # A Simple Counter
|
||||
//!
|
||||
//! ```rust
|
||||
//! use leptos::*;
|
||||
|
@ -39,7 +59,7 @@
|
|||
//! #[component]
|
||||
//! pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
|
||||
//! // create a reactive signal with the initial value
|
||||
//! let (value, set_value) = create_signal(cx, inital_value);
|
||||
//! let (value, set_value) = create_signal(cx, initial_value);
|
||||
//!
|
||||
//! // create event handlers for our buttons
|
||||
//! // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
|
||||
|
@ -61,7 +81,7 @@
|
|||
//!
|
||||
//! // Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setup
|
||||
//! pub fn main() {
|
||||
//! mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=3> })
|
||||
//! mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=3 /> })
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
|
|
|
@ -30,6 +30,131 @@ use view::render_view;
|
|||
mod component;
|
||||
mod props;
|
||||
|
||||
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
|
||||
/// same rules as HTML, with the following differences:
|
||||
/// 1. Text content should be provided as a Rust string, i.e., double-quoted:
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// view! { cx, <p>"Here’s some text"</p> }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 2. Self-closing tags need an explicit `/` as in XML/XHTML
|
||||
/// ```rust,compile_fail
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// // ❌ not like this
|
||||
/// view! { cx, <input type="text" name="name"> }
|
||||
/// # });
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// // ✅ add that slash
|
||||
/// view! { cx, <input type="text" name="name" /> }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 3. Components (functions annotated with `#[component]`) can be inserted as camel-cased tags
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use leptos_core as leptos; use leptos_core::*;
|
||||
/// # #[component] fn Counter(cx: Scope, initial_value: i32) -> Element { view! { cx, <p></p>} }
|
||||
/// # run_scope(|cx| {
|
||||
/// view! { cx, <div><Counter initial_value=3 /></div> }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 4. Dynamic content can be wrapped in curly braces (`{ }`) to insert text nodes, elements, or set attributes.
|
||||
/// If you insert signal here, Leptos will create an effect to update the DOM whenever the value changes.
|
||||
/// *(“Signal” here means `Fn() -> T` where `T` is the appropriate type for that node: a `String` in case
|
||||
/// of text nodes, a `bool` for `class:` attributes, etc.)*
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <div>
|
||||
/// "Count: " {count} // pass a signal
|
||||
/// <br/>
|
||||
/// "Double Count: " {move || count() % 2} // or derive a signal inline
|
||||
/// </div>
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 5. Event handlers can be added with `on:` attributes
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <button on:click=|ev: web_sys::Event| {
|
||||
/// log::debug!("click event: {ev:#?}");
|
||||
/// }>
|
||||
/// "Click me"
|
||||
/// </button>
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 6. DOM properties can be set with `prop:` attributes, which take any primitive type or `JsValue` (or a signal
|
||||
/// that returns a primitive or JsValue).
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
///
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <input
|
||||
/// type="text"
|
||||
/// name="user_name"
|
||||
/// value={name} // this only sets the default value!
|
||||
/// prop:value={name} // here's how you update values. Sorry, I didn’t invent the DOM.
|
||||
/// on:click=move |ev| set_name(event_target_value(ev)) // `event_target_value` is a useful little Leptos helper
|
||||
/// />
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 7. Classes can be toggled with `class:` attributes, which take a `bool` (or a signal that returns a `bool`).
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # run_scope(|cx| {
|
||||
/// view! { cx, <div class:hidden={move || count() % 2 }>"Now you see me, now you don’t."</div> }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// Here’s a simple example that shows off several of these features, put together
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use leptos_core::*; use leptos_core as leptos;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
|
||||
/// // create a reactive signal with the initial value
|
||||
/// let (value, set_value) = create_signal(cx, initial_value);
|
||||
///
|
||||
/// // create event handlers for our buttons
|
||||
/// // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
|
||||
/// let clear = move |_ev: web_sys::Event| set_value(0);
|
||||
/// let decrement = move |_ev: web_sys::Event| set_value.update(|value| *value -= 1);
|
||||
/// let increment = move |_ev: web_sys::Event| set_value.update(|value| *value += 1);
|
||||
///
|
||||
/// // this JSX is compiled to an HTML template string for performance
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <div>
|
||||
/// <button on:click=clear>"Clear"</button>
|
||||
/// <button on:click=decrement>"-1"</button>
|
||||
/// <span>"Value: " {move || value().to_string()} "!"</span>
|
||||
/// <button on:click=increment>"+1"</button>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
let mut tokens = tokens.into_iter();
|
||||
|
|
Loading…
Reference in New Issue