mirror of https://github.com/yewstack/yew
Final documentation touch ups & tag 0.19.0 (#2220)
* some documentation fixes * more fixes * why it didn't work before is beyond me * next redirect * change 0.18 docs folder structure to make it inline with next * move docs to /docs now that home is its own page, docs should be under `/docs/` * tag 0.19.0 version on website
This commit is contained in:
parent
4875430852
commit
996bf5b41a
|
@ -63,7 +63,7 @@ jobs:
|
|||
targets: website
|
||||
channelId: "${{ env.CHANNEL_ID }}"
|
||||
# link to the next version because that's what we care about
|
||||
commentURLPath: "/next"
|
||||
commentURLPath: "/docs/next"
|
||||
# PR information
|
||||
prNumber: "${{ env.PR_NUMBER }}"
|
||||
prBranchName: "${{ env.PR_BRANCH }}"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<h4>
|
||||
<a href="https://yew.rs/">Documentation (stable)</a>
|
||||
<span> | </span>
|
||||
<a href="https://yew.rs/next/">Documentation (latest)</a>
|
||||
<a href="https://yew.rs/docs/next/">Documentation (latest)</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/yewstack/yew/tree/v0.18/examples">Examples</a>
|
||||
<span> | </span>
|
||||
|
|
|
@ -6,7 +6,7 @@ This is an implementation of [TodoMVC](http://todomvc.com/) for Yew using functi
|
|||
|
||||
## Concepts
|
||||
|
||||
- Uses [`function_components`](https://yew.rs/next/concepts/function-components)
|
||||
- Uses [`function_components`](https://yew.rs/docs/next/concepts/function-components)
|
||||
- Uses [`gloo_storage`](https://gloo-rs.web.app/docs/storage) to persist the state
|
||||
|
||||
## Improvements
|
||||
|
|
|
@ -107,7 +107,7 @@ It is possible to configure release builds to be smaller using the available set
|
|||
`[profile.release]` section of your `Cargo.toml`.
|
||||
|
||||
|
||||
```text
|
||||
```toml, title=Cargo.toml
|
||||
[profile.release]
|
||||
# less code to include into binary
|
||||
panic = 'abort'
|
||||
|
|
|
@ -1,95 +1,9 @@
|
|||
---
|
||||
title: "Callbacks"
|
||||
description: "ComponentLink and Callbacks"
|
||||
---
|
||||
|
||||
## Component's `Scope<_>` API
|
||||
|
||||
The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves
|
||||
using messages. We obtain a reference to this by calling `link()` on the context object passed to the component.
|
||||
|
||||
### `send_message`
|
||||
|
||||
Sends a message to the component.
|
||||
Messages are handled by the `update` method which determines whether the component should re-render.
|
||||
|
||||
### `send_message_batch`
|
||||
|
||||
Sends multiple messages to the component at the same time.
|
||||
This is similar to `send_message` but if any of the messages cause the `update` method to return `true`,
|
||||
the component will re-render after all messages in the batch have been processed.
|
||||
|
||||
If the given vector is empty, this function doesn't do anything.
|
||||
|
||||
### `callback`
|
||||
|
||||
Create a callback that will send a message to the component when it is executed.
|
||||
Under the hood, it will call `send_message` with the message returned by the provided closure.
|
||||
|
||||
There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`.
|
||||
You should use this with care though, as the resulting callback will panic if executed more than once.
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html};
|
||||
|
||||
enum Msg {
|
||||
Text(String),
|
||||
}
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// Create a callback that accepts some text and sends it
|
||||
// to the component as the `Msg::Text` message variant.
|
||||
// highlight-next-line
|
||||
let cb = ctx.link().callback(|text: String| Msg::Text(text));
|
||||
|
||||
// The previous line is needlessly verbose to make it clearer.
|
||||
// It can be simplified it to this:
|
||||
// highlight-next-line
|
||||
let cb = ctx.link().callback(Msg::Text);
|
||||
|
||||
// Will send `Msg::Text("Hello World!")` to the component.
|
||||
// highlight-next-line
|
||||
cb.emit("Hello World!".to_owned());
|
||||
|
||||
html! {
|
||||
// html here
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `batch_callback`
|
||||
|
||||
Create a callback that will send a batch of messages to the component when it is executed.
|
||||
The difference to `callback` is that the closure passed to this method doesn't have to return a message.
|
||||
Instead, the closure can return either `Vec<Msg>` or `Option<Msg>` where `Msg` is the component's message type.
|
||||
|
||||
`Vec<Msg>` is treated as a batch of messages and uses `send_message_batch` under the hood.
|
||||
|
||||
`Option<Msg>` calls `send_message` if it is `Some`. If the value is `None`, nothing happens.
|
||||
This can be used in cases where, depending on the situation, an update isn't required.
|
||||
|
||||
This is achieved using the `SendAsMessage` trait which is only implemented for these types.
|
||||
You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`.
|
||||
|
||||
Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`.
|
||||
The same restrictions apply as for `callback_once`.
|
||||
|
||||
## Callbacks
|
||||
|
||||
_\(This might need its own short page.\)_
|
||||
|
||||
Callbacks are used to communicate with services, agents, and parent components within Yew.
|
||||
Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned.
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ impl Component for MyComponent {
|
|||
}
|
||||
```
|
||||
|
||||
For usage details, check out [the `html!` guide](./../html.md).
|
||||
For usage details, check out [the `html!` guide](../html/introduction).
|
||||
|
||||
### Rendered
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: "Scope"
|
||||
description: "Component's Scope"
|
||||
---
|
||||
|
||||
## Component's `Scope<_>` API
|
||||
|
||||
The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves
|
||||
using messages. We obtain a reference to this by calling `link()` on the context object passed to the component.
|
||||
|
||||
### `send_message`
|
||||
|
||||
Sends a message to the component.
|
||||
Messages are handled by the `update` method which determines whether the component should re-render.
|
||||
|
||||
### `send_message_batch`
|
||||
|
||||
Sends multiple messages to the component at the same time.
|
||||
This is similar to `send_message` but if any of the messages cause the `update` method to return `true`,
|
||||
the component will re-render after all messages in the batch have been processed.
|
||||
|
||||
If the given vector is empty, this function doesn't do anything.
|
||||
|
||||
### `callback`
|
||||
|
||||
Create a callback that will send a message to the component when it is executed.
|
||||
Under the hood, it will call `send_message` with the message returned by the provided closure.
|
||||
|
||||
There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`.
|
||||
You should use this with care though, as the resulting callback will panic if executed more than once.
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html};
|
||||
|
||||
enum Msg {
|
||||
Text(String),
|
||||
}
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// Create a callback that accepts some text and sends it
|
||||
// to the component as the `Msg::Text` message variant.
|
||||
// highlight-next-line
|
||||
let cb = ctx.link().callback(|text: String| Msg::Text(text));
|
||||
|
||||
// The previous line is needlessly verbose to make it clearer.
|
||||
// It can be simplified it to this:
|
||||
// highlight-next-line
|
||||
let cb = ctx.link().callback(Msg::Text);
|
||||
|
||||
// Will send `Msg::Text("Hello World!")` to the component.
|
||||
// highlight-next-line
|
||||
cb.emit("Hello World!".to_owned());
|
||||
|
||||
html! {
|
||||
// html here
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `batch_callback`
|
||||
|
||||
Create a callback that will send a batch of messages to the component when it is executed.
|
||||
The difference to `callback` is that the closure passed to this method doesn't have to return a message.
|
||||
Instead, the closure can return either `Vec<Msg>` or `Option<Msg>` where `Msg` is the component's message type.
|
||||
|
||||
`Vec<Msg>` is treated as a batch of messages and uses `send_message_batch` under the hood.
|
||||
|
||||
`Option<Msg>` calls `send_message` if it is `Some`. If the value is `None`, nothing happens.
|
||||
This can be used in cases where, depending on the situation, an update isn't required.
|
||||
|
||||
This is achieved using the `SendAsMessage` trait which is only implemented for these types.
|
||||
You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`.
|
||||
|
||||
Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`.
|
||||
The same restrictions apply as for `callback_once`.
|
|
@ -2,9 +2,15 @@
|
|||
title: "Fragments"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
The `html!` macro always requires a single root node. In order to get around this restriction, you
|
||||
can use an "empty tag" (these are also called "fragments").
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Valid" label="Valid">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
|
@ -14,15 +20,24 @@ html! {
|
|||
<p></p>
|
||||
</>
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
```rust ,compile_fail
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="Invalid" label="Invalid">
|
||||
|
||||
```rust, compile_fail
|
||||
use yew::html;
|
||||
|
||||
/* error: only one root html element allowed */
|
||||
// error: only one root html element allowed
|
||||
|
||||
html! {
|
||||
<div></div>
|
||||
<p></p>
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
---
|
||||
title: "Project Setup"
|
||||
sidebar_label: Introduction
|
||||
title: "`wasm-bindgen`"
|
||||
sidebar_label: wasm-bindgen
|
||||
slug: /concepts/wasm-bindgen
|
||||
---
|
||||
|
||||
[`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) is a library and tool to facilitate
|
||||
high-level interactions between Wasm modules and JavaScript; it is built with Rust by
|
||||
[The Rust and WebAssembly Working Group](https://rustwasm.github.io/).
|
||||
|
||||
Yew builds off wasm-bindgen and specifically uses the following of its crates:
|
||||
Yew builds off `wasm-bindgen` and specifically uses the following of its crates:
|
||||
|
||||
- [`js-sys`](https://crates.io/crates/js-sys)
|
||||
- [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen)
|
||||
|
@ -28,14 +29,14 @@ over using `wasm-bindgen`.
|
|||
## [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen)
|
||||
|
||||
This crate provides many of the building blocks for the rest of the crates above. In this section we
|
||||
are only going to cover two main areas of the `wasm-bindgen` crate and that is the macro and some of
|
||||
the types / traits you will see pop up again and again.
|
||||
are only going to cover two main areas of the `wasm-bindgen` crate and that is the macro and some
|
||||
types / traits you will see pop up again and again.
|
||||
|
||||
### `#[wasm_bindgen]` macro
|
||||
|
||||
The `#[wasm_bindgen]` macro, in a high level view, is your translator between Rust and JavaScript, it
|
||||
allows you to describe imported JavaScript types in terms of Rust and vice versa. Using this macro
|
||||
is more advanced and you shouldn't need to reach for it unless you are trying to interop with an
|
||||
is more advanced, and you shouldn't need to reach for it unless you are trying to interop with an
|
||||
external JavaScript library. The `js-sys` and `web-sys` crates are essentially imported types using
|
||||
this macro for JavaScript types and the browser API respectively.
|
||||
|
||||
|
@ -80,7 +81,7 @@ _This example was adapted from [1.2 Using console.log of The `wasm-bindgen` Guid
|
|||
|
||||
Inheritance between JavaScript classes is a big part of the language and is a major part of how the
|
||||
Document Object Model (DOM). When types are imported using `wasm-bindgen` you can
|
||||
also add attributes that describe it's inheritance.
|
||||
also add attributes that describe its inheritance.
|
||||
|
||||
In Rust this inheritance is simulated using the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html)
|
||||
and [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) traits. An example of this
|
||||
|
@ -122,7 +123,7 @@ _[`JsValue` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bind
|
|||
|
||||
Rust has a strong type system and JavaScript...doesn't 😞 So in order for Rust to maintain these
|
||||
strong types but still be convenient the web assembly group came up with a pretty neat trait `JsCast`.
|
||||
Its job is to help you move from one JavaScript "type" to another, which sounds vague but it means
|
||||
Its job is to help you move from one JavaScript "type" to another, which sounds vague, but it means
|
||||
that if you have one type which you know is really another then you can use the functions of `JsCast`
|
||||
to jump from one type to the other. It's a nice trait to get to know when working with `web-sys`,
|
||||
`wasm_bindgen`, `js-sys` - you'll notice lots of types will implement `JsCast` from those crates.
|
||||
|
@ -132,10 +133,10 @@ unsure what type a certain object is you can try to cast it which returns possib
|
|||
[`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) and
|
||||
[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html).
|
||||
|
||||
A common example of this in [`web-sys`](wasm-bindgen/web-sys) is when you are trying to get the
|
||||
A common example of this in [`web-sys`](web-sys) is when you are trying to get the
|
||||
target of an event, you might know what the target element is but the
|
||||
[`web_sys::Event`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html) API will always return an [`Option<web_sys::EventTarget>`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html#method.target)
|
||||
so you will need to cast it to the element type so you can call it's methods.
|
||||
so you will need to cast it to the element type. so you can call its methods.
|
||||
|
||||
```rust
|
||||
// need to import the trait.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
The [`web-sys` crate](https://crates.io/crates/web-sys) provides bindings for Web APIs. This is
|
||||
procedurally generated from browser WebIDL which is why some of the names are so long and why
|
||||
|
@ -86,6 +88,9 @@ The two code blocks below do essentially the same thing, the first is using `Nod
|
|||
the second is using [`JsCast::dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into)
|
||||
on the `web_sys::Node` returned from `NodeRef::get`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Using NodeRef::cast" label="Using NodeRef::cast">
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::NodeRef;
|
||||
|
@ -97,6 +102,9 @@ fn with_node_ref_cast(node_ref: NodeRef) {
|
|||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Using NodeRef::get" label="Using NodeRef::get">
|
||||
|
||||
```rust
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlInputElement;
|
||||
|
@ -111,6 +119,10 @@ fn with_jscast(node_ref: NodeRef) {
|
|||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## JavaScript example to Rust
|
||||
|
||||
This section is to help show that any examples that use JavaScript to interact with the Web APIs
|
||||
|
|
|
@ -40,7 +40,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
# you can check the latest version here: https://crates.io/crates/yew
|
||||
yew = "0.17"
|
||||
yew = "0.19"
|
||||
```
|
||||
|
||||
### Update main.rs
|
||||
|
@ -53,7 +53,7 @@ The line `yew::start_app::<Model>()` inside `main()` starts your application and
|
|||
If you would like to start your application with any dynamic properties, you can instead use `yew::start_app_with_props::<Model>(..)`.
|
||||
:::
|
||||
|
||||
```rust ,no_run
|
||||
```rust ,no_run, title=main.rs
|
||||
use yew::prelude::*;
|
||||
|
||||
enum Msg {
|
||||
|
@ -106,7 +106,7 @@ fn main() {
|
|||
|
||||
Finally, add an `index.html` file in the root directory of your app.
|
||||
|
||||
```html
|
||||
```html, title=index.html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
|
@ -10,7 +10,7 @@ Your local development environment will need a couple of tools to compile, build
|
|||
|
||||
## Installing Rust
|
||||
|
||||
To install Rust follow the [official instructions](https://www.rust-lang.org/tools/install).
|
||||
To install Rust, follow the [official instructions](https://www.rust-lang.org/tools/install).
|
||||
|
||||
:::important
|
||||
The minimum supported Rust version (MSRV) for Yew is `1.49.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages.
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
---
|
||||
title: "Introduction"
|
||||
slug: /
|
||||
---
|
||||
|
||||
## What is Yew?
|
||||
|
||||
**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded
|
||||
front-end web apps using [WebAssembly](https://webassembly.org/).
|
||||
|
||||
* It features a **component-based** framework which makes it easy to create interactive UIs.
|
||||
Developers who have experience with frameworks like [React](https://reactjs.org/) and
|
||||
[Elm](https://elm-lang.org/) should feel quite at home when using Yew.
|
||||
* It achieves **great performance** by minimizing DOM API calls and by helping developers to easily
|
||||
offload processing to background threads using web workers.
|
||||
* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and
|
||||
integrate with existing JavaScript applications.
|
||||
|
||||
### Join Us 😊
|
||||
|
||||
* You can report bugs and discuss features on the [GitHub issues page](https://github.com/yewstack/yew/issues)
|
||||
* We love pull requests. Check out the [good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
if you'd like to help out!
|
||||
* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask
|
||||
questions
|
||||
|
||||
### Ready to dive in?
|
||||
|
||||
Click the link below to learn how to build your first Yew app and learn from community-built example
|
||||
projects
|
||||
|
||||
[Getting started](./../getting-started/project-setup.md)
|
||||
|
||||
### Still not convinced?
|
||||
|
||||
This project is built on cutting edge technology and is great for developers who like to develop the
|
||||
foundational projects of tomorrow. We think that the speed and reliability of the technologies on
|
||||
which Yew is built are set to become the standard for fast and resilient web applications of the
|
||||
future.
|
||||
|
||||
#### Wait, why WebAssembly?
|
||||
|
||||
WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile to. It runs at native
|
||||
speeds in the browser and is interoperable with JavaScript and supported in all major modern
|
||||
browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of
|
||||
[use cases](https://webassembly.org/docs/use-cases/).
|
||||
|
||||
It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of
|
||||
web apps. As of the present, using DOM APIs from WebAssembly is still slower than calling them
|
||||
directly from JavaScript. This is a temporary issue which the
|
||||
[WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to resolve. If you would like to learn more, check out this
|
||||
[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) (from Mozilla)
|
||||
which describes the proposal.
|
||||
|
||||
#### Ok, but why Rust?
|
||||
|
||||
Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough
|
||||
learning curve but is well worth the effort. Rust has been voted the most loved programming
|
||||
language in Stack Overflow's Developer Survey five years in a row:
|
||||
[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted),
|
||||
[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted),
|
||||
[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages),
|
||||
[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages)
|
||||
and [2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted).
|
||||
|
||||
Rust also helps developers write safer code with its rich type system and ownership model. Say
|
||||
goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your
|
||||
bugs will be caught by the compiler before your app even runs. And don't worry, when your app does
|
||||
run into an error, you can still get full stack-traces for your Rust code in the browser console.
|
||||
|
||||
#### Alternatives?
|
||||
|
||||
We love to share ideas with other projects and believe we can all help each other reach the full
|
||||
potential of this exciting new technology. If you're not into Yew, you might like the following
|
||||
projects:
|
||||
|
||||
* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web apps
|
||||
with Rust + WebAssembly"_
|
||||
* [Seed](https://github.com/seed-rs/seed) - _"A Rust framework for creating web apps"_
|
|
@ -451,10 +451,10 @@ element returned by the `Iterator` with the `{ for ... }` syntax.
|
|||
|
||||
Remember the `use_state` used earlier? That is a special function, called a "hook". Hooks are used to "hook" into
|
||||
lifecycle of a function component and perform actions. You can learn more about this hook, and others
|
||||
[here](/next/concepts/function-components/pre-defined-hooks#use_state)
|
||||
[here](concepts/function-components/pre-defined-hooks#use_state)
|
||||
|
||||
:::note
|
||||
Struct components act differently. See [the documentation](/concepts/components) to learn about those.
|
||||
Struct components act differently. See [the documentation](concepts/components) to learn about those.
|
||||
:::
|
||||
|
||||
## Fetching data (using external REST API)
|
||||
|
@ -576,9 +576,9 @@ to learn how to add style sheets.
|
|||
### More libraries
|
||||
|
||||
Our app made use of only a few external dependencies. There are lots of crates out there that can be used.
|
||||
See [external libraries](/next/more/external-libs) for more details.
|
||||
See [external libraries](more/external-libs) for more details.
|
||||
|
||||
### Learning more about Yew
|
||||
|
||||
Read our [official documentation](/next). It explains a lot of concepts in much more details.
|
||||
Read our [official documentation](/docs). It explains a lot of concepts in much more details.
|
||||
To learn more about our the Yew API, see our [API docs](https://docs.rs/yew).
|
||||
|
|
|
@ -26,8 +26,10 @@ module.exports = {
|
|||
position: 'left',
|
||||
},
|
||||
{
|
||||
to: '/',
|
||||
label: 'Docs'
|
||||
type: 'doc',
|
||||
position: 'left',
|
||||
docId: 'getting-started/project-setup/introduction',
|
||||
label: 'Docs',
|
||||
},
|
||||
{
|
||||
href: 'https://docs.rs/yew',
|
||||
|
@ -108,7 +110,7 @@ module.exports = {
|
|||
docs: {
|
||||
sidebarPath: require.resolve('./sidebars.js'),
|
||||
editUrl: 'https://github.com/yewstack/yew/blob/master/website/',
|
||||
routeBasePath: '/',
|
||||
routeBasePath: '/docs',
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve('./src/css/custom.css'),
|
||||
|
@ -116,4 +118,19 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'@docusaurus/plugin-client-redirects',
|
||||
{
|
||||
redirects: [
|
||||
// this handles the redirect from `/next` -> to the (current) first item in the docs sidebar
|
||||
// note: if the first item is changed, it should be reflected here
|
||||
{
|
||||
to: '/docs/next/getting-started/project-setup/introduction', // string
|
||||
from: ['/docs/next'], // string | string[]
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-beta.9",
|
||||
"@docusaurus/plugin-client-redirects": "^2.0.0-beta.9",
|
||||
"@docusaurus/plugin-google-analytics": "*",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.9",
|
||||
"@docusaurus/theme-search-algolia": "*",
|
||||
|
@ -2098,6 +2099,31 @@
|
|||
"integrity": "sha512-FPy/Wa4dH0QvtR92/JJi7t2SB1tnySuSGVNf4e0llArZ4E7+vzTAmd4qVfzBuS+LMj/M6woKHLn+zKKUkfaZOw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-client-redirects": {
|
||||
"version": "2.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.9.tgz",
|
||||
"integrity": "sha512-rksXkA9keq9jsgxp0ZLX+MY68ViDhCpdLcxNCNmZl2c9XA2v8AQN4HU2e6Dq+OEefk/ltQYLIbfTa2Hj/ZEwzQ==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.0.0-beta.9",
|
||||
"@docusaurus/types": "2.0.0-beta.9",
|
||||
"@docusaurus/utils": "2.0.0-beta.9",
|
||||
"@docusaurus/utils-common": "2.0.0-beta.9",
|
||||
"@docusaurus/utils-validation": "2.0.0-beta.9",
|
||||
"chalk": "^4.1.2",
|
||||
"eta": "^1.12.3",
|
||||
"fs-extra": "^10.0.0",
|
||||
"globby": "^11.0.2",
|
||||
"lodash": "^4.17.20",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-blog": {
|
||||
"version": "2.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.9.tgz",
|
||||
|
@ -14287,6 +14313,24 @@
|
|||
"integrity": "sha512-FPy/Wa4dH0QvtR92/JJi7t2SB1tnySuSGVNf4e0llArZ4E7+vzTAmd4qVfzBuS+LMj/M6woKHLn+zKKUkfaZOw==",
|
||||
"dev": true
|
||||
},
|
||||
"@docusaurus/plugin-client-redirects": {
|
||||
"version": "2.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.9.tgz",
|
||||
"integrity": "sha512-rksXkA9keq9jsgxp0ZLX+MY68ViDhCpdLcxNCNmZl2c9XA2v8AQN4HU2e6Dq+OEefk/ltQYLIbfTa2Hj/ZEwzQ==",
|
||||
"requires": {
|
||||
"@docusaurus/core": "2.0.0-beta.9",
|
||||
"@docusaurus/types": "2.0.0-beta.9",
|
||||
"@docusaurus/utils": "2.0.0-beta.9",
|
||||
"@docusaurus/utils-common": "2.0.0-beta.9",
|
||||
"@docusaurus/utils-validation": "2.0.0-beta.9",
|
||||
"chalk": "^4.1.2",
|
||||
"eta": "^1.12.3",
|
||||
"fs-extra": "^10.0.0",
|
||||
"globby": "^11.0.2",
|
||||
"lodash": "^4.17.20",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@docusaurus/plugin-content-blog": {
|
||||
"version": "2.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.9.tgz",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-beta.9",
|
||||
"@docusaurus/plugin-client-redirects": "^2.0.0-beta.9",
|
||||
"@docusaurus/plugin-google-analytics": "*",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.9",
|
||||
"@docusaurus/theme-search-algolia": "*",
|
||||
|
|
|
@ -13,122 +13,122 @@ module.exports = {
|
|||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
// conceptsSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
sidebar: [
|
||||
"intro",
|
||||
{
|
||||
type: "category",
|
||||
label: "Getting Started",
|
||||
items: [
|
||||
"getting-started/build-a-sample-app",
|
||||
"getting-started/examples",
|
||||
"getting-started/starter-templates",
|
||||
// But you can create a sidebar manually
|
||||
sidebar: [
|
||||
{
|
||||
type: "category",
|
||||
label: "Project Setup",
|
||||
items: [
|
||||
"getting-started/project-setup/introduction",
|
||||
"getting-started/project-setup/using-trunk",
|
||||
"getting-started/project-setup/using-wasm-pack",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Concepts",
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
label: "wasm-bindgen",
|
||||
items: [
|
||||
"concepts/wasm-bindgen/introduction",
|
||||
"concepts/wasm-bindgen/web-sys"
|
||||
],
|
||||
type: 'category',
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Project Setup',
|
||||
items: [
|
||||
'getting-started/project-setup/introduction',
|
||||
'getting-started/project-setup/using-trunk',
|
||||
'getting-started/project-setup/using-wasm-pack',
|
||||
]
|
||||
},
|
||||
"getting-started/build-a-sample-app",
|
||||
"getting-started/examples",
|
||||
"getting-started/starter-templates",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Components",
|
||||
items: [
|
||||
"concepts/components/introduction",
|
||||
"concepts/components/callbacks",
|
||||
"concepts/components/properties",
|
||||
"concepts/components/children",
|
||||
"concepts/components/refs",
|
||||
],
|
||||
type: "category",
|
||||
label: "Concepts",
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
label: "wasm-bindgen",
|
||||
items: [
|
||||
"concepts/wasm-bindgen/introduction",
|
||||
"concepts/wasm-bindgen/web-sys",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Components",
|
||||
items: [
|
||||
"concepts/components/introduction",
|
||||
"concepts/components/callbacks",
|
||||
"concepts/components/scope",
|
||||
"concepts/components/properties",
|
||||
"concepts/components/children",
|
||||
"concepts/components/refs"
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "HTML",
|
||||
items: [
|
||||
"concepts/html/introduction",
|
||||
"concepts/html/components",
|
||||
"concepts/html/elements",
|
||||
"concepts/html/events",
|
||||
"concepts/html/classes",
|
||||
"concepts/html/fragments",
|
||||
"concepts/html/lists",
|
||||
"concepts/html/literals-and-expressions"
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Function Components",
|
||||
items: [
|
||||
"concepts/function-components/introduction",
|
||||
"concepts/function-components/attribute",
|
||||
"concepts/function-components/pre-defined-hooks",
|
||||
"concepts/function-components/custom-hooks",
|
||||
]
|
||||
},
|
||||
"concepts/agents",
|
||||
"concepts/contexts",
|
||||
"concepts/router",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "HTML",
|
||||
items: [
|
||||
"concepts/html/introduction",
|
||||
"concepts/html/components",
|
||||
"concepts/html/elements",
|
||||
"concepts/html/events",
|
||||
"concepts/html/classes",
|
||||
"concepts/html/fragments",
|
||||
"concepts/html/lists",
|
||||
"concepts/html/literals-and-expressions",
|
||||
],
|
||||
type: 'category',
|
||||
label: 'Advanced topics',
|
||||
items: [
|
||||
"advanced-topics/how-it-works",
|
||||
"advanced-topics/optimizations",
|
||||
"advanced-topics/portals",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Function Components",
|
||||
items: [
|
||||
"concepts/function-components/introduction",
|
||||
"concepts/function-components/attribute",
|
||||
"concepts/function-components/pre-defined-hooks",
|
||||
"concepts/function-components/custom-hooks",
|
||||
],
|
||||
},
|
||||
"concepts/agents",
|
||||
"concepts/contexts",
|
||||
"concepts/router",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Advanced topics",
|
||||
items: [
|
||||
"advanced-topics/how-it-works",
|
||||
"advanced-topics/optimizations",
|
||||
"advanced-topics/portals",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "More",
|
||||
items: [
|
||||
"more/debugging",
|
||||
"more/development-tips",
|
||||
"more/external-libs",
|
||||
"more/css",
|
||||
"more/testing",
|
||||
"more/roadmap",
|
||||
"more/wasm-build-tools",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Migration guides",
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
label: "yew",
|
||||
items: ["migration-guides/yew/from-0_18_0-to-0_19_0"],
|
||||
type: 'category',
|
||||
label: 'More',
|
||||
items: [
|
||||
"more/debugging",
|
||||
"more/development-tips",
|
||||
"more/external-libs",
|
||||
"more/css",
|
||||
"more/testing",
|
||||
"more/roadmap",
|
||||
"more/wasm-build-tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "yew-agent",
|
||||
items: ["migration-guides/yew-agent/from-0_0_0-to-0_1_0"],
|
||||
type: "category",
|
||||
label: "Migration guides",
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
label: "yew",
|
||||
items: ["migration-guides/yew/from-0_18_0-to-0_19_0"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "yew-agent",
|
||||
items: ["migration-guides/yew-agent/from-0_0_0-to-0_1_0"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "yew-router",
|
||||
items: ["migration-guides/yew-router/from-0_15_0-to-0_16_0"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "yew-router",
|
||||
items: ["migration-guides/yew-router/from-0_15_0-to-0_16_0"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"tutorial",
|
||||
],
|
||||
"tutorial"
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,82 +1,77 @@
|
|||
---
|
||||
title: "Introduction"
|
||||
slug: /
|
||||
---
|
||||
# What is Yew?
|
||||
|
||||
## What is Yew?
|
||||
|
||||
**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded
|
||||
**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded
|
||||
front-end web apps using [WebAssembly](https://webassembly.org/).
|
||||
|
||||
* It features a **component-based** framework which makes it easy to create interactive UIs.
|
||||
Developers who have experience with frameworks like [React](https://reactjs.org/) and
|
||||
[Elm](https://elm-lang.org/) should feel quite at home when using Yew.
|
||||
* It achieves **great performance** by minimizing DOM API calls and by helping developers to easily
|
||||
offload processing to background threads using web workers.
|
||||
* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and
|
||||
integrate with existing JavaScript applications.
|
||||
* It features a **component-based** framework which makes it easy to create interactive UIs.
|
||||
Developers who have experience with frameworks like [React](https://reactjs.org/) and
|
||||
[Elm](https://elm-lang.org/) should feel quite at home when using Yew.
|
||||
* It achieves **great performance** by minimizing DOM API calls and by helping developers to easily
|
||||
offload processing to background threads using web workers.
|
||||
* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and
|
||||
integrate with existing JavaScript applications.
|
||||
|
||||
### Join Us 😊
|
||||
## Join Us 😊
|
||||
|
||||
* You can report bugs and discuss features on the [GitHub issues page](https://github.com/yewstack/yew/issues)
|
||||
* We love pull requests. Check out the [good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
if you'd like to help out!
|
||||
* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask
|
||||
questions
|
||||
* We love pull requests. Check out the [good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
if you'd like to help out!
|
||||
* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask
|
||||
questions
|
||||
|
||||
### Ready to dive in?
|
||||
## Ready to dive in?
|
||||
|
||||
Click the link below to learn how to build your first Yew app and learn from community-built example
|
||||
Click the link below to learn how to build your first Yew app and learn from community-built example
|
||||
projects
|
||||
|
||||
[Getting started](getting-started/project-setup.md)
|
||||
[Getting started](getting-started/project-setup/introduction)
|
||||
|
||||
### Still not convinced?
|
||||
## Still not convinced?
|
||||
|
||||
This project is built on cutting edge technology and is great for developers who like to develop the
|
||||
This project is built on cutting edge technology and is great for developers who like to develop the
|
||||
foundational projects of tomorrow. We think that the speed and reliability of the technologies on
|
||||
which Yew is built are set to become the standard for fast and resilient web applications of the
|
||||
future.
|
||||
future.
|
||||
|
||||
#### Wait, why WebAssembly?
|
||||
### Wait, why WebAssembly?
|
||||
|
||||
WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile to. It runs at native
|
||||
speeds in the browser and is interoperable with JavaScript and supported in all major modern
|
||||
browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of
|
||||
WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile to. It runs at native
|
||||
speeds in the browser and is interoperable with JavaScript and supported in all major modern
|
||||
browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of
|
||||
[use cases](https://webassembly.org/docs/use-cases/).
|
||||
|
||||
It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of
|
||||
web apps. As of the present, using DOM APIs from WebAssembly is still slower than calling them
|
||||
directly from JavaScript. This is a temporary issue which the
|
||||
[WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to resolve. If you would like to learn more, check out this
|
||||
[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) (from Mozilla)
|
||||
It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of
|
||||
web apps. As of the present, using DOM APIs from WebAssembly is still slower than calling them
|
||||
directly from JavaScript. This is a temporary issue which the
|
||||
[WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to resolve. If you would like to learn more, check out this
|
||||
[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) (from Mozilla)
|
||||
which describes the proposal.
|
||||
|
||||
#### Ok, but why Rust?
|
||||
### Ok, but why Rust?
|
||||
|
||||
Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough
|
||||
learning curve but is well worth the effort. Rust has been voted the most loved programming
|
||||
language in Stack Overflow's Developer Survey six years in a row:
|
||||
[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted),
|
||||
[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted),
|
||||
[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages),
|
||||
[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages),
|
||||
[2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted) and
|
||||
Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough
|
||||
learning curve but is well worth the effort. Rust has been voted the most loved programming
|
||||
language in Stack Overflow's Developer Survey six years in a row:
|
||||
[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted),
|
||||
[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted),
|
||||
[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages),
|
||||
[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages),
|
||||
[2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted) and
|
||||
[2021](https://insights.stackoverflow.com/survey/2021/#technology-most-loved-dreaded-and-wanted).
|
||||
|
||||
Rust also helps developers write safer code with its rich type system and ownership model. Say
|
||||
goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your
|
||||
bugs will be caught by the compiler before your app even runs. And don't worry, when your app does
|
||||
Rust also helps developers write safer code with its rich type system and ownership model. Say
|
||||
goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your
|
||||
bugs will be caught by the compiler before your app even runs. And don't worry, when your app does
|
||||
run into an error, you can still get full stack-traces for your Rust code in the browser console.
|
||||
|
||||
#### Alternatives?
|
||||
### Alternatives?
|
||||
|
||||
We love to share ideas with other projects and believe we can all help each other reach the full
|
||||
potential of this exciting new technology. If you're not into Yew, you might like the following
|
||||
We love to share ideas with other projects and believe we can all help each other reach the full
|
||||
potential of this exciting new technology. If you're not into Yew, you might like the following
|
||||
projects:
|
||||
|
||||
* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web apps
|
||||
with Rust + WebAssembly"_
|
||||
* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web apps
|
||||
with Rust + WebAssembly"_
|
||||
* [Seed](https://github.com/seed-rs/seed) - _"A Rust framework for creating web apps"_
|
||||
* [Perseus](https://github.com/arctic-hen7/perseus) - _"A high-level web development framework for Rust with full support for server-side rendering and static generation"_
|
||||
* [Sycamore](https://github.com/sycamore-rs/sycamore) - _"A reactive library for creating web apps in Rust and WebAssembly"_
|
|
@ -67,7 +67,7 @@ impl Component for MyComponent {
|
|||
}
|
||||
```
|
||||
|
||||
For usage details, check out [the `html!` guide](html.md).
|
||||
For usage details, check out [the `html!` guide](../html/introduction.md).
|
||||
|
||||
### Rendered
|
||||
|
|
@ -13,7 +13,7 @@ The `html!` macro allows you to write HTML and SVG code declaratively. It is sim
|
|||
**Important notes**
|
||||
|
||||
1. The `html!` macro only accepts a single root HTML node \(this obstacle is easily overcome by
|
||||
[using fragments or iterators](html/lists.md)\)
|
||||
[using fragments or iterators](../html/lists.md)\)
|
||||
2. An empty `html! {}` invocation is valid and will not render anything
|
||||
3. Literals must always be wrapped in quotes as well as braces (i.e.
|
||||
`html! { <p>{"Hello, World"}</p> }` is valid, but not `html! { <p>Hello, World</p> }` or
|
|
@ -32,7 +32,7 @@ It can bundle assets for your app and even ships with a Sass compiler.
|
|||
|
||||
All of our examples are built with Trunk.
|
||||
|
||||
[Getting started with `trunk`](project-setup/using-trunk.md)
|
||||
[Getting started with `trunk`](../project-setup/using-trunk.md)
|
||||
|
||||
### [**`wasm-pack`**](https://rustwasm.github.io/docs/wasm-pack/)
|
||||
|
||||
|
@ -41,20 +41,20 @@ together with the [`wasm-pack-plugin`](https://github.com/wasm-tool/wasm-pack-pl
|
|||
The primary purpose of `wasm-pack` is building Wasm libraries for use in JavaScript.
|
||||
Because of this, it can only build libraries and doesn't provide useful tools like a development server or automatic rebuilds.
|
||||
|
||||
[Get started with `wasm-pack`](project-setup/using-wasm-pack.md)
|
||||
[Get started with `wasm-pack`](../project-setup/using-wasm-pack.md)
|
||||
|
||||
### [**`cargo-web`**](https://github.com/koute/cargo-web)
|
||||
|
||||
This was the best preferred tool to use before the creation of `wasm-bindgen`.
|
||||
|
||||
[Getting started with `cargo web`](project-setup/using-cargo-web.md)
|
||||
[Getting started with `cargo web`](../project-setup/using-cargo-web.md)
|
||||
|
||||
### Comparison
|
||||
|
||||
| | `trunk` | `wasm-pack` | `cargo-web` |
|
||||
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) | No Github activity for over 6 months |
|
||||
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. |
|
||||
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. |
|
||||
| Local Server | Supported | Only with webpack plugin | Supported |
|
||||
| Auto rebuild on local changes | Supported | Only with webpack plugin | Supported |
|
||||
| Asset handling | Supported | Only with webpack plugin | Static assets only |
|
||||
|
@ -62,4 +62,4 @@ This was the best preferred tool to use before the creation of `wasm-bindgen`.
|
|||
| Supported Targets | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul> <li><code>wasm32-unknown-unknown</code></li> <li><code>wasm32-unknown-emscripten</code></li> <li><code>asmjs-unknown-emscripten</code></li> </ul> |
|
||||
| `web-sys` | Compatible | Compatible | Incompatible |
|
||||
| `stdweb` | Incompatible | Compatible | Compatible |
|
||||
| Example Usage | [Sample app](./build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) | [Build script](https://www.github.com/yewstack/yew/tree/master/packages/yew-stdweb/examples) for `yew-stdweb` examples |
|
||||
| Example Usage | [Sample app](../build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) | [Build script](https://www.github.com/yewstack/yew/tree/master/packages/yew-stdweb/examples) for `yew-stdweb` examples |
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
title: "How it works"
|
||||
description: "Low level details about the framework"
|
||||
---
|
||||
|
||||
# Low-level library internals
|
||||
|
||||
## Under the hood of the `html!` macro
|
||||
|
||||
The `html!` macro turns code written in a custom HTML-like syntax into valid Rust code. Using this
|
||||
macro is not necessary for developing Yew applications, but it is recommended. The code generated
|
||||
by this macro makes use of the public Yew library API which can be used directly if you wish. Note
|
||||
that some methods used are undocumented intentionally to avoid accidental misuse. With each
|
||||
update of `yew-macro`, the generated code will be more efficient and handle any breaking changes
|
||||
without many (if any) modifications to the `html!` syntax.
|
||||
|
||||
Because the `html!` macro allows you to write code in a declarative style, your UI layout code will
|
||||
closely match the HTML that is generated to the page. This becomes increasingly useful as your
|
||||
application gets more interactive and your codebase gets larger. Rather than manually writing the
|
||||
all of the code to manipulate the DOM yourself, the macro will handle it for you.
|
||||
|
||||
Using the `html!` macro can feel pretty magic, but it has nothing to hide. If you're curious about
|
||||
how it works, try expanding the `html!` macro calls in your program. There's a useful command called
|
||||
`cargo expand` which allows you to see the expansion of Rust macros. `cargo expand` isn't shipped with
|
||||
`cargo` by default so you'll need to install it with `cargo install cargo-expand` if you haven't
|
||||
already.
|
||||
|
||||
Note that when viewing expanded macro code, you're likely to encounter unusually verbose code. The
|
||||
reason is because generated code can sometimes clash with other code in an application. In order
|
||||
to prevent issues, `proc_macro` "hygiene" is adhered to. Some examples include:
|
||||
|
||||
1. Instead of using `yew::<module>` the macro generates `::yew::<module>` to make sure that the
|
||||
Yew package is referenced correctly. This is also why `::alloc::vec::Vec::new()` is called instead
|
||||
of just `Vec::new()`.
|
||||
2. Due to potential trait method name collisions, `<Type as Trait>` is used to make sure that we're using items from the right trait.
|
||||
|
||||
## What is a virtual DOM?
|
||||
|
||||
The DOM ("document object model") is a representation of the HTML content that is managed by the browser
|
||||
for your web page. A "virtual" DOM is simply a copy of the DOM that is held in application memory. Managing
|
||||
a virtual DOM results in a higher memory overhead, but allows for batching and faster reads by avoiding
|
||||
or delaying the use of browser APIs.
|
||||
|
||||
Having a copy of the DOM in memory can be really helpful for libraries which promote the use of
|
||||
declarative UIs. Rather than needing specific code for describing how the DOM should be modified
|
||||
in response to a user event, the library can use a generalized approach with DOM "diffing". When a Yew
|
||||
component is updated and wants to change how it is rendered, the Yew library will build a second copy
|
||||
of the virtual DOM and directly compare to a virtual DOM which mirrors what is currently on screen.
|
||||
The "diff" (or difference) between the two can be broken down into incremental updates and applied in
|
||||
a batch with browser APIs. Once the updates are applied, the old virtual DOM copy is discarded and the
|
||||
new copy is saved for future diff checks.
|
||||
|
||||
This "diff" algorithm can be optimized over time to improve the performance of complex applications.
|
||||
Since Yew applications are run with WebAssembly, we believe that Yew has a competitive edge to adopt
|
||||
more sophisticated algorithms in the future.
|
||||
|
||||
The Yew virtual DOM is not exactly one-to-one with the browser DOM. It also includes "lists" and
|
||||
"components" for organizing DOM elements. A list can simply be an ordered list of elements but can
|
||||
also be much more powerful. By annotating each list element with a "key", application developers
|
||||
can help Yew make additional optimizations to ensure that when a list changes, the least amount
|
||||
of work is done to calculate the diff update. Similarly, components provide custom logic to
|
||||
indicate whether a re-render is required to help with performance.
|
||||
|
||||
## Yew scheduler and component-scoped event loop
|
||||
|
||||
*Contribute to the docs – explain how `yew::scheduler` and `yew::html::scope` work in depth*
|
||||
|
||||
## Further reading
|
||||
* [More information about macros from the Rust Book](https://doc.rust-lang.org/stable/book/ch19-06-macros.html)
|
||||
* [More information about `cargo-expand`](https://github.com/dtolnay/cargo-expand)
|
||||
* [The API documentation for `yew::virtual_dom`](https://docs.rs/yew/*/yew/virtual_dom/index.html)
|
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
title: "Optimizations & Best Practices"
|
||||
sidebar_label: Optimizations
|
||||
description: "Make your app faster"
|
||||
---
|
||||
|
||||
## Using smart pointers effectively
|
||||
|
||||
**Note: if you're unsure about some of the terms used in this section, the Rust Book has a useful
|
||||
[chapter about smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html).**
|
||||
|
||||
In an effort to avoid cloning large amounts of data to create props when re-rendering, we can use
|
||||
smart pointers to only clone a reference to the data instead of the data itself. If you pass
|
||||
references to the relevant data in your props and child components instead of the actual data you
|
||||
can avoid cloning any data until you need to modify it in the child component, where you can
|
||||
use `Rc::make_mut` to clone and obtain a mutable reference to the data you want to alter.
|
||||
|
||||
This brings further benefits in `Component::changed` when working out whether prop changes require
|
||||
the component to re-render. This is because instead of comparing the value of the data the
|
||||
underlying pointer addresses (i.e. the position in a machine's memory where the data is stored) can
|
||||
instead be compared; if two pointers point to the same data then the value of the data they point to
|
||||
must be the same. Note that the inverse might not be true! Even if two pointer addresses differ the
|
||||
underlying data might still be the same - in this case you should compare the underlying data.
|
||||
|
||||
To do this comparison you'll need to use `Rc::ptr_eq` instead of just using `PartialEq` (which is
|
||||
automatically used when comparing data using the equality operator `==`). The Rust documentation
|
||||
has [more details about `Rc::ptr_eq`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq).
|
||||
|
||||
This optimization is most useful for data types that don't implement `Copy`. If you can copy your
|
||||
data cheaply, then it isn't worth putting it behind a smart pointer. For structures that
|
||||
can be data-heavy like `Vec`s, `HashMap`s, and `String`s using smart pointers is likely to bring
|
||||
performance improvements.
|
||||
|
||||
This optimization works best if the values are never updated by the children, and even better, if
|
||||
they are rarely updated by parents. This makes `Rc<_>s` a good choice for wrapping property values
|
||||
in for pure components.
|
||||
|
||||
However, it must be noted that unless you need to clone the data yourself in the child component,
|
||||
this optimization is not only useless, it also adds unnecessary cost of reference counting. Props
|
||||
in Yew are already reference counted and no data clones occur internally.
|
||||
|
||||
## View functions
|
||||
|
||||
For code readability reasons, it often makes sense to migrate sections of `html!` to their own
|
||||
functions. Not only does this make your code more readable because it reduces the amount of
|
||||
indentation present, it also encourages good design patterns – particularly around building
|
||||
composable applications because these functions can be called in multiple places which reduces the
|
||||
amount of code that has to be written.
|
||||
|
||||
## Pure Components
|
||||
|
||||
Pure components are components that don't mutate their state, only displaying content and
|
||||
propagating messages up to normal, mutable components. They differ from view functions in that they
|
||||
can be used from within the `html!` macro using the component syntax \(`<SomePureComponent />`\)
|
||||
instead of expression syntax \(`{some_view_function()}`\), and that depending on their
|
||||
implementation, they can be memoized (this means that once a function is called its value is "saved"
|
||||
so that if it's called with the same arguments more than once it doesn't have to recompute its value
|
||||
and can just return the saved value from the first function call) - preventing re-renders for
|
||||
identical props. Yew compares the props internally and so the UI is only re-rendered if the props change.
|
||||
|
||||
## Reducing compile time using workspaces
|
||||
|
||||
Arguably, the largest drawback to using Yew is the long time it takes to compile Yew apps. The time
|
||||
taken to compile a project seems to be related to the quantity of code passed to the `html!` macro.
|
||||
This tends to not be much of an issue for smaller projects, but for larger applications it makes
|
||||
sense to split code across multiple crates to minimize the amount of work the compiler has to do for
|
||||
each change made to the application.
|
||||
|
||||
One possible approach is to make your main crate handle routing/page selection, and then make a
|
||||
different crate for each page, where each page could be a different component, or just a big
|
||||
function that produces `Html`. Code which is shared between the crates containing different parts of
|
||||
the application could be stored in a separate crate which is depended on throughout the project.
|
||||
In the best case scenario, you go from rebuilding all of your code on each compile to rebuilding
|
||||
only the main crate, and one of your page crates. In the worst case, where you edit something in the
|
||||
"common" crate, you will be right back to where you started: compiling all code that depends on that
|
||||
commonly shared crate, which is probably everything else.
|
||||
|
||||
If your main crate is too heavyweight, or you want to rapidly iterate on a deeply nested page \(eg.
|
||||
a page that renders on top of another page\), you can use an example crate to create a simplified
|
||||
implementation of the main page and render the component you are working on on top of that.
|
||||
|
||||
## Reducing binary sizes
|
||||
|
||||
* optimize Rust code
|
||||
* `wee_alloc` \( using tiny allocator \)
|
||||
* `cargo.toml` \( defining release profile \)
|
||||
* optimize wasm code using `wasm-opt`
|
||||
|
||||
**Note: more information about reducing binary sizes can be found in the
|
||||
[Rust Wasm Book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size).**
|
||||
|
||||
### wee\_alloc
|
||||
|
||||
[wee\_alloc](https://github.com/rustwasm/wee_alloc) is a tiny allocator that is much smaller than the allocator that is normally used in Rust binaries. Replacing the default allocator with this one will result in smaller Wasm file sizes, at the expense of speed and memory overhead.
|
||||
|
||||
The slower speed and memory overhead are minor in comparison to the size gains made by not including the default allocator. This smaller file size means that your page will load faster, and so it is generally recommended that you use this allocator over the default, unless your app is doing some allocation-heavy work.
|
||||
|
||||
```rust ,ignore
|
||||
// Use `wee_alloc` as the global allocator.
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
```
|
||||
|
||||
### Cargo.toml
|
||||
|
||||
It is possible to configure release builds to be smaller using the available settings in the
|
||||
`[profile.release]` section of your `Cargo.toml`.
|
||||
|
||||
|
||||
```toml, title=Cargo.toml
|
||||
[profile.release]
|
||||
# less code to include into binary
|
||||
panic = 'abort'
|
||||
# optimization over all codebase ( better optimization, slower build )
|
||||
codegen-units = 1
|
||||
# optimization for size ( more aggressive )
|
||||
opt-level = 'z'
|
||||
# optimization for size
|
||||
# opt-level = 's'
|
||||
# link time optimization using using whole-program analysis
|
||||
lto = true
|
||||
```
|
||||
|
||||
### wasm-opt
|
||||
|
||||
Further more it is possible to optimize size of `wasm` code.
|
||||
|
||||
The Rust Wasm Book has a section about reducing the size of Wasm binaries:
|
||||
[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html)
|
||||
|
||||
* using `wasm-pack` which by default optimizes `wasm` code in release builds
|
||||
* using `wasm-opt` directly on `wasm` files.
|
||||
|
||||
```text
|
||||
wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm
|
||||
```
|
||||
|
||||
#### Build size of 'minimal' example in yew/examples/
|
||||
|
||||
Note: `wasm-pack` combines optimization for Rust and Wasm code. `wasm-bindgen` is used in this example without any Rust size optimization.
|
||||
|
||||
| used tool | size |
|
||||
| :--- | :--- |
|
||||
| wasm-bindgen | 158KB |
|
||||
| wasm-bindgen + wasm-opt -Os | 116KB |
|
||||
| wasm-pack | 99 KB |
|
||||
|
||||
## Further reading:
|
||||
* [The Rust Book's chapter on smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html)
|
||||
* [Information from the Rust Wasm Book about reducing binary sizes](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size)
|
||||
* [Documentation about Rust profiles](https://doc.rust-lang.org/cargo/reference/profiles.html)
|
||||
* [binaryen project](https://github.com/WebAssembly/binaryen)
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: "Portals"
|
||||
description: "Rendering into out-of-tree DOM nodes"
|
||||
---
|
||||
|
||||
## How to think about portals?
|
||||
|
||||
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
|
||||
`yew::create_portal(child, host)` returns a `Html` value that renders `child` not hierarchically under its parent component,
|
||||
but as a child of the `host` element.
|
||||
|
||||
## Usage
|
||||
|
||||
Typical uses of portals can include modal dialogs and hovercards, as well as more technical applications such as controlling the contents of an element's [`shadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot), appending stylesheets to the surrounding document's `<head>` and collecting referenced elements inside a central `<defs>` element of an `<svg>`.
|
||||
|
||||
Note that `yew::create_portal` is a rather low-level building block, on which other components should be built that provide the interface for your specific use case. As an example, here is a simple modal dialogue that renders its `children` into an element outside `yew`'s control, identified by the `id="modal_host"`.
|
||||
|
||||
```rust
|
||||
use yew::{html, create_portal, function_component, Children, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ModalProps {
|
||||
#[prop_or_default]
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component(Modal)]
|
||||
fn modal(props: &ModalProps) -> Html {
|
||||
let modal_host = gloo_utils::document()
|
||||
.get_element_by_id("modal_host")
|
||||
.expect("a #modal_host element");
|
||||
|
||||
create_portal(
|
||||
html!{ {for props.children.iter()} },
|
||||
modal_host.into(),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Further reading
|
||||
- [Portals example](https://github.com/yewstack/yew/tree/master/examples/portals)
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: "Agents"
|
||||
description: "Yew's Actor System"
|
||||
---
|
||||
|
||||
Agents are similar to Angular's [Services](https://angular.io/guide/architecture-services)
|
||||
\(but without dependency injection\), and provide Yew with an
|
||||
[Actor Model](https://en.wikipedia.org/wiki/Actor_model). Agents can be used to route messages
|
||||
between components independently of where they sit in the component hierarchy, or they can be used
|
||||
to create shared state between different components. Agents can also be used to offload
|
||||
computationally expensive tasks from the main thread which renders the UI. There is also planned
|
||||
support for using agents to allow Yew applications to communicate across tabs \(in the future\).
|
||||
|
||||
In order for agents to run concurrently, Yew uses
|
||||
[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).
|
||||
|
||||
## Lifecycle
|
||||
|
||||

|
||||
|
||||
## Types of Agents
|
||||
|
||||
### Reaches
|
||||
|
||||
* Context - There will exist at most one instance of a Context Agent at any given time. Bridges will
|
||||
spawn or connect to an already spawned agent on the UI thread. This can be used to coordinate
|
||||
state between components or other agents. When no bridges are connected to this agent, the agent
|
||||
will disappear.
|
||||
|
||||
* Job - Spawn a new agent on the UI thread for every new bridge. This is good for moving shared but
|
||||
independent behavior that communicates with the browser out of components. \(TODO verify\) When
|
||||
the task is done, the agent will disappear.
|
||||
|
||||
* Public - Same as Context, but runs on its own web worker.
|
||||
|
||||
* Private - Same as Job, but runs on its own web worker.
|
||||
|
||||
* Global \(WIP\)
|
||||
|
||||
## Communication between Agents and Components
|
||||
|
||||
### Bridges
|
||||
|
||||
A bridge allows bi-directional communication between an agent and a component. Bridges also allow agents to communicate with one another.
|
||||
|
||||
A `use_bridge` hook is also provided to create bridges in a function component.
|
||||
|
||||
### Dispatchers
|
||||
|
||||
A dispatcher allows uni-directional communication between a component and an agent. A dispatcher allows a component to send messages to an agent.
|
||||
|
||||
## Overhead
|
||||
|
||||
Agents that use web workers \(i.e. Private and Public\) will incur a serialization overhead on the
|
||||
messages they send and receive. They use [bincode](https://github.com/servo/bincode) to communicate
|
||||
with other threads, so the cost is substantially higher than just calling a function. Unless the
|
||||
cost of computation will outweigh the cost of message passing, you should use agents running on the
|
||||
UI thread \(i.e. Job or Context\).
|
||||
|
||||
## Further reading
|
||||
|
||||
* The [pub\_sub](https://github.com/yewstack/yew/tree/master/examples/pub_sub) example shows how
|
||||
components can use agents to communicate with each other.
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
title: "Callbacks"
|
||||
---
|
||||
|
||||
## Callbacks
|
||||
|
||||
Callbacks are used to communicate with services, agents, and parent components within Yew.
|
||||
Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned.
|
||||
|
||||
They have an `emit` function that takes their `<IN>` type as an argument and converts that to a message expected by its destination. If a callback from a parent is provided in props to a child component, the child can call `emit` on the callback in its `update` lifecycle hook to send a message back to its parent. Closures or Functions provided as props inside the `html!` macro are automatically converted to Callbacks.
|
||||
|
||||
A simple use of a callback might look something like this:
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html};
|
||||
|
||||
enum Msg {
|
||||
Clicked,
|
||||
}
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// highlight-next-line
|
||||
let onclick = ctx.link().callback(|_| Msg::Clicked);
|
||||
html! {
|
||||
// highlight-next-line
|
||||
<button {onclick}>{ "Click" }</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The function passed to `callback` must always take a parameter. For example, the `onclick` handler requires a function which takes a parameter of type `MouseEvent`. The handler can then decide what kind of message should be sent to the component. This message is scheduled for the next update loop unconditionally.
|
||||
|
||||
If you need a callback that might not need to cause an update, use `batch_callback`.
|
||||
|
||||
```rust
|
||||
use yew::{events::KeyboardEvent, html, Component, Context, Html};
|
||||
|
||||
enum Msg {
|
||||
Submit,
|
||||
}
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// highlight-start
|
||||
let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| {
|
||||
if event.key() == "Enter" {
|
||||
Some(Msg::Submit)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
html! {
|
||||
<input type="text" {onkeypress} />
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Relevant examples
|
||||
- [Counter](https://github.com/yewstack/yew/tree/master/examples/counter)
|
||||
- [Timer](https://github.com/yewstack/yew/tree/master/examples/timer)
|
|
@ -0,0 +1,252 @@
|
|||
---
|
||||
title: "Children"
|
||||
---
|
||||
|
||||
## General usage
|
||||
|
||||
_Most of the time,_ when allowing a component to have children, you don't care
|
||||
what type of children the component has. In such cases, the below example will
|
||||
suffice.
|
||||
|
||||
```rust
|
||||
use yew::{html, Children, Component, Context, Html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ListProps {
|
||||
#[prop_or_default]
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
pub struct List;
|
||||
|
||||
impl Component for List {
|
||||
type Message = ();
|
||||
type Properties = ListProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div class="list">
|
||||
{ for ctx.props().children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced usage
|
||||
|
||||
### Typed children
|
||||
In cases where you want one type of component to be passed as children to your component,
|
||||
you can use `yew::html::ChildrenWithProps<T>`.
|
||||
|
||||
```rust
|
||||
use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
|
||||
|
||||
pub struct Item;
|
||||
|
||||
impl Component for Item {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
{ "item" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ListProps {
|
||||
#[prop_or_default]
|
||||
pub children: ChildrenWithProps<Item>,
|
||||
}
|
||||
|
||||
pub struct List;
|
||||
|
||||
impl Component for List {
|
||||
type Message = ();
|
||||
type Properties = ListProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div class="list">
|
||||
{ for ctx.props().children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Enum typed children
|
||||
Of course, sometimes you might need to restrict the children to a few different
|
||||
components. In these cases, you have to get a little more hands-on with Yew.
|
||||
|
||||
The [`derive_more`](https://github.com/JelteF/derive_more) crate is used here
|
||||
for better ergonomics. If you don't want to use it, you can manually implement
|
||||
`From` for each variant.
|
||||
|
||||
```rust
|
||||
use yew::{
|
||||
html, html::ChildrenRenderer, virtual_dom::VChild, Component,
|
||||
Context, Html, Properties,
|
||||
};
|
||||
|
||||
pub struct Primary;
|
||||
|
||||
impl Component for Primary {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
{ "Primary" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Secondary;
|
||||
|
||||
impl Component for Secondary {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
{ "Secondary" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_more::From, PartialEq)]
|
||||
pub enum Item {
|
||||
Primary(VChild<Primary>),
|
||||
Secondary(VChild<Secondary>),
|
||||
}
|
||||
|
||||
// Now, we implement `Into<Html>` so that yew knows how to render `Item`.
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<Html> for Item {
|
||||
fn into(self) -> Html {
|
||||
match self {
|
||||
Self::Primary(child) => child.into(),
|
||||
Self::Secondary(child) => child.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ListProps {
|
||||
#[prop_or_default]
|
||||
pub children: ChildrenRenderer<Item>,
|
||||
}
|
||||
|
||||
pub struct List;
|
||||
|
||||
impl Component for List {
|
||||
type Message = ();
|
||||
type Properties = ListProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div class="list">
|
||||
{ for ctx.props().children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Optional typed child
|
||||
You can also have a single optional child component of a specific type too:
|
||||
|
||||
```rust
|
||||
use yew::{
|
||||
html, html_nested, virtual_dom::VChild, Component,
|
||||
Context, Html, Properties
|
||||
};
|
||||
|
||||
pub struct PageSideBar;
|
||||
|
||||
impl Component for PageSideBar {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
{ "sidebar" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct PageProps {
|
||||
#[prop_or_default]
|
||||
pub sidebar: Option<VChild<PageSideBar>>,
|
||||
}
|
||||
|
||||
struct Page;
|
||||
|
||||
impl Component for Page {
|
||||
type Message = ();
|
||||
type Properties = PageProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div class="page">
|
||||
{ ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() }
|
||||
// ... page content
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The page component can be called either with the sidebar or without:
|
||||
|
||||
pub fn render_page(with_sidebar: bool) -> Html {
|
||||
if with_sidebar {
|
||||
// Page with sidebar
|
||||
html! {
|
||||
<Page sidebar={{html_nested! {
|
||||
<PageSideBar />
|
||||
}}} />
|
||||
}
|
||||
} else {
|
||||
// Page without sidebar
|
||||
html! {
|
||||
<Page />
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,305 @@
|
|||
---
|
||||
title: "Introduction"
|
||||
description: "Components and their lifecycle hooks"
|
||||
---
|
||||
|
||||
## What are Components?
|
||||
|
||||
Components are the building blocks of Yew. They manage their own state and can render themselves to the DOM. Components are created by implementing the `Component` trait for a type. The `Component`
|
||||
trait has a number of methods which need to be implemented; Yew will call these at different stages
|
||||
in the lifecycle of a component.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
:::important contribute
|
||||
`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/yew/issues/1915)
|
||||
:::
|
||||
|
||||
## Lifecycle Methods
|
||||
|
||||
### Create
|
||||
|
||||
When a component is created, it receives properties from its parent component and is stored within
|
||||
the `Context<Self>` thats passed down to the `create` method. The properties can be used to
|
||||
initialize the component's state and the "link" can be used to register callbacks or send messages to the component.
|
||||
|
||||
```rust
|
||||
use yew::{Component, Context, html, Html, Properties};
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props;
|
||||
|
||||
pub struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
// highlight-start
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
MyComponent
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
// impl
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
The `view` method allows you to describe how a component should be rendered to the DOM. Writing
|
||||
HTML-like code using Rust functions can become quite messy, so Yew provides a macro called `html!`
|
||||
for declaring HTML and SVG nodes (as well as attaching attributes and event listeners to them) and a
|
||||
convenient way to render child components. The macro is somewhat similar to React's JSX (the
|
||||
differences in programming language aside).
|
||||
One difference is that Yew provides a shorthand syntax for properties, similar to Svelte, where instead of writing `onclick={onclick}`, you can just write `{onclick}`.
|
||||
|
||||
```rust
|
||||
use yew::{Component, Context, html, Html, Properties};
|
||||
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct Props {
|
||||
button_text: String,
|
||||
}
|
||||
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let onclick = ctx.link().callback(|_| Msg::Click);
|
||||
html! {
|
||||
<button {onclick}>{ &ctx.props().button_text }</button>
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
For usage details, check out [the `html!` guide](../html/introduction).
|
||||
|
||||
### Rendered
|
||||
|
||||
The `rendered` component lifecycle method is called once `view` has been called and Yew has rendered
|
||||
the results to the DOM, but before the browser refreshes the page. This method is useful when you
|
||||
want to perform actions that can only be completed after the component has rendered elements. There
|
||||
is also a parameter called `first_render` which can be used to determine whether this function is
|
||||
being called on the first render, or instead a subsequent one.
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{
|
||||
Component, Context, html, Html, NodeRef,
|
||||
};
|
||||
|
||||
pub struct MyComponent {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
node_ref: NodeRef::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<input ref={self.node_ref.clone()} type="text" />
|
||||
}
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
|
||||
if first_render {
|
||||
if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
:::tip note
|
||||
Note that this lifecycle method does not require an implementation and will do nothing by default.
|
||||
:::
|
||||
|
||||
### Update
|
||||
|
||||
Communication with components happens primarily through messages which are handled by the
|
||||
`update` lifecycle method. This allows the component to update itself
|
||||
based on what the message was, and determine if it needs to re-render itself. Messages can be sent
|
||||
by event listeners, child components, Agents, Services, or Futures.
|
||||
|
||||
Here's an example of what an implementation of `update` could look like:
|
||||
|
||||
```rust
|
||||
use yew::{Component, Context, html, Html};
|
||||
|
||||
// highlight-start
|
||||
pub enum Msg {
|
||||
SetInputEnabled(bool)
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
struct MyComponent {
|
||||
input_enabled: bool,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
// highlight-next-line
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
input_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::SetInputEnabled(enabled) => {
|
||||
if self.input_enabled != enabled {
|
||||
self.input_enabled = enabled;
|
||||
true // Re-render
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
// impl
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
Components may be re-rendered by their parents. When this happens, they could receive new properties
|
||||
and need to re-render. This design facilitates parent to child component communication by just
|
||||
changing the values of a property. There is a default implementation which re-renders the component
|
||||
when props are changed.
|
||||
|
||||
### Destroy
|
||||
|
||||
After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is
|
||||
necessary if you need to undertake operations to clean up after earlier actions of a component
|
||||
before it is destroyed. This method is optional and does nothing by default.
|
||||
|
||||
### Infinite loops
|
||||
|
||||
Infinite loops are possible with Yew's lifecycle methods, but are only caused when trying to update
|
||||
the same component after every render when that update also requests the component to be rendered.
|
||||
|
||||
A simple example can be seen below:
|
||||
|
||||
```rust
|
||||
use yew::{Context, Component, Html};
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
||||
// We are going to always request to re-render on any msg
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
// For this example it doesn't matter what is rendered
|
||||
Html::default()
|
||||
}
|
||||
|
||||
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
|
||||
// Request that the component is updated with this new msg
|
||||
ctx.link().send_message(());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's run through what happens here:
|
||||
1. Component is created using the `create` function.
|
||||
2. The `view` method is called so Yew knows what to render to the browser DOM.
|
||||
3. The `rendered` method is called, which schedules an update message using the `Context` link.
|
||||
4. Yew finishes the post-render phase.
|
||||
5. Yew checks for scheduled events and sees the update message queue is not empty so works through
|
||||
the messages.
|
||||
6. The `update` method is called which returns `true` to indicate something has changed and the
|
||||
component needs to re-render.
|
||||
7. Jump back to 2.
|
||||
|
||||
You can still schedule updates in the `rendered` method and it's often useful to do so, but
|
||||
consider how your component will terminate this loop when you do.
|
||||
|
||||
|
||||
## Associated Types
|
||||
|
||||
The `Component` trait has two associated types: `Message` and `Properties`.
|
||||
|
||||
```rust ,ignore
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `Message` type is used to send messages to a component after an event has taken place; for
|
||||
example you might want to undertake some action when a user clicks a button or scrolls down the
|
||||
page. Because components tend to have to respond to more than one event, the `Message` type will
|
||||
normally be an enum, where each variant is an event to be handled.
|
||||
|
||||
When organizing your codebase, it is sensible to include the definition of the `Message` type in the
|
||||
same module in which your component is defined. You may find it helpful to adopt a consistent naming
|
||||
convention for message types. One option (though not the only one) is to name the types
|
||||
`ComponentNameMsg`, e.g. if your component was called `Homepage` then you might call the type
|
||||
`HomepageMsg`.
|
||||
|
||||
```rust
|
||||
enum Msg {
|
||||
Click,
|
||||
FormInput(String)
|
||||
}
|
||||
```
|
||||
|
||||
`Properties` represents the information passed to a component from its parent. This type must implement the `Properties` trait \(usually by deriving it\) and can specify whether certain properties are required or optional. This type is used when creating and updating a component. It is common practice to create a struct called `Props` in your component's module and use that as the component's `Properties` type. It is common to shorten "properties" to "props". Since props are handed down from parent components, the root component of your application typically has a `Properties` type of `()`. If you wish to specify properties for your root component, use the `App::mount_with_props` method.
|
||||
|
||||
## Context
|
||||
|
||||
All component lifecycle methods take a context object. This object provides a reference to component's scope, which
|
||||
allows sending messages to a component and the props passed to the component.
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
---
|
||||
title: "Properties"
|
||||
description: "Parent to child communication"
|
||||
---
|
||||
|
||||
Properties enable child and parent components to communicate with each other.
|
||||
Every component has an associated properties type which describes what is passed down from the parent.
|
||||
In theory this can be any type that implements the `Properties` trait, but in practice there's no
|
||||
reason for it to be anything but a struct where each field represents a property.
|
||||
|
||||
## Derive macro
|
||||
|
||||
Instead of implementing the `Properties` trait yourself, you should use `#[derive(Properties)]` to
|
||||
automatically generate the implementation instead.
|
||||
Types for which you derive `Properties` must also implement `PartialEq`.
|
||||
|
||||
### Field attributes
|
||||
|
||||
When deriving `Properties`, all fields are required by default.
|
||||
The following attributes allow you to give your props initial values which will be used unless they're set to another value.
|
||||
|
||||
:::tip
|
||||
Attributes aren't visible in Rustdoc generated documentation.
|
||||
The docstrings of your properties should mention whether a prop is optional and if it has a special default value.
|
||||
:::
|
||||
|
||||
#### `#[prop_or_default]`
|
||||
|
||||
Initialize the prop value with the default value of the field's type using the `Default` trait.
|
||||
|
||||
#### `#[prop_or(value)]`
|
||||
|
||||
Use `value` to initialize the prop value. `value` can be any expression that returns the field's type.
|
||||
For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`.
|
||||
|
||||
#### `#[prop_or_else(function)]`
|
||||
|
||||
Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type.
|
||||
|
||||
## `PartialEq`
|
||||
|
||||
`Properties` require `PartialEq` to be implemented. This is so that they can be compared by Yew to call the `changed` method
|
||||
only when they change.
|
||||
|
||||
## Memory/speed overhead of using Properties
|
||||
|
||||
Internally properties are reference counted. This means that only a pointer is passed down the component tree for props.
|
||||
It saves us from the cost of having to clone the entire props, which might be expensive.
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use yew::Properties;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum LinkColor {
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
Black,
|
||||
Purple,
|
||||
}
|
||||
|
||||
fn create_default_link_color() -> LinkColor {
|
||||
LinkColor::Blue
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
/// The link must have a target.
|
||||
href: String,
|
||||
text: String,
|
||||
/// Color of the link. Defaults to `Blue`.
|
||||
#[prop_or_else(create_default_link_color)]
|
||||
color: LinkColor,
|
||||
/// The view function will not specify a size if this is None.
|
||||
#[prop_or_default]
|
||||
size: Option<u32>,
|
||||
/// When the view function doesn't specify active, it defaults to true.
|
||||
#[prop_or(true)]
|
||||
active: bool,
|
||||
}
|
||||
```
|
||||
|
||||
## Props macro
|
||||
|
||||
The `yew::props!` macro allows you to build properties the same way the `html!` macro does it.
|
||||
|
||||
The macro uses the same syntax as a struct expression except that you can't use attributes or a base expression (`Foo { ..base }`).
|
||||
The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`).
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use yew::{props, Properties};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum LinkColor {
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
Black,
|
||||
Purple,
|
||||
}
|
||||
|
||||
fn create_default_link_color() -> LinkColor {
|
||||
LinkColor::Blue
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
/// The link must have a target.
|
||||
href: String,
|
||||
text: Rc<String>,
|
||||
/// Color of the link. Defaults to `Blue`.
|
||||
#[prop_or_else(create_default_link_color)]
|
||||
color: LinkColor,
|
||||
/// The view function will not specify a size if this is None.
|
||||
#[prop_or_default]
|
||||
size: Option<u32>,
|
||||
/// When the view function doesn't specify active, it defaults to true.
|
||||
#[prop_or(true)]
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl LinkProps {
|
||||
pub fn new_link_with_size(href: String, text: String, size: u32) -> Self {
|
||||
// highlight-start
|
||||
props! {LinkProps {
|
||||
href,
|
||||
text: Rc::from(text),
|
||||
size,
|
||||
}}
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
title: "Refs"
|
||||
description: "Out-of-band DOM access"
|
||||
---
|
||||
|
||||
The `ref` keyword can be used inside of any HTML element or component to get the DOM `Element` that
|
||||
the item is attached to. This can be used to make changes to the DOM outside of the `view` lifecycle
|
||||
method.
|
||||
|
||||
This is useful for getting ahold of canvas elements, or scrolling to different sections of a page.
|
||||
For example, using a `NodeRef` in a component's `rendered` method allows you to make draw calls to
|
||||
a canvas element after it has been rendered from `view`.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```rust
|
||||
use web_sys::Element;
|
||||
use yew::{html, Component, Context, Html, NodeRef};
|
||||
|
||||
struct Comp {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
// highlight-next-line
|
||||
node_ref: NodeRef::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
// highlight-next-line
|
||||
<div ref={self.node_ref.clone()}></div>
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
|
||||
// highlight-start
|
||||
let has_attributes = self.node_ref
|
||||
.cast::<Element>()
|
||||
.unwrap()
|
||||
.has_attributes();
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Relevant examples
|
||||
- [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs)
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: "Scope"
|
||||
description: "Component's Scope"
|
||||
---
|
||||
|
||||
## Component's `Scope<_>` API
|
||||
|
||||
The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves
|
||||
using messages. We obtain a reference to this by calling `link()` on the context object passed to the component.
|
||||
|
||||
### `send_message`
|
||||
|
||||
Sends a message to the component.
|
||||
Messages are handled by the `update` method which determines whether the component should re-render.
|
||||
|
||||
### `send_message_batch`
|
||||
|
||||
Sends multiple messages to the component at the same time.
|
||||
This is similar to `send_message` but if any of the messages cause the `update` method to return `true`,
|
||||
the component will re-render after all messages in the batch have been processed.
|
||||
|
||||
If the given vector is empty, this function doesn't do anything.
|
||||
|
||||
### `callback`
|
||||
|
||||
Create a callback that will send a message to the component when it is executed.
|
||||
Under the hood, it will call `send_message` with the message returned by the provided closure.
|
||||
|
||||
There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`.
|
||||
You should use this with care though, as the resulting callback will panic if executed more than once.
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html};
|
||||
|
||||
enum Msg {
|
||||
Text(String),
|
||||
}
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// Create a callback that accepts some text and sends it
|
||||
// to the component as the `Msg::Text` message variant.
|
||||
// highlight-next-line
|
||||
let cb = ctx.link().callback(|text: String| Msg::Text(text));
|
||||
|
||||
// The previous line is needlessly verbose to make it clearer.
|
||||
// It can be simplified it to this:
|
||||
// highlight-next-line
|
||||
let cb = ctx.link().callback(Msg::Text);
|
||||
|
||||
// Will send `Msg::Text("Hello World!")` to the component.
|
||||
// highlight-next-line
|
||||
cb.emit("Hello World!".to_owned());
|
||||
|
||||
html! {
|
||||
// html here
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `batch_callback`
|
||||
|
||||
Create a callback that will send a batch of messages to the component when it is executed.
|
||||
The difference to `callback` is that the closure passed to this method doesn't have to return a message.
|
||||
Instead, the closure can return either `Vec<Msg>` or `Option<Msg>` where `Msg` is the component's message type.
|
||||
|
||||
`Vec<Msg>` is treated as a batch of messages and uses `send_message_batch` under the hood.
|
||||
|
||||
`Option<Msg>` calls `send_message` if it is `Some`. If the value is `None`, nothing happens.
|
||||
This can be used in cases where, depending on the situation, an update isn't required.
|
||||
|
||||
This is achieved using the `SendAsMessage` trait which is only implemented for these types.
|
||||
You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`.
|
||||
|
||||
Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`.
|
||||
The same restrictions apply as for `callback_once`.
|
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
title: "Contexts"
|
||||
sidebar_label: Contexts
|
||||
description: "Using contexts to pass data within application"
|
||||
---
|
||||
|
||||
Generally data is passed down the component tree using props but that becomes tedious for values such as
|
||||
user preferences, authentication information etc. Consider the following example which passes down the
|
||||
theme using props:
|
||||
```rust
|
||||
use yew::{html, Children, Component, Context, Html, Properties};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct NavbarProps {
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
pub struct Navbar;
|
||||
|
||||
impl Component for Navbar {
|
||||
type Message = ();
|
||||
type Properties = NavbarProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<Title theme={ctx.props().theme.clone()}>
|
||||
{ "App title" }
|
||||
</Title>
|
||||
<NavButton theme={ctx.props().theme.clone()}>
|
||||
{ "Somewhere" }
|
||||
</NavButton>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct ThemeProps {
|
||||
theme: Theme,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[yew::function_component(Title)]
|
||||
fn title(_props: &ThemeProps) -> Html {
|
||||
html! {
|
||||
// impl
|
||||
}
|
||||
}
|
||||
#[yew::function_component(NavButton)]
|
||||
fn nav_button(_props: &ThemeProps) -> Html {
|
||||
html! {
|
||||
// impl
|
||||
}
|
||||
}
|
||||
|
||||
// root
|
||||
let theme = Theme {
|
||||
foreground: "yellow".to_owned(),
|
||||
background: "pink".to_owned(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<Navbar {theme} />
|
||||
};
|
||||
```
|
||||
|
||||
Passing down data like this isn't ideal for something like a theme which needs to be available everywhere.
|
||||
This is where contexts come in.
|
||||
|
||||
Contexts provide a way to share data between components without passing them down explicitly as props.
|
||||
They make data available to all components in the tree.
|
||||
|
||||
## Using Contexts
|
||||
|
||||
In order to use contexts, we need a struct which defines what data is to be passed.
|
||||
For the above use-case, consider the following struct:
|
||||
```rust
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
```
|
||||
|
||||
A context provider is required to consume the context. `ContextProvider<T>`, where `T` is the context struct is used as the provider.
|
||||
`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them.
|
||||
The children are re-rendered when the context changes.
|
||||
|
||||
### Consuming context
|
||||
|
||||
#### Struct components
|
||||
|
||||
The `Scope::context` method is used to consume contexts in struct components.
|
||||
|
||||
##### Example
|
||||
|
||||
```rust
|
||||
use yew::{Callback, html, Component, Context, Html};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
|
||||
struct ContextDemo;
|
||||
|
||||
impl Component for ContextDemo {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let (theme, _) = ctx
|
||||
.link()
|
||||
.context::<Theme>(Callback::noop())
|
||||
.expect("context to be set");
|
||||
html! {
|
||||
<button style={format!(
|
||||
"background: {}; color: {};",
|
||||
theme.background,
|
||||
theme.foreground
|
||||
)}
|
||||
>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Function components
|
||||
|
||||
`use_context` hook is used to consume contexts in function components.
|
||||
See [docs for use_context](function-components/pre-defined-hooks.md#use_context) to learn more.
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
title: "#[function_component]"
|
||||
description: "The #[function_component] attribute"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
`#[function_component(_)]` turns a normal Rust function into a function component.
|
||||
Functions with the attribute have to return `Html` and may take a single parameter for the type of props the component should accept.
|
||||
The parameter type needs to be a reference to a `Properties` type (ex. `props: &MyProps`).
|
||||
If the function doesn't have any parameters the resulting component doesn't accept any props.
|
||||
|
||||
The attribute doesn't replace your original function with a component. You need to provide a name as an input to the attribute which will be the identifier of the component.
|
||||
Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this:
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html};
|
||||
|
||||
#[function_component(ChatContainer)]
|
||||
pub fn chat_container() -> Html {
|
||||
html! {
|
||||
// chat container impl
|
||||
}
|
||||
}
|
||||
|
||||
html! {
|
||||
<ChatContainer />
|
||||
};
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="With props" label="With props">
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct RenderedAtProps {
|
||||
pub time: String,
|
||||
}
|
||||
|
||||
#[function_component(RenderedAt)]
|
||||
pub fn rendered_at(props: &RenderedAtProps) -> Html {
|
||||
html! {
|
||||
<p>
|
||||
<b>{ "Rendered at: " }</b>
|
||||
{ &props.time }
|
||||
</p>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Without props" label="Without props">
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, use_state, Callback};
|
||||
|
||||
#[function_component(App)]
|
||||
fn app() -> Html {
|
||||
let counter = use_state(|| 0);
|
||||
|
||||
let onclick = {
|
||||
let counter = counter.clone();
|
||||
Callback::from(move |_| counter.set(*counter + 1))
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<button {onclick}>{ "Increment value" }</button>
|
||||
<p>
|
||||
<b>{ "Current value: " }</b>
|
||||
{ *counter }
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Generic function components
|
||||
|
||||
The `#[function_component(_)]` attribute also works with generic functions for creating generic components.
|
||||
|
||||
```rust title=my_generic_component.rs
|
||||
use std::fmt::Display;
|
||||
use yew::{function_component, html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props<T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
data: T,
|
||||
}
|
||||
|
||||
#[function_component(MyGenericComponent)]
|
||||
pub fn my_generic_component<T>(props: &Props<T>) -> Html
|
||||
where
|
||||
T: PartialEq + Display,
|
||||
{
|
||||
html! {
|
||||
<p>
|
||||
{ &props.data }
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
// used like this
|
||||
html! {
|
||||
<MyGenericComponent<i32> data=123 />
|
||||
};
|
||||
|
||||
// or
|
||||
html! {
|
||||
<MyGenericComponent<String> data={"foo".to_string()} />
|
||||
};
|
||||
```
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: "Custom Hooks"
|
||||
description: "Defining your own Hooks "
|
||||
---
|
||||
|
||||
## Defining custom Hooks
|
||||
|
||||
Component's stateful logic can be extracted into usable function by creating custom Hooks.
|
||||
|
||||
Consider that we have a component which subscribes to an agent and displays the messages sent to it.
|
||||
```rust
|
||||
use yew::{function_component, html, use_effect, use_state, Callback};
|
||||
use yew_agent::Bridged;
|
||||
// EventBus is an implementation yew_agent::Agent
|
||||
use website_test::agents::EventBus;
|
||||
|
||||
|
||||
#[function_component(ShowMessages)]
|
||||
pub fn show_messages() -> Html {
|
||||
let state = use_state(Vec::new);
|
||||
|
||||
{
|
||||
let state = state.clone();
|
||||
use_effect(move || {
|
||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||
let mut messages = (*state).clone();
|
||||
messages.push(msg);
|
||||
state.set(messages)
|
||||
}));
|
||||
|
||||
|| drop(producer)
|
||||
});
|
||||
}
|
||||
|
||||
let output = state.iter().map(|it| html! { <p>{ it }</p> });
|
||||
html! { <div>{ for output }</div> }
|
||||
}
|
||||
```
|
||||
|
||||
There's one problem with this code: the logic can't be reused by another component.
|
||||
If we build another component which keeps track of the messages, instead of copying the code we can move the logic into a custom hook.
|
||||
|
||||
We'll start by creating a new function called `use_subscribe`.
|
||||
The `use_` prefix conventionally denotes that a function is a hook.
|
||||
This function will take no arguments and return `Rc<RefCell<Vec<String>>>`.
|
||||
```rust
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
This is a simple hook which can be created by combining other hooks. For this example, we'll two pre-defined hooks.
|
||||
We'll use `use_state` hook to store the `Vec` for messages, so they persist between component re-renders.
|
||||
We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle.
|
||||
|
||||
```rust
|
||||
use std::collections::HashSet;
|
||||
use yew::{use_effect, use_state, Callback};
|
||||
use yew_agent::Bridged;
|
||||
// EventBus is an implementation yew_agent::Agent
|
||||
use website_test::agents::EventBus;
|
||||
|
||||
fn use_subscribe() -> Vec<String> {
|
||||
let state = use_state(Vec::new);
|
||||
|
||||
let effect_state = state.clone();
|
||||
|
||||
use_effect(move || {
|
||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||
let mut messages = (*effect_state).clone();
|
||||
messages.push(msg);
|
||||
effect_state.set(messages)
|
||||
}));
|
||||
|| drop(producer)
|
||||
});
|
||||
|
||||
(*state).clone()
|
||||
}
|
||||
```
|
||||
|
||||
Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already
|
||||
|
||||
### Writing primitive hooks
|
||||
|
||||
`use_hook` function is used to write such hooks. View the docs on [docs.rs](https://docs.rs/yew/0.18.0/yew-functional/use_hook.html) for the documentation
|
||||
and `hooks` directory to see implementations of pre-defined hooks.
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
title: "Function Components"
|
||||
sidebar_label: Introduction
|
||||
description: "Introduction to function components "
|
||||
---
|
||||
|
||||
Function components are a simplified version of normal components. They consist of a single function
|
||||
that receives props and determines what should be rendered by returning `Html`. Basically, it's a
|
||||
component that's been reduced to just the `view` method. On its own that would be quite limiting
|
||||
because you can only create pure components, but that's where Hooks come in. Hooks allow function
|
||||
components to maintain their own internal state and use other Yew features without needing to manually
|
||||
implement the `Component` trait.
|
||||
|
||||
## Creating function components
|
||||
|
||||
The easiest way to create a function component is to add the [`#[function_component]`](./../function-components/attribute.md) attribute to a function.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html};
|
||||
|
||||
#[function_component(HelloWorld)]
|
||||
fn hello_world() -> Html {
|
||||
html! { "Hello world" }
|
||||
}
|
||||
```
|
||||
|
||||
### Under the hood
|
||||
|
||||
There are two parts to how Yew implements function components.
|
||||
|
||||
The first part is the `FunctionProvider` trait which is analogous to the `Component` trait, except
|
||||
that it only has a single method (called `run`). The second part is the `FunctionComponent` struct
|
||||
which wraps types implementing `FunctionProvider` and implements `Component`.
|
||||
|
||||
The `#[function_component]` attribute is a procedural macro which automatically implements
|
||||
`FunctionProvider` for you and exposes it wrapped in `FunctionComponent`.
|
||||
|
||||
### Hooks
|
||||
|
||||
Hooks are functions that let you "hook into" components' state and/or lifecycle and perform
|
||||
actions. Yew comes with a few pre-defined Hooks. You can also create your own.
|
||||
|
||||
#### Pre-defined Hooks
|
||||
|
||||
Yew comes with the following predefined Hooks:
|
||||
- [`use_state`](./../function-components/pre-defined-hooks.md#use_state)
|
||||
- [`use_ref`](./../function-components/pre-defined-hooks.md#use_ref)
|
||||
- [`use_reducer`](./../function-components/pre-defined-hooks.md#use_reducer)
|
||||
- [`use_reducer_with_init`](./../function-components/pre-defined-hooks.md#use_reducer_with_init)
|
||||
- [`use_effect`](./../function-components/pre-defined-hooks.md#use_effect)
|
||||
- [`use_effect_with_deps`](./../function-components/pre-defined-hooks.md#use_effect_with_deps)
|
||||
|
||||
#### Custom Hooks
|
||||
|
||||
There are cases where you want to define your own Hooks for reasons. Yew allows you to define your own Hooks which lets you extract your potentially stateful logic from the component into reusable functions.
|
||||
See the [Defining custom hooks](./../function-components/custom-hooks.md#defining-custom-hooks) section for more information.
|
||||
|
||||
## Further reading
|
||||
|
||||
* The React documentation has a section on [React hooks](https://reactjs.org/docs/hooks-intro.html).
|
||||
These are not exactly the same as Yew's hooks, but the underlying concept is similar.
|
|
@ -0,0 +1,413 @@
|
|||
---
|
||||
title: "Pre-defined Hooks"
|
||||
description: "The pre-defined Hooks that Yew comes with "
|
||||
---
|
||||
|
||||
## `use_state`
|
||||
|
||||
`use_state` is used to manage state in a function component.
|
||||
It returns a `UseStateHandle` object which `Deref`s to the current value
|
||||
and provides a `set` method to update the value.
|
||||
|
||||
The hook takes a function as input which determines the initial state.
|
||||
This value remains up-to-date on subsequent renders.
|
||||
|
||||
The setter function is guaranteed to be the same across the entire
|
||||
component lifecycle. You can safely omit the `UseStateHandle` from the
|
||||
dependents of `use_effect_with_deps` if you only intend to set
|
||||
values from within the hook.
|
||||
|
||||
This hook will always trigger a re-render upon receiving a new state. See
|
||||
[`use_state_eq`](#use_state_eq) if you want the component to only
|
||||
re-render when the state changes.
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use yew::{Callback, function_component, html, use_state};
|
||||
|
||||
#[function_component(UseState)]
|
||||
fn state() -> Html {
|
||||
let counter = use_state(|| 0);
|
||||
let onclick = {
|
||||
let counter = counter.clone();
|
||||
Callback::from(move |_| counter.set(*counter + 1))
|
||||
};
|
||||
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<button {onclick}>{ "Increment value" }</button>
|
||||
<p>
|
||||
<b>{ "Current value: " }</b>
|
||||
{ *counter }
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
The value held in the handle will reflect the value at the time the
|
||||
handle is returned by the `use_state`. It is possible that the handle
|
||||
does not dereference to an up to date value if you are moving it into a
|
||||
`use_effect_with_deps` hook. You can register the
|
||||
state to the dependents so the hook can be updated when the value changes.
|
||||
|
||||
:::
|
||||
|
||||
## `use_state_eq`
|
||||
|
||||
This hook has the same effect as `use_state` but will only trigger a
|
||||
re-render when the setter receives a value that `prev_state != next_state`.
|
||||
|
||||
This hook requires the state object to implement `PartialEq`.
|
||||
|
||||
## `use_ref`
|
||||
`use_ref` is used for obtaining an immutable reference to a value.
|
||||
Its state persists across renders.
|
||||
|
||||
`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as
|
||||
you don't store a clone of the resulting `Rc` anywhere that outlives the component.
|
||||
|
||||
If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref).
|
||||
If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state).
|
||||
|
||||
```rust
|
||||
// EventBus is an implementation of yew_agent::Agent
|
||||
use website_test::agents::EventBus;
|
||||
use yew::{function_component, html, use_ref, use_state, Callback};
|
||||
use yew_agent::Bridged;
|
||||
|
||||
#[function_component(UseRef)]
|
||||
fn ref_hook() -> Html {
|
||||
let greeting = use_state(|| "No one has greeted me yet!".to_owned());
|
||||
|
||||
{
|
||||
let greeting = greeting.clone();
|
||||
use_ref(|| EventBus::bridge(Callback::from(move |msg| {
|
||||
greeting.set(msg);
|
||||
})));
|
||||
}
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<span>{ (*greeting).clone() }</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `use_mut_ref`
|
||||
`use_mut_ref` is used for obtaining a mutable reference to a value.
|
||||
Its state persists across renders.
|
||||
|
||||
It is important to note that you do not get notified of state changes.
|
||||
If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state).
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{
|
||||
events::Event,
|
||||
function_component, html, use_mut_ref, use_state,
|
||||
Callback, TargetCast,
|
||||
};
|
||||
|
||||
#[function_component(UseMutRef)]
|
||||
fn mut_ref_hook() -> Html {
|
||||
let message = use_state(|| "".to_string());
|
||||
let message_count = use_mut_ref(|| 0);
|
||||
|
||||
let onclick = Callback::from(move |_| {
|
||||
let window = gloo_utils::window();
|
||||
|
||||
if *message_count.borrow_mut() > 3 {
|
||||
window.alert_with_message("Message limit reached").unwrap();
|
||||
} else {
|
||||
*message_count.borrow_mut() += 1;
|
||||
window.alert_with_message("Message sent").unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
let onchange = {
|
||||
let message = message.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let input: HtmlInputElement = e.target_unchecked_into();
|
||||
message.set(input.value());
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<input {onchange} value={(*message).clone()} />
|
||||
<button {onclick}>{ "Send" }</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `use_node_ref`
|
||||
`use_node_ref` is used for obtaining a `NodeRef` that persists across renders.
|
||||
|
||||
When conditionally rendering elements you can use `NodeRef` in conjunction with `use_effect_with_deps`
|
||||
to perform actions each time an element is rendered and just before its going to be removed from the
|
||||
DOM.
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{
|
||||
function_component, functional::*, html,
|
||||
NodeRef
|
||||
};
|
||||
|
||||
#[function_component(UseRef)]
|
||||
pub fn ref_hook() -> Html {
|
||||
let input_ref = use_node_ref();
|
||||
let value = use_state(|| 25f64);
|
||||
|
||||
let onclick = {
|
||||
let input_ref = input_ref.clone();
|
||||
let value = value.clone();
|
||||
move |_| {
|
||||
if let Some(input) = input_ref.cast::<HtmlInputElement>() {
|
||||
value.set(*value + input.value_as_number());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<input ref={input_ref} type="number" />
|
||||
<button {onclick}>{ format!("Add input to {}", *value) }</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `use_reducer`
|
||||
|
||||
`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used
|
||||
when complex actions needs to be performed on said state.
|
||||
|
||||
It accepts an initial state function and returns a `UseReducerHandle` that dereferences to the state,
|
||||
and a dispatch function.
|
||||
The dispatch function takes one argument of type `Action`. When called, the action and current value
|
||||
are passed to the reducer function which computes a new state which is returned,
|
||||
and the component is re-rendered.
|
||||
|
||||
The dispatch function is guaranteed to be the same across the entire
|
||||
component lifecycle. You can safely omit the `UseReducerHandle` from the
|
||||
dependents of `use_effect_with_deps` if you only intend to dispatch
|
||||
values from within the hooks.
|
||||
|
||||
The state object returned by the initial state function is required to
|
||||
implement a `Reducible` trait which provides an `Action` type and a
|
||||
reducer function.
|
||||
|
||||
This hook will always trigger a re-render upon receiving an action. See
|
||||
[`use_reducer_eq`](#use_reducer_eq) if you want the component to only
|
||||
re-render when the state changes.
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// reducer's Action
|
||||
enum CounterAction {
|
||||
Double,
|
||||
Square,
|
||||
}
|
||||
|
||||
/// reducer's State
|
||||
struct CounterState {
|
||||
counter: i32,
|
||||
}
|
||||
|
||||
impl Default for CounterState {
|
||||
fn default() -> Self {
|
||||
Self { counter: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Reducible for CounterState {
|
||||
/// Reducer Action Type
|
||||
type Action = CounterAction;
|
||||
|
||||
/// Reducer Function
|
||||
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
|
||||
let next_ctr = match action {
|
||||
CounterAction::Double => self.counter * 2,
|
||||
CounterAction::Square => self.counter.pow(2)
|
||||
};
|
||||
|
||||
Self { counter: next_ctr }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(UseReducer)]
|
||||
fn reducer() -> Html {
|
||||
// The use_reducer hook takes an initialization function which will be called only once.
|
||||
let counter = use_reducer(CounterState::default);
|
||||
|
||||
let double_onclick = {
|
||||
let counter = counter.clone();
|
||||
Callback::from(move |_| counter.dispatch(CounterAction::Double))
|
||||
};
|
||||
let square_onclick = {
|
||||
let counter = counter.clone();
|
||||
Callback::from(move |_| counter.dispatch(CounterAction::Square))
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<div id="result">{ counter.counter }</div>
|
||||
|
||||
<button onclick={double_onclick}>{ "Double" }</button>
|
||||
<button onclick={square_onclick}>{ "Square" }</button>
|
||||
</>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
The value held in the handle will reflect the value of at the time the
|
||||
handle is returned by the `use_reducer`. It is possible that the handle does
|
||||
not dereference to an up to date value if you are moving it into a
|
||||
`use_effect_with_deps` hook. You can register the
|
||||
state to the dependents so the hook can be updated when the value changes.
|
||||
|
||||
:::
|
||||
|
||||
## `use_reducer_eq`
|
||||
|
||||
This hook has the same effect as `use_reducer` but will only trigger a
|
||||
re-render when the reducer function produces a value that `prev_state != next_state`.
|
||||
|
||||
This hook requires the state object to implement `PartialEq` in addition
|
||||
to the `Reducible` trait required by `use_reducer`.
|
||||
|
||||
## `use_effect`
|
||||
|
||||
`use_effect` is used for hooking into the component's lifecycle.
|
||||
Similar to `rendered` from the `Component` trait,
|
||||
`use_effect` takes a function which is called after the render finishes.
|
||||
|
||||
The input function has to return a closure, the destructor, which is called when the component is destroyed.
|
||||
The destructor can be used to clean up the effects introduced and it can take ownership of values to delay dropping them until the component is destroyed.
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use yew::{Callback, function_component, html, use_effect, use_state};
|
||||
|
||||
#[function_component(UseEffect)]
|
||||
fn effect() -> Html {
|
||||
let counter = use_state(|| 0);
|
||||
|
||||
{
|
||||
let counter = counter.clone();
|
||||
use_effect(move || {
|
||||
// Make a call to DOM API after component is rendered
|
||||
gloo_utils::document().set_title(&format!("You clicked {} times", *counter));
|
||||
|
||||
// Perform the cleanup
|
||||
|| gloo_utils::document().set_title("You clicked 0 times")
|
||||
});
|
||||
}
|
||||
let onclick = {
|
||||
let counter = counter.clone();
|
||||
Callback::from(move |_| counter.set(*counter + 1))
|
||||
};
|
||||
|
||||
html! {
|
||||
<button {onclick}>{ format!("Increment to {}", *counter) }</button>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `use_effect_with_deps`
|
||||
|
||||
Sometimes, it's needed to manually define dependencies for [`use_effect`](#use_effect). In such cases, we use `use_effect_with_deps`.
|
||||
```rust ,no_run
|
||||
use yew::use_effect_with_deps;
|
||||
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
// ...
|
||||
|| ()
|
||||
},
|
||||
(), // dependents
|
||||
);
|
||||
```
|
||||
|
||||
**Note**: `dependents` must implement `PartialEq`.
|
||||
|
||||
## `use_context`
|
||||
|
||||
`use_context` is used for consuming [contexts](../contexts.md) in function components.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
use yew::{ContextProvider, function_component, html, use_context, use_state};
|
||||
|
||||
|
||||
/// App theme
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
|
||||
/// Main component
|
||||
#[function_component(App)]
|
||||
pub fn app() -> Html {
|
||||
let ctx = use_state(|| Theme {
|
||||
foreground: "#000000".to_owned(),
|
||||
background: "#eeeeee".to_owned(),
|
||||
});
|
||||
|
||||
html! {
|
||||
// `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme`
|
||||
// so we deref it.
|
||||
// It derefs to `&Theme`, hence the clone
|
||||
<ContextProvider<Theme> context={(*ctx).clone()}>
|
||||
// Every child here and their children will have access to this context.
|
||||
<Toolbar />
|
||||
</ContextProvider<Theme>>
|
||||
}
|
||||
}
|
||||
|
||||
/// The toolbar.
|
||||
/// This component has access to the context
|
||||
#[function_component(Toolbar)]
|
||||
pub fn toolbar() -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<ThemedButton />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
/// Button placed in `Toolbar`.
|
||||
/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access to the context.
|
||||
#[function_component(ThemedButton)]
|
||||
pub fn themed_button() -> Html {
|
||||
let theme = use_context::<Theme>().expect("no ctx found");
|
||||
|
||||
html! {
|
||||
<button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
title: "Classes"
|
||||
description: "A handy macro to handle classes"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Classes
|
||||
|
||||
The struct `Classes` can be used to deal with HTML classes.
|
||||
|
||||
When pushing a string to the set, `Classes` ensures that there is one element
|
||||
for every class even if a single string might contain multiple classes.
|
||||
|
||||
`Classes` can also be merged by using `Extend` (i.e.
|
||||
`classes1.extend(classes2)`) or `push()` (i.e. `classes1.push(classes2)`). In
|
||||
fact, anything that implements `Into<Classes>` can be used to push new classes
|
||||
to the set.
|
||||
|
||||
The macro `classes!` is a convenient macro that creates one single `Classes`.
|
||||
Its input accepts a comma separated list of expressions. The only requirement
|
||||
is that every expression implements `Into<Classes>`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Literal" label="Literal">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
html! {
|
||||
<div class={classes!("container")}></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Multiple" label="Multiple">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
html! {
|
||||
<div class={classes!("class-1", "class-2")}></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="String" label="String">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
let my_classes = String::from("class-1 class-2");
|
||||
|
||||
html! {
|
||||
<div class={classes!(my_classes)}></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Optional" label="Optional">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
html! {
|
||||
<div class={classes!(Some("class"))} />
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Vector" label="Vector">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
html! {
|
||||
<div class={classes!(vec!["class-1", "class-2"])}></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Array" label="Array">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
let my_classes = ["class-1", "class-2"];
|
||||
|
||||
html! {
|
||||
<div class={classes!(my_classes.as_ref())}></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Components that accept classes
|
||||
|
||||
```rust
|
||||
use yew::{
|
||||
classes, html, Children, Classes, Component,
|
||||
Context, Html, Properties
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct Props {
|
||||
#[prop_or_default]
|
||||
class: Classes,
|
||||
fill: bool,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let Props {
|
||||
class,
|
||||
fill,
|
||||
children,
|
||||
} = &ctx.props();
|
||||
html! {
|
||||
<div
|
||||
class={classes!(
|
||||
"my-container-class",
|
||||
fill.then(|| Some("my-fill-class")),
|
||||
class.clone(),
|
||||
)}
|
||||
>
|
||||
{ children.clone() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,236 @@
|
|||
---
|
||||
title: "Components"
|
||||
description: "Create complex layouts with component hierarchies"
|
||||
---
|
||||
|
||||
## Basic
|
||||
|
||||
Any type that implements `Component` can be used in the `html!` macro:
|
||||
|
||||
```rust
|
||||
use yew::{Component, Html, html, Context, Properties};
|
||||
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
{ "This component has no properties!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
struct Props {
|
||||
prop1: String,
|
||||
prop2: String,
|
||||
}
|
||||
|
||||
struct MyComponentWithProps;
|
||||
|
||||
impl Component for MyComponentWithProps {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
{
|
||||
format!(
|
||||
"prop1: {} and prop2: {}",
|
||||
ctx.props().prop1,
|
||||
ctx.props().prop2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let props = Props {
|
||||
prop1: "Hello".to_owned(),
|
||||
prop2: "World".to_owned(),
|
||||
};
|
||||
|
||||
|
||||
html!{
|
||||
<>
|
||||
// No properties
|
||||
<MyComponent />
|
||||
|
||||
// With Properties
|
||||
<MyComponentWithProps prop1="lorem" prop2="ipsum" />
|
||||
|
||||
// With the whole set of props provided at once
|
||||
<MyComponentWithProps ..props.clone() />
|
||||
|
||||
// With Properties from a variable and specific values overridden
|
||||
<MyComponentWithProps prop2="lorem" ..props />
|
||||
</>
|
||||
};
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
Components can be passed children if they have a `children` field in their `Properties`.
|
||||
|
||||
```rust title="parent.rs"
|
||||
use yew::{Children, Component, Context, html, Html, Properties};
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct Props {
|
||||
id: String,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
struct Container;
|
||||
|
||||
impl Component for Container {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div id={ctx.props().id.clone()}>
|
||||
{ ctx.props().children.clone() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html! {
|
||||
<Container id="container">
|
||||
<h4>{ "Hi" }</h4>
|
||||
<div>{ "Hello" }</div>
|
||||
</Container>
|
||||
};
|
||||
```
|
||||
|
||||
The `html!` macro allows you to pass a base expression with the `..props` syntax instead of specifying each property individually,
|
||||
similar to Rust's [Functional Update Syntax](https://doc.rust-lang.org/stable/reference/expressions/struct-expr.html#functional-update-syntax).
|
||||
This base expression must occur after any individual props are passed.
|
||||
When passing a base props expression with a `children` field, the children passed in the `html!` macro overwrite the ones already present in the props.
|
||||
|
||||
```rust
|
||||
use yew::{Children, Component, Context, html, Html, props, Properties};
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct Props {
|
||||
id: String,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
struct Container;
|
||||
|
||||
impl Component for Container {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div id={ctx.props().id.clone()}>
|
||||
{ ctx.props().children.clone() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let props = yew::props!(Container::Properties {
|
||||
id: "container-2",
|
||||
children: Children::default(),
|
||||
});
|
||||
|
||||
html! {
|
||||
<Container ..props>
|
||||
// props.children will be overwritten with this
|
||||
<span>{ "I am a child, as you can see" }</span>
|
||||
</Container>
|
||||
};
|
||||
```
|
||||
|
||||
## Nested Children with Props
|
||||
|
||||
Nested component properties can be accessed and mutated if the containing component types its children. In the following example, the `List` component can wrap `ListItem` components. For a real world example of this pattern, check out the `yew-router` source code. For a more advanced example, check out the `nested-list` example in the main yew repository.
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct ListItemProps {
|
||||
value: String,
|
||||
}
|
||||
|
||||
pub struct ListItem;
|
||||
|
||||
impl Component for ListItem {
|
||||
type Message = ();
|
||||
type Properties = ListItemProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<span>
|
||||
{ ctx.props().value.clone() }
|
||||
</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub children: ChildrenWithProps<ListItem>,
|
||||
}
|
||||
|
||||
pub struct List;
|
||||
impl Component for List {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {{
|
||||
for ctx.props().children.iter().map(|mut item| {
|
||||
let mut props = Rc::make_mut(&mut item.props);
|
||||
props.value = format!("item-{}", props.value);
|
||||
item
|
||||
})
|
||||
}}
|
||||
}
|
||||
}
|
||||
html! {
|
||||
<List>
|
||||
<ListItem value="a" />
|
||||
<ListItem value="b" />
|
||||
<ListItem value="c" />
|
||||
</List>
|
||||
};
|
||||
```
|
||||
|
||||
## Relevant examples
|
||||
- [Boids](https://github.com/yewstack/yew/tree/master/examples/boids)
|
||||
- [Router](https://github.com/yewstack/yew/tree/master/examples/router)
|
||||
- [Store](https://github.com/yewstack/yew/tree/master/examples/store)
|
|
@ -0,0 +1,242 @@
|
|||
---
|
||||
title: "Elements"
|
||||
description: "Both HTML and SVG elements are supported"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## DOM nodes
|
||||
|
||||
There are many reasons why you might want to create or manage DOM nodes manually in Yew, such as
|
||||
when integrating with JS libraries that can cause conflicts with managed components.
|
||||
|
||||
Using `web-sys`, you can create DOM elements and convert them into a `Node` - which can then be
|
||||
used as a `Html` value using `VRef`:
|
||||
|
||||
```rust
|
||||
use web_sys::{Element, Node};
|
||||
use yew::{Component, Context, html, Html};
|
||||
use gloo_utils::document;
|
||||
|
||||
struct Comp;
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
// Create a div element from the document
|
||||
let div: Element = document().create_element("div").unwrap();
|
||||
// Add content, classes etc.
|
||||
div.set_inner_html("Hello, World!");
|
||||
// Convert Element into a Node
|
||||
let node: Node = div.into();
|
||||
// Return that Node as a Html value
|
||||
Html::VRef(node)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic tag names
|
||||
|
||||
When building a higher-order component you might find yourself in a situation where the element's tag name isn't static.
|
||||
For example, you might have a `Title` component which can render anything from `h1` to `h6` depending on a level prop.
|
||||
Instead of having to use a big match expression, Yew allows you to set the tag name dynamically
|
||||
using `@{name}` where `name` can be any expression that returns a string.
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
let level = 5;
|
||||
let text = "Hello World!".to_owned();
|
||||
|
||||
html! {
|
||||
<@{format!("h{}", level)} class="title">{ text }</@>
|
||||
};
|
||||
```
|
||||
|
||||
## Boolean Attributes
|
||||
|
||||
Some content attributes (e.g checked, hidden, required) are called boolean attributes. In Yew,
|
||||
boolean attributes need to be set to a bool value:
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<div hidden=true>
|
||||
{ "This div is hidden." }
|
||||
</div>
|
||||
};
|
||||
```
|
||||
|
||||
This will result in **HTML** that's functionally equivalent to this:
|
||||
|
||||
```html
|
||||
<div hidden>This div is hidden.</div>
|
||||
```
|
||||
|
||||
Setting a boolean attribute to false is equivalent to not using the attribute at all; values from
|
||||
boolean expressions can be used:
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
let no = 1 + 1 != 2;
|
||||
|
||||
html! {
|
||||
<div hidden={no}>
|
||||
{ "This div is NOT hidden." }
|
||||
</div>
|
||||
};
|
||||
```
|
||||
|
||||
This will result in the following **HTML**:
|
||||
|
||||
```html
|
||||
<div>This div is NOT hidden.</div>
|
||||
```
|
||||
|
||||
## Optional attributes for HTML elements
|
||||
|
||||
Most HTML attributes can use optional values (Some(x) or None). This allows us to omit the attribute if the attribute is marked as optional.
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
let maybe_id = Some("foobar");
|
||||
|
||||
html! {
|
||||
<div id={maybe_id}></div>
|
||||
};
|
||||
```
|
||||
|
||||
If the attribute is set to `None`, the attribute won't be set in the DOM.
|
||||
|
||||
## Listeners
|
||||
|
||||
Listener attributes need to be passed a `Callback` which is a wrapper around a closure. How you create your callback depends on how you wish your app to react to a listener event:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Component handler" label="Component handler">
|
||||
|
||||
```rust
|
||||
use yew::{Component, Context, html, Html};
|
||||
|
||||
struct MyComponent;
|
||||
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::Click => {
|
||||
// Handle Click
|
||||
}
|
||||
};
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// Create a callback from a component link to handle it in a component
|
||||
let click_callback = ctx.link().callback(|_| Msg::Click);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Agent Handler" label="Agent Handler">
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html};
|
||||
use yew_agent::{Dispatcher, Dispatched};
|
||||
use website_test::agents::{MyWorker, WorkerMsg};
|
||||
|
||||
struct MyComponent {
|
||||
worker: Dispatcher<MyWorker>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = WorkerMsg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
MyComponent {
|
||||
worker: MyWorker::dispatcher(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
self.worker.send(msg);
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// Create a callback from a worker to handle it in another context
|
||||
let click_callback = ctx.link().callback(|_| WorkerMsg::Process);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Other Cases" label="Other Cases">
|
||||
|
||||
```rust
|
||||
use yew::{Callback, Context, Component, html, Html};
|
||||
use weblog::console_log;
|
||||
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
MyComponent
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
// Create an ephemeral callback
|
||||
let click_callback = Callback::from(|_| {
|
||||
console_log!("clicked!");
|
||||
});
|
||||
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Relevant examples
|
||||
|
||||
- [Inner HTML](https://github.com/yewstack/yew/tree/master/examples/inner_html)
|
|
@ -0,0 +1,696 @@
|
|||
---
|
||||
title: "Events"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Yew integrates with the [`web-sys`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/) crate and
|
||||
uses the events from that crate. The [table below](#event-types) lists all of the `web-sys`
|
||||
events that are accepted in the `html!` macro.
|
||||
|
||||
You can still add a [`Callback`](../components/callbacks.md) for an event that is not listed in the table
|
||||
below, see [Manual event listener](#manual-event-listener).
|
||||
|
||||
## Event Types
|
||||
|
||||
:::tip
|
||||
All the event types mentioned in the following table are re-exported under `yew::events`.
|
||||
Using the types from `yew::events` makes it easier to ensure version compatibility than
|
||||
if you were to manually include `web-sys` as a dependency in your crate because you won't
|
||||
end up using a version which conflicts with the version that Yew specifies.
|
||||
:::
|
||||
|
||||
The event listener name is the expected name when adding an event `Callback` in the `html` macro:
|
||||
|
||||
```rust
|
||||
use yew::{html, Callback};
|
||||
|
||||
html! {
|
||||
<button onclick={Callback::from(|_| ())}>
|
||||
// ^^^^^^^ event listener name
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
};
|
||||
```
|
||||
|
||||
The event name is the listener without the "on" prefix, therefore, the `onclick` event listener
|
||||
listens for `click` events.
|
||||
|
||||
| Event listener name | `web_sys` Event Type |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) |
|
||||
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `onfocusin` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `onfocusout` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oninput` | [InputEvent](https://docs.rs/web-sys/latest/web_sys/struct.InputEvent.html) |
|
||||
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
|
||||
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
|
||||
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
|
||||
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
|
||||
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
|
||||
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) |
|
||||
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
|
||||
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
|
||||
## Typed event target
|
||||
|
||||
:::caution
|
||||
In this section **target ([Event.target](https://developer.mozilla.org/en-US/docs/Web/API/Event/target))**
|
||||
is always referring to the element at which the event was dispatched from.
|
||||
|
||||
|
||||
This will **not** always be the element at which the `Callback` is placed.
|
||||
:::
|
||||
|
||||
In event `Callback`s you may want to get the target of that event. For example, the
|
||||
`change` event gives no information but is used to notify that something has changed.
|
||||
|
||||
In Yew getting the target element in the correct type can be done in a few ways and we will go through
|
||||
them here. Calling [`web_sys::Event::target`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html#method.target)
|
||||
on an event returns an optional [`web_sys::EventTarget`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.EventTarget.html)
|
||||
type, which might not seem very useful when you want to know the value of your input element.
|
||||
|
||||
In all the approaches below we are going to tackle the same problem, so it's clear where the approach
|
||||
differs opposed to the problem at hand.
|
||||
|
||||
**The Problem:**
|
||||
|
||||
We have an `onchange` `Callback` on my `<input>` element and each time it is invoked we want to send
|
||||
an [update](../components#update) `Msg` to our component.
|
||||
|
||||
Our `Msg` enum looks like this:
|
||||
|
||||
```rust
|
||||
pub enum Msg {
|
||||
InputValue(String),
|
||||
}
|
||||
```
|
||||
|
||||
### Using `JsCast`
|
||||
|
||||
The [`wasm-bindgen`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html) crate has
|
||||
a useful trait; [`JsCast`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)
|
||||
which allows us to hop and skip our way to the type we want, as long as it implements `JsCast`. We can
|
||||
do this cautiously, which involves some runtime checks and failure types like `Option` and `Result`,
|
||||
or we can do it dangerously.
|
||||
|
||||
Enough talk, more code:
|
||||
|
||||
```toml title="Cargo.toml"
|
||||
[dependencies]
|
||||
# need wasm-bindgen for JsCast
|
||||
wasm-bindgen = "0.2"
|
||||
```
|
||||
|
||||
```rust
|
||||
//highlight-next-line
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{EventTarget, HtmlInputElement};
|
||||
use yew::{
|
||||
events::Event,
|
||||
html,
|
||||
Component, Context, Html,
|
||||
};
|
||||
|
||||
pub struct Comp;
|
||||
|
||||
pub enum Msg {
|
||||
InputValue(String),
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
|
||||
// Use batch_callback so if something unexpected happens we can return
|
||||
// None and do nothing
|
||||
let on_cautious_change = link.batch_callback(|e: Event| {
|
||||
// When events are created the target is undefined, it's only
|
||||
// when dispatched does the target get added.
|
||||
let target: Option<EventTarget> = e.target();
|
||||
// Events can bubble so this listener might catch events from child
|
||||
// elements which are not of type HtmlInputElement
|
||||
//highlight-next-line
|
||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||
|
||||
input.map(|input| Msg::InputValue(input.value()))
|
||||
});
|
||||
|
||||
let on_dangerous_change = link.callback(|e: Event| {
|
||||
let target: EventTarget = e
|
||||
.target()
|
||||
.expect("Event should have a target when dispatched");
|
||||
// You must KNOW target is a HtmlInputElement, otherwise
|
||||
// the call to value would be Undefined Behaviour (UB).
|
||||
//highlight-next-line
|
||||
Msg::InputValue(target.unchecked_into::<HtmlInputElement>().value())
|
||||
});
|
||||
|
||||
html! {
|
||||
<>
|
||||
<label for="cautious-input">
|
||||
{ "My cautious input:" }
|
||||
<input onchange={on_cautious_change}
|
||||
id="cautious-input"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
<label for="dangerous-input">
|
||||
{ "My dangerous input:" }
|
||||
<input onchange={on_dangerous_change}
|
||||
id="dangerous-input"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
Use `batch_callback` when you want to conditionally return a value from a `Callback`.
|
||||
:::
|
||||
|
||||
The methods from `JsCast` are [`dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into)
|
||||
and [`unchecked_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.unchecked_into)
|
||||
and you can probably see, they allowed
|
||||
us to go from `EventTarget` to [`HtmlInputElement`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.HtmlInputElement.html).
|
||||
The `dyn_into` method is cautious because at
|
||||
runtime it will check whether the type is actually a `HtmlInputElement` and if not return an
|
||||
`Err(JsValue)`, the [`JsValue`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html)
|
||||
is a catch-all type and is essentially giving you back the object to try again.
|
||||
|
||||
At this point you might be thinking... when is the dangerous version ok to use? In the case above it
|
||||
is safe<sup>1</sup> as we've set the `Callback` on to an element with no children so the target can
|
||||
only be that same element.
|
||||
|
||||
|
||||
_<sup>1</sup> As safe as anything can be when JS land is involved._
|
||||
|
||||
### Using `TargetCast`
|
||||
|
||||
**It is highly recommended to read [Using JsCast](#using-jscast) first!**
|
||||
|
||||
:::note
|
||||
`TargetCast` was designed to feel very similar to `JsCast` - this is to allow new users to get a feel
|
||||
for the behaviour of `JsCast` but with the smaller scope of events and their targets.
|
||||
|
||||
`TargetCast` vs `JsCast` is purely preference, you will find that `TargetCast` implements something
|
||||
similar to what you would using `JsCast`.
|
||||
:::
|
||||
|
||||
The `TargetCast` trait builds off of `JsCast` and is specialized towards getting typed event targets
|
||||
from events.
|
||||
|
||||
`TargetCast` comes with Yew so no need to add a dependency in order to use the trait methods on events
|
||||
but it works in a very similar way to `JsCast`.
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{
|
||||
events::Event,
|
||||
html,
|
||||
// Need to import TargetCast
|
||||
//highlight-next-line
|
||||
Component, Context, Html, TargetCast,
|
||||
};
|
||||
|
||||
pub struct Comp;
|
||||
|
||||
pub enum Msg {
|
||||
InputValue(String),
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
|
||||
let on_cautious_change = link.batch_callback(|e: Event| {
|
||||
//highlight-next-line
|
||||
let input = e.target_dyn_into::<HtmlInputElement>();
|
||||
|
||||
input.map(|input| Msg::InputValue(input.value()))
|
||||
});
|
||||
|
||||
let on_dangerous_change = link.callback(|e: Event| {
|
||||
// You must KNOW target is a HtmlInputElement, otherwise
|
||||
// the call to value would be Undefined Behaviour (UB).
|
||||
//highlight-next-line
|
||||
Msg::InputValue(e.target_unchecked_into::<HtmlInputElement>().value())
|
||||
});
|
||||
|
||||
html! {
|
||||
<>
|
||||
<label for="cautious-input">
|
||||
{ "My cautious input:" }
|
||||
<input onchange={on_cautious_change}
|
||||
id="cautious-input"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
<label for="dangerous-input">
|
||||
{ "My dangerous input:" }
|
||||
<input onchange={on_dangerous_change}
|
||||
id="dangerous-input"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
If you followed the advice above and read about `JsCast`, or know the trait, you can probably
|
||||
see that `TargetCast::target_dyn_into` feels similar to `JsCast::dyn_into` but specifically
|
||||
does the cast on the target of the event. `TargetCast::target_unchecked_into` is similar to
|
||||
`JsCast::unchecked_into`, and as such all the same warnings above `JsCast` apply to `TargetCast`.
|
||||
|
||||
|
||||
### Using `NodeRef`
|
||||
|
||||
[`NodeRef`](../components/refs.md) can be used instead of querying the event given to a `Callback`.
|
||||
|
||||
```rust
|
||||
//highlight-start
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{html, Component, Context, Html, NodeRef};
|
||||
//highlight-end
|
||||
|
||||
pub struct Comp {
|
||||
//highlight-next-line
|
||||
my_input: NodeRef,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
InputValue(String),
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self {
|
||||
//highlight-next-line
|
||||
my_input: NodeRef::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
|
||||
let my_input_ref = self.my_input.clone();
|
||||
|
||||
let onchange = link.batch_callback(move |_| {
|
||||
//highlight-next-line
|
||||
let input = my_input_ref.cast::<HtmlInputElement>();
|
||||
|
||||
input.map(|input| Msg::InputValue(input.value()))
|
||||
});
|
||||
|
||||
html! {
|
||||
<>
|
||||
<label for="my-input">
|
||||
{ "My input:" }
|
||||
//highlight-next-line
|
||||
<input ref={self.my_input.clone()}
|
||||
{onchange}
|
||||
id="my-input"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Using `NodeRef`, you can ignore the event and use the `NodeRef::cast` method to get an
|
||||
`Option<HtmlInputElement>` - this is optional as calling `cast` before the `NodeRef` has been
|
||||
set, or when the type doesn't match will return `None`.
|
||||
|
||||
You might also see by using `NodeRef` we don't have to send the `String` back in the
|
||||
`Msg::InputValue` as we always have `my_input` in the component state - so we could do the following:
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{html, Component, Context, Html, NodeRef};
|
||||
|
||||
pub struct Comp {
|
||||
my_input: NodeRef,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
// Signal the input element has changed
|
||||
//highlight-next-line
|
||||
InputChanged,
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self {
|
||||
my_input: NodeRef::default(),
|
||||
}
|
||||
}
|
||||
|
||||
//highlight-start
|
||||
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::InputChanged => {
|
||||
if let Some(input) = self.my_input.cast::<HtmlInputElement>() {
|
||||
let value = input.value();
|
||||
// do something with value
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//highlight-end
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
//highlight-next-line
|
||||
let onchange = link.callback(|_| Msg::InputChanged);
|
||||
|
||||
html! {
|
||||
<label for="my-input">
|
||||
{ "My input:" }
|
||||
<input ref={self.my_input.clone()}
|
||||
{onchange}
|
||||
id="my-input"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Which approach you take depends on your component and your preferences, there is no _blessed_ way
|
||||
per se.
|
||||
|
||||
## Manual event listener
|
||||
|
||||
You may want to listen to an event that is not supported by Yew's `html` macro, see the
|
||||
[supported events listed here](#event-types).
|
||||
|
||||
In order to add an event listener to one of elements manually we need the help of
|
||||
[`NodeRef`](../components/refs) so that in the `rendered` method we can add a listener using the
|
||||
[`web-sys`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html) and
|
||||
[wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html) API.
|
||||
We do this in `rendered` as this is the only time we can guarantee that the element exists in
|
||||
the browser, Yew needs some time to create them after `view` is called.
|
||||
|
||||
The examples below are going to show adding listeners for the made-up `custard` event. All events
|
||||
either unsupported by yew or custom can be represented as a
|
||||
[`web_sys::Event`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html). If you
|
||||
need to access a specific method or field on a custom / unsupported event then you can use the
|
||||
methods of [`JsCast`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)
|
||||
in order to convert to the type required.
|
||||
|
||||
### Using `Closure` (verbose)
|
||||
|
||||
Using the `web-sys` and `wasm-bindgen` API's directly for this can be a bit painful.. so brace
|
||||
yourself ([there is a more concise way thanks to `gloo`](#using-gloo-concise)).
|
||||
|
||||
```rust
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
use web_sys::HtmlElement;
|
||||
use yew::{
|
||||
events::Event,
|
||||
html,
|
||||
Component, Context, Html, NodeRef,
|
||||
};
|
||||
|
||||
pub struct Comp {
|
||||
my_div: NodeRef,
|
||||
custard_listener: Option<Closure<dyn Fn(Event)>>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Custard,
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self {
|
||||
my_div: NodeRef::default(),
|
||||
custard_listener: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::Custard => {
|
||||
// do something about custard..
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div ref={self.my_div.clone()} id="my-div"></div>
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
|
||||
if !first_render {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(element) = self.my_div.cast::<HtmlElement>() {
|
||||
// Create your Callback as you normally would
|
||||
let oncustard = ctx.link().callback(|_: Event| Msg::Custard);
|
||||
|
||||
// Create a Closure from a Box<dyn Fn> - this has to be 'static
|
||||
let listener =
|
||||
Closure::<dyn Fn(Event)>::wrap(
|
||||
Box::new(move |e: Event| oncustard.emit(e))
|
||||
);
|
||||
element
|
||||
.add_event_listener_with_callback(
|
||||
"custard",
|
||||
listener.as_ref().unchecked_ref()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Need to save listener in the component otherwise when the
|
||||
// event is fired it will try and call the listener that no longer
|
||||
// exists in memory!
|
||||
self.custard_listener = Some(listener);
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy(&mut self, _: &Context<Self>) {
|
||||
// All done with the component but need to remove
|
||||
// the event listener before the custard_listener memory
|
||||
// goes out of scope.
|
||||
if let (Some(element), Some(listener)) = (
|
||||
self.my_div.cast::<HtmlElement>(),
|
||||
self.custard_listener.take(),
|
||||
) {
|
||||
element
|
||||
.remove_event_listener_with_callback(
|
||||
"custard",
|
||||
listener.as_ref().unchecked_ref()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more information on `Closures`, see
|
||||
[The `wasm-bindgen` Guide](https://rustwasm.github.io/wasm-bindgen/examples/closures.html).
|
||||
|
||||
### Using `gloo` (concise)
|
||||
|
||||
The easier way is with `gloo`, more specifically [`gloo_events`](https://docs.rs/gloo-events/0.1.1/gloo_events/index.html)
|
||||
which is an abstraction for `web-sys`, `wasm-bindgen`.
|
||||
|
||||
`gloo_events` has the `EventListener` type which can be used to create and store the
|
||||
event listener.
|
||||
|
||||
```toml title="Cargo.toml"
|
||||
[dependencies]
|
||||
gloo-events = "0.1"
|
||||
```
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlElement;
|
||||
use yew::{
|
||||
events::Event,
|
||||
html,
|
||||
Component, Context, Html, NodeRef,
|
||||
};
|
||||
|
||||
use gloo_events::EventListener;
|
||||
|
||||
pub struct Comp {
|
||||
my_div: NodeRef,
|
||||
custard_listener: Option<EventListener>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Custard,
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self {
|
||||
my_div: NodeRef::default(),
|
||||
custard_listener: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::Custard => {
|
||||
// do something about custard..
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div ref={self.my_div.clone()} id="my-div"></div>
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
|
||||
if !first_render {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(element) = self.my_div.cast::<HtmlElement>() {
|
||||
// Create your Callback as you normally would
|
||||
let oncustard = ctx.link().callback(|_: Event| Msg::Custard);
|
||||
|
||||
let listener = EventListener::new(
|
||||
&element,
|
||||
"custard",
|
||||
move |e| oncustard.emit(e.clone())
|
||||
);
|
||||
|
||||
self.custard_listener = Some(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that when using an `EventListener` you don't need to do anything when the
|
||||
component is about to be destroyed as the `EventListener` has a `drop` implementation
|
||||
which will remove the event listener from the element.
|
||||
|
||||
For more information on `EventListener`, see the
|
||||
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: "Fragments"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
The `html!` macro always requires a single root node. In order to get around this restriction, you
|
||||
can use an "empty tag" (these are also called "fragments").
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Valid" label="Valid">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<>
|
||||
<div></div>
|
||||
<p></p>
|
||||
</>
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="Invalid" label="Invalid">
|
||||
|
||||
```rust, compile_fail
|
||||
use yew::html;
|
||||
|
||||
// error: only one root html element allowed
|
||||
|
||||
html! {
|
||||
<div></div>
|
||||
<p></p>
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
|
@ -0,0 +1,230 @@
|
|||
---
|
||||
title: "HTML"
|
||||
sidebar_label: Introduction
|
||||
description: "The procedural macro for generating HTML and SVG"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
The `html!` macro allows you to write HTML and SVG code declaratively. It is similar to JSX
|
||||
\(an extension to JavaScript which allows you to write HTML-like code inside of JavaScript\).
|
||||
|
||||
**Important notes**
|
||||
|
||||
1. The `html!` macro only accepts one root html node \(you can counteract this by
|
||||
[using fragments or iterators](./../html/lists.md)\)
|
||||
2. An empty `html! {}` invocation is valid and will not render anything
|
||||
3. Literals must always be quoted and wrapped in braces: `html! { "Hello, World" }`
|
||||
|
||||
:::note
|
||||
The `html!` macro can reach the default recursion limit of the compiler. If you encounter compilation errors, add an attribute like `#![recursion_limit="1024"]` in the crate root to overcome the problem.
|
||||
:::
|
||||
|
||||
## Tag Structure
|
||||
|
||||
Tags are based on HTML tags. Components, Elements, and Lists are all based on this tag syntax.
|
||||
|
||||
Tags must either self-close `<... />` or have a corresponding end tag for each start tag.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Open - Close" label="Open - Close" default>
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<div id="my_div"></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Invalid" label="Invalid">
|
||||
|
||||
```rust ,compile_fail
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<div id="my_div"> // <- MISSING CLOSE TAG
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Self-closing" label="Self-closing">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<input id="my_input" />
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Invalid" label="Invalid">
|
||||
|
||||
```rust ,compile_fail
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<input id="my_input"> // <- MISSING SELF-CLOSE
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
For convenience, elements which _usually_ require a closing tag are **allowed** to self-close. For example, writing `html! { <div class="placeholder" /> }` is valid.
|
||||
:::
|
||||
|
||||
## Children
|
||||
|
||||
Create complex nested HTML and SVG layouts with ease:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="HTML" label="HTML">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<div data-key="abc"></div>
|
||||
<div class="parent">
|
||||
<span class="child" value="anything"></span>
|
||||
<label for="first-name">{ "First Name" }</label>
|
||||
<input type="text" id="first-name" value="placeholder" />
|
||||
<input type="checkbox" checked=true />
|
||||
<textarea value="write a story" />
|
||||
<select name="status">
|
||||
<option selected=true disabled=false value="">{ "Selected" }</option>
|
||||
<option selected=false disabled=true value="">{ "Unselected" }</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="SVG" label="SVG">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
|
||||
<path d="M108.361 94.9937L138.708 90.686L115.342 69.8642" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<circle cx="75.3326" cy="73.4918" r="55" fill="#FDD630"/>
|
||||
<circle cx="75.3326" cy="73.4918" r="52.5" stroke="black" stroke-width="5"/>
|
||||
</g>
|
||||
<circle cx="71" cy="99" r="5" fill="white" fill-opacity="0.75" stroke="black" stroke-width="3"/>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="16.3326" y="18.4918" width="118" height="118" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Lints
|
||||
|
||||
If you compile Yew using a nightly version of the Rust compiler, the macro will warn you about some
|
||||
common pitfalls that you might run into. Of course, you may need to use the stable compiler (e.g.
|
||||
your organization might have a policy mandating it) for release builds, but even if you're using a
|
||||
stable toolchain, running `cargo +nightly check` might flag some ways that you could improve your
|
||||
HTML code.
|
||||
|
||||
At the moment the lints are mostly accessibility-related. If you have ideas for lints, please feel
|
||||
free to [chime in on this issue](https://github.com/yewstack/yew/issues/1334).
|
||||
|
||||
## Special properties
|
||||
|
||||
There are special properties which don't directly influence the DOM but instead act as instructions to Yew's virtual DOM.
|
||||
Currently, there are two such special props: `ref` and `key`.
|
||||
|
||||
`ref` allows you to access and manipulate the underlying DOM node directly. See [Refs](components/refs) for more details.
|
||||
|
||||
`key` on the other hand gives an element a unique identifier which Yew can use for optimization purposes.
|
||||
|
||||
:::important
|
||||
The documentation for keys is yet to be written. See [#1263](https://github.com/yewstack/yew/issues/1263).
|
||||
|
||||
For now, use keys when you have a list where the order of elements changes. This includes inserting or removing elements from anywhere but the end of the list.
|
||||
:::
|
||||
|
||||
## If blocks
|
||||
|
||||
To conditionally render some markup, we wrap it in an `if` block:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="if" label="if">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
html! {
|
||||
if true {
|
||||
<p>{ "True case" }</p>
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="if - else" label="if - else">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
let some_condition = true;
|
||||
|
||||
html! {
|
||||
if false {
|
||||
<p>{ "True case" }</p>
|
||||
} else {
|
||||
<p>{ "False case" }</p>
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="if let" label="if let">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
let some_text = Some("text");
|
||||
|
||||
html! {
|
||||
if let Some(text) = some_text {
|
||||
<p>{ text }</p>
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="if let else" label="if let else">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
let some_text = Some("text");
|
||||
|
||||
html! {
|
||||
if let Some(text) = some_text {
|
||||
<p>{ text }</p>
|
||||
} else {
|
||||
<p>{ "False case" }</p>
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
|
@ -0,0 +1,124 @@
|
|||
---
|
||||
title: "Lists"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Iterators
|
||||
|
||||
Yew supports two different syntaxes for building HTML from an iterator.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Syntax type 1" label="Syntax type 1">
|
||||
|
||||
The first is to call `collect::<Html>()` on the final transform in your iterator, which returns a
|
||||
list that Yew can display.
|
||||
|
||||
```rust
|
||||
use yew::{html, Html};
|
||||
|
||||
let items = (1..=10).collect::<Vec<_>>();
|
||||
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ items.iter().collect::<Html>() }
|
||||
</ul>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Syntax type 2" label="Syntax type 2">
|
||||
|
||||
The alternative is to use the `for` keyword, which is not native Rust syntax and instead is used by
|
||||
the HTML macro to output the needed code to display the iterator.
|
||||
|
||||
```rust
|
||||
use yew::{html};
|
||||
|
||||
let items = (1..=10).collect::<Vec<_>>();
|
||||
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ for items.iter() }
|
||||
</ul>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Keyed lists
|
||||
|
||||
A keyed list is a optimized list that has keys on all tags.
|
||||
`key` is a special prop provided by Yew which gives an html element a unique identifier which can be used for optimization purposes.
|
||||
|
||||
:::caution
|
||||
|
||||
Key has to be unique and must not depend on the order of the list.
|
||||
|
||||
:::
|
||||
|
||||
It is always recommended to add keys to lists.
|
||||
|
||||
Keys can be added by passing a unique value to the special `key` prop:
|
||||
|
||||
```rust , ignore
|
||||
use yew::html;
|
||||
|
||||
let names = vec!["Sam","Bob","Ray"]
|
||||
|
||||
html! {
|
||||
<div id="introductions">
|
||||
{
|
||||
names.into_iter().map(|name| {
|
||||
html!{<div key={name}>{ format!("Hello, I'am {}!",name) }</div>}
|
||||
}).collect::<Html>()
|
||||
}
|
||||
</div>
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Performance increases
|
||||
|
||||
We have [Keyed list](https://github.com/yewstack/yew/tree/master/examples/keyed_list) example that lets you test the performance improvements, but here is rough example of testing:
|
||||
|
||||
1. Go to [Keyed list](https://github.com/yewstack/yew/tree/master/examples/keyed_list) hosted demo
|
||||
2. Add 500 elements.
|
||||
3. Disable keys.
|
||||
4. Reverse the list.
|
||||
5. Look at "The last rendering took Xms" (At the time of writing this it was ~60ms)
|
||||
6. Enable keys.
|
||||
7. Reverse the list.
|
||||
8. Look at "The last rendering took Xms" (At the time of writing this it was ~30ms)
|
||||
|
||||
So just at the time of writing this, for 500 components its a x2 increase of speed.
|
||||
|
||||
### Detailed explanation
|
||||
|
||||
Usually you just need a key on every list item when you iterate and the order of data can change.
|
||||
So lets say you iterate through `["bob","sam","rob"]` ended up with html:
|
||||
|
||||
```html
|
||||
<div id="bob">My name is Bob</div>
|
||||
<div id="sam">My name is Sam</div>
|
||||
<div id="rob">My name is rob</div>
|
||||
```
|
||||
|
||||
Then if your list changed to `["bob","rob"]`,
|
||||
yew would delete from previous html element with id="rob" and update id="sam" to be id="rob"
|
||||
|
||||
Now if you had added a key to each element, html would stay the same, but in case where it changed to `["bob","rob"]`, yew would just delete the second html element since it knows which one it is.
|
||||
|
||||
Keys also help for weird cases where yew reuses html elements.
|
||||
|
||||
If you ever encounter a bug/"feature" where you switch from one component to another but both have a div as the highest rendered element.
|
||||
Yew reuses the rendered html div in those cases as an optimization.
|
||||
If you need that div to be recreated instead of reused, then you can add different keys and they wont be reused
|
||||
|
||||
## Further reading
|
||||
|
||||
- [TodoMVC](https://github.com/yewstack/yew/tree/master/examples/todomvc)
|
||||
- [Keyed list](https://github.com/yewstack/yew/tree/master/examples/keyed_list)
|
||||
- [Router](https://github.com/yewstack/yew/tree/master/examples/router)
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: "Literals and Expressions"
|
||||
---
|
||||
## Literals
|
||||
|
||||
If expressions resolve to types that implement `Display`, they will be converted to strings and inserted into the DOM as a [Text](https://developer.mozilla.org/en-US/docs/Web/API/Text) node.
|
||||
|
||||
All display text must be enclosed by `{}` blocks because text is handled as an expression. This is
|
||||
the largest deviation from normal HTML syntax that Yew makes.
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
let text = "lorem ipsum";
|
||||
html!{
|
||||
<>
|
||||
<div>{text}</div>
|
||||
<div>{"dolor sit"}</div>
|
||||
<span>{42}</span>
|
||||
</>
|
||||
};
|
||||
```
|
||||
|
||||
## Expressions
|
||||
|
||||
You can insert expressions in your HTML using `{}` blocks, as long as they resolve to `Html`
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
let show_link = true;
|
||||
|
||||
html! {
|
||||
<div>
|
||||
{
|
||||
if show_link {
|
||||
html! {
|
||||
<a href="https://example.com">{"Link"}</a>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
};
|
||||
```
|
||||
|
||||
It often makes sense to extract these expressions into functions or closures to optimize for readability:
|
||||
|
||||
```rust
|
||||
use yew::{html, Html};
|
||||
|
||||
let show_link = true;
|
||||
let maybe_display_link = move || -> Html {
|
||||
if show_link {
|
||||
html! {
|
||||
<a href="https://example.com">{"Link"}</a>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>{maybe_display_link()}</div>
|
||||
};
|
||||
```
|
|
@ -0,0 +1,478 @@
|
|||
---
|
||||
title: "Router"
|
||||
description: "Yew's official router"
|
||||
---
|
||||
|
||||
Routers in Single Page Applications (SPA) handle displaying different pages depending on what the URL is. Instead of the
|
||||
default behavior of requesting a different remote resource when a link is clicked, the router instead sets the URL
|
||||
locally to point to a valid route in your application. The router then detects this change and then decides what to
|
||||
render.
|
||||
|
||||
Yew provides router support in the `yew-router` crate. To start using it, add the dependency to your `Cargo.toml`
|
||||
|
||||
<!-- Reminder: fix this when we release a new version of yew -->
|
||||
|
||||
```toml
|
||||
yew-router = { git = "https://github.com/yewstack/yew.git" }
|
||||
```
|
||||
|
||||
The utilities needed are provided under `yew_router::prelude`,
|
||||
|
||||
## Usage
|
||||
|
||||
You start by defining a `Route`.
|
||||
|
||||
Routes are defined as an `enum` which derives `Routable`. This enum must be `Clone + PartialEq`.
|
||||
|
||||
```rust
|
||||
use yew_router::prelude::*;
|
||||
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum Route {
|
||||
#[at("/")]
|
||||
Home,
|
||||
#[at("/secure")]
|
||||
Secure,
|
||||
#[not_found]
|
||||
#[at("/404")]
|
||||
NotFound,
|
||||
}
|
||||
```
|
||||
|
||||
A `Route` is paired with a `<Switch />` component, which finds the variant whose path matches the browser's
|
||||
current URL and passes it to the `render` callback. The callback then decides what to render. In case no path is
|
||||
matched, the router navigates to the path with `not_found` attribute. If no route is specified, nothing is rendered, and
|
||||
a message is logged to console stating that no route was matched.
|
||||
|
||||
Finally, you need to register the `<Router />` component as a context.
|
||||
`<Router />` provides session history information to its children.
|
||||
|
||||
When using `yew-router` in browser environment, `<BrowserRouter />` is recommended.
|
||||
|
||||
```rust
|
||||
use yew_router::prelude::*;
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum Route {
|
||||
#[at("/")]
|
||||
Home,
|
||||
#[at("/secure")]
|
||||
Secure,
|
||||
#[not_found]
|
||||
#[at("/404")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[function_component(Secure)]
|
||||
fn secure() -> Html {
|
||||
let history = use_history().unwrap();
|
||||
|
||||
let onclick = Callback::once(move |_| history.push(Route::Home));
|
||||
html! {
|
||||
<div>
|
||||
<h1>{ "Secure" }</h1>
|
||||
<button {onclick}>{ "Go Home" }</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
fn switch(routes: &Route) -> Html {
|
||||
match routes {
|
||||
Route::Home => html! { <h1>{ "Home" }</h1> },
|
||||
Route::Secure => html! {
|
||||
<Secure />
|
||||
},
|
||||
Route::NotFound => html! { <h1>{ "404" }</h1> },
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(Main)]
|
||||
fn app() -> Html {
|
||||
html! {
|
||||
<BrowserRouter>
|
||||
<Switch<Route> render={Switch::render(switch)} />
|
||||
</BrowserRouter>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Path Segments
|
||||
|
||||
It is also possible to extract information from a route.
|
||||
|
||||
```rust
|
||||
use yew_router::prelude::*;
|
||||
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum Route {
|
||||
#[at("/")]
|
||||
Home,
|
||||
#[at("/post/:id")]
|
||||
Post { id: String },
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can then access the post's id inside `<Switch />` and forward it to the appropriate component via properties.
|
||||
|
||||
```rust ,ignore
|
||||
fn switch(routes: &Route) -> Html {
|
||||
match routes {
|
||||
Route::Home => html! { <h1>{ "Home" }</h1> },
|
||||
Route::Post { id } => <Post {id} />,
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
You can have a normal `Post` variant instead of `Post {id: String}` too. For example when `Post` is rendered
|
||||
with another router, the field can then be redundant as the other router is able to match and handle the path. See the
|
||||
[Nested Router](#nested-router) section below for details
|
||||
:::
|
||||
|
||||
Note the fields must implement `Clone + PartialEq` as part of the `Route` enum. They must also implement
|
||||
`std::fmt::Display` and `std::str::FromStr` for serialization and deserialization. Primitive types like integer, float,
|
||||
and String already satisfy the requirements.
|
||||
|
||||
In case when the form of the path matches, but the deserialization fails (as per `FromStr`). The router will consider
|
||||
the route as unmatched and try to render the not found route (or a blank page if the not found route is unspecified).
|
||||
|
||||
Consider this example:
|
||||
|
||||
```rust ,ignore
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum Route {
|
||||
#[at("/news/:id")]
|
||||
News { id: u8 },
|
||||
#[not_found]
|
||||
#[at("/404")]
|
||||
NotFound,
|
||||
}
|
||||
// switch function renders News and id as is. Omitted here.
|
||||
```
|
||||
|
||||
When the segment goes over 255, `u8::from_str()` fails with `ParseIntError`, the router will then consider the route
|
||||
unmatched.
|
||||
|
||||

|
||||
|
||||
For more information about the route syntax and how to bind parameters, check
|
||||
out [route-recognizer](https://docs.rs/route-recognizer/0.3.1/route_recognizer/#routing-params).
|
||||
|
||||
### History and Location
|
||||
|
||||
The router provides a universal `History` and `Location` struct which can be used to access routing information. They
|
||||
can be retrieved by hooks or convenient functions on `ctx.link()`.
|
||||
|
||||
They have a couple flavours:
|
||||
|
||||
#### `AnyHistory` and `AnyLocation`
|
||||
|
||||
These types are available with all routers and should be used whenever possible. They implement a subset
|
||||
of `window.history` and `window.location`.
|
||||
|
||||
You can access them using the following hooks:
|
||||
|
||||
- `use_history`
|
||||
- `use_location`
|
||||
|
||||
#### `BrowserHistory` and `BrowserLocation`
|
||||
|
||||
These are only available when `<BrowserRouter />` is used. They provide additional functionality that is not available
|
||||
in `AnyHistory` and
|
||||
`AnyLocation` (such as: `location.host`).
|
||||
|
||||
### Navigation
|
||||
|
||||
`yew_router` provides a handful of tools to work with navigation.
|
||||
|
||||
#### Link
|
||||
|
||||
A `<Link/>` renders as an `<a>` element, the `onclick` event handler will call
|
||||
[preventDefault](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault), and push the targeted page to the
|
||||
history and render the desired page, which is what should be expected from a Single Page App. The default onclick of a
|
||||
normal anchor element would reload the page.
|
||||
|
||||
The `<Link/>` element also passes its children to the `<a>` element. Consider it a replacement of `<a/>` for in-app
|
||||
routes. Except you supply a `to` attribute instead of a `href`. An example usage:
|
||||
|
||||
```rust ,ignore
|
||||
<Link<Route> to={Route::Home}>{ "click here to go home" }</Link<Route>>
|
||||
```
|
||||
|
||||
Struct variants work as expected too:
|
||||
|
||||
```rust ,ignore
|
||||
<Link<Route> to={Route::Post { id: "new-yew-release".to_string() }}>{ "Yew v0.19 out now!" }</Link<Route>>
|
||||
```
|
||||
|
||||
#### History API
|
||||
|
||||
History API is provided for both function components and struct components. They can enable callbacks to change the
|
||||
route. An `AnyHistory` instance can be obtained in either cases to manipulate the route.
|
||||
|
||||
##### Function Components
|
||||
|
||||
For function components, the `use_history` hook re-renders the component and returns the current route whenever the
|
||||
history changes. Here's how to implement a button that navigates to the `Home` route when clicked.
|
||||
|
||||
```rust ,ignore
|
||||
#[function_component(MyComponent)]
|
||||
pub fn my_component() -> Html {
|
||||
let history = use_history().unwrap();
|
||||
let onclick = Callback::once(move |_| history.push(Route::Home));
|
||||
|
||||
html! {
|
||||
<>
|
||||
<button {onclick}>{"Click to go home"}</button>
|
||||
</>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
The example here uses `Callback::once`. Use a normal callback if the target route can be the same with the route
|
||||
the component is in. For example when you have a logo button on every page the that goes back to home when clicked,
|
||||
clicking that button twice on home page causes the code to panic because the second click pushes an identical Home route
|
||||
and won't trigger a re-render of the element.
|
||||
|
||||
In other words, only use `Callback::once` when you are sure the target route is different. Or use normal callbacks only
|
||||
to be safe.
|
||||
:::
|
||||
|
||||
If you want to replace the current history instead of pushing a new history onto the stack, use `history.replace()`
|
||||
instead of `history.push()`.
|
||||
|
||||
You may notice `history` has to move into the callback, so it can't be used again for other callbacks. Luckily `history`
|
||||
implements `Clone`, here's for example how to have multiple buttons to different routes:
|
||||
|
||||
```rust ,ignore
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
#[function_component(NavItems)]
|
||||
pub fn nav_items() -> Html {
|
||||
let history = use_history().unwrap();
|
||||
|
||||
let go_home_button = {
|
||||
let history = history.clone();
|
||||
let onclick = Callback::once(move |_| history.push(Route::Home));
|
||||
html! {
|
||||
<button {onclick}>{"click to go home"}</button>
|
||||
}
|
||||
};
|
||||
|
||||
let go_to_first_post_button = {
|
||||
let history = history.clone();
|
||||
let onclick = Callback::once(move |_| history.push(Route::Post { id: "first-post".to_string() }));
|
||||
html! {
|
||||
<button {onclick}>{"click to go the first post"}</button>
|
||||
}
|
||||
};
|
||||
|
||||
let go_to_secure_button = {
|
||||
let onclick = Callback::once(move |_| history.push(Route::Secure));
|
||||
html! {
|
||||
<button {onclick}>{"click to go to secure"}</button>
|
||||
}
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
{go_home_button}
|
||||
{go_to_first_post_button}
|
||||
{go_to_secure_button}
|
||||
</>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Struct Components
|
||||
|
||||
For struct components, the `AnyHistory` instance can be obtained through the `ctx.link().history()` API. The rest is
|
||||
identical with the function component case. Here's an example of a view function that renders a single button.
|
||||
|
||||
```rust ,ignore
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let history = ctx.link().history().unwrap();
|
||||
let onclick = Callback::once(move |_| history.push(MainRoute::Home));
|
||||
html!{
|
||||
<button {onclick}>{"Go Home"}</button>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Redirect
|
||||
|
||||
`yew-router` also provides a `<Redirect/>` element in the prelude. It can be used to achieve similar effects as the
|
||||
history API. The element accepts a
|
||||
`to` attribute as the target route. When a `<Redirect/>` element is rendered, it internally calls `history.push()` and
|
||||
changes the route. Here is an example:
|
||||
|
||||
```rust ,ignore
|
||||
#[function_component(SomePage)]
|
||||
fn some_page() -> Html {
|
||||
// made-up hook `use_user`
|
||||
let user = match use_user() {
|
||||
Some(user) => user,
|
||||
// an early return that redirects to the login page
|
||||
// technicality: `Redirect` actually renders an empty html. But since it also pushes history, the target page
|
||||
// shows up immediately. Consider it a "side-effect" component.
|
||||
None => return html! {
|
||||
<Redirect<Route> to={Route::Login}/>
|
||||
},
|
||||
};
|
||||
// ... actual page content.
|
||||
}
|
||||
```
|
||||
|
||||
:::tip `Redirect` vs `history`, which to use
|
||||
The history API is the only way to manipulate route in callbacks.
|
||||
While `<Redirect/>` can be used as return values in a component. You might also want to use `<Redirect/>` in other
|
||||
non-component context, for example in the switch function of a [Nested Router](#nested-router).
|
||||
:::
|
||||
|
||||
### Listening to Changes
|
||||
|
||||
#### Function Components
|
||||
|
||||
Alongside the `use_history` hook, there are also `use_location` and `use_route`. Your components will re-render when
|
||||
provided values change.
|
||||
|
||||
#### Struct Components
|
||||
|
||||
In order to react on route changes, you can pass a callback closure to the `add_history_listener()` method of `ctx.link()`.
|
||||
|
||||
:::note
|
||||
The history listener will get unregistered once it is dropped. Make sure to store the handle inside your
|
||||
component state.
|
||||
:::
|
||||
|
||||
```rust ,ignore
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
let listener = ctx.link()
|
||||
.add_history_listener(ctx.link().callback(
|
||||
// handle event
|
||||
))
|
||||
.unwrap();
|
||||
MyComponent {
|
||||
_listener: listener
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ctx.link().location()` and `ctx.link().route::<R>()` can also be used to retrieve the location and the route once.
|
||||
|
||||
### Query Parameters
|
||||
|
||||
#### Specifying query parameters when navigating
|
||||
|
||||
In order to specify query parameters when navigating to a new route, use either `history.push_with_query` or
|
||||
the `history.replace_with_query` functions. It uses `serde` to serialize the parameters into query string for the URL so
|
||||
any type that implements `Serialize` can be passed. In its simplest form this is just a `HashMap` containing string
|
||||
pairs.
|
||||
|
||||
#### Obtaining query parameters for current route
|
||||
|
||||
`location.query` is used to obtain the query parameters. It uses `serde` to deserialize the parameters from query string
|
||||
in the URL.
|
||||
|
||||
## Nested Router
|
||||
|
||||
Nested router can be useful when the app grows larger. Consider the following router structure:
|
||||
|
||||
<!--
|
||||
The graph is produced with the following code, with graphviz.
|
||||
To reproduce. Save the code in a file, say `input.dot`,
|
||||
And run `$ dot -Tgif input.dot -o nested-router.gif`
|
||||
|
||||
digraph {
|
||||
node [shape=box style=rounded]
|
||||
Home; News; Contact; "Not Found"; Profile; Friends; Theme; SettingsNotFound [label="Not Found"];
|
||||
|
||||
node [fillcolor=lightblue style="filled, rounded"]
|
||||
"Main Router"; "Settings Router";
|
||||
|
||||
"Main Router" -> {Home News Contact "Not Found" "Settings Router"} [arrowhead=none]
|
||||
"Settings Router" -> {SettingsNotFound Profile Friends Theme } [arrowhead=none]
|
||||
SettingsNotFound -> "Not Found" [constraint=false]
|
||||
}
|
||||
-->
|
||||
|
||||

|
||||
|
||||
The nested `SettingsRouter` handles all urls that start with `/settings`. Additionally, it redirects urls that are not
|
||||
matched to the main `NotFound` route. So `/settings/gibberish` will redirect to `/404`.
|
||||
|
||||
It can be implemented with the following code:
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum MainRoute {
|
||||
#[at("/")]
|
||||
Home,
|
||||
#[at("/news")]
|
||||
News,
|
||||
#[at("/contact")]
|
||||
Contact,
|
||||
#[at("/settings/:s")]
|
||||
Settings,
|
||||
#[not_found]
|
||||
#[at("/404")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum SettingsRoute {
|
||||
#[at("/settings/profile")]
|
||||
Profile,
|
||||
#[at("/settings/friends")]
|
||||
Friends,
|
||||
#[at("/settings/theme")]
|
||||
Theme,
|
||||
#[not_found]
|
||||
#[at("/settings/404")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
fn switch_main(route: &MainRoute) -> Html {
|
||||
match route {
|
||||
MainRoute::Home => html! {<h1>{"Home"}</h1>},
|
||||
MainRoute::News => html! {<h1>{"News"}</h1>},
|
||||
MainRoute::Contact => html! {<h1>{"Contact"}</h1>},
|
||||
MainRoute::Settings => html! {
|
||||
<Switch<SettingsRoute> render={Switch::render(switch_settings)} />
|
||||
},
|
||||
MainRoute::NotFound => html! {<h1>{"Not Found"}</h1>},
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_settings(route: &SettingsRoute) -> Html {
|
||||
match route {
|
||||
SettingsRoute::Profile => html! {<h1>{"Profile"}</h1>},
|
||||
SettingsRoute::Friends => html! {<h1>{"Friends"}</h1>},
|
||||
SettingsRoute::Theme => html! {<h1>{"Theme"}</h1>},
|
||||
SettingsRoute::NotFound => html! {
|
||||
<Redirect<MainRoute> to={MainRoute::NotFound}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(App)]
|
||||
pub fn app() -> Html {
|
||||
html! {
|
||||
<BrowserRouter>
|
||||
<Switch<MainRoute> render={Switch::render(switch_main)} />
|
||||
</BrowserRouter>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Relevant examples
|
||||
|
||||
- [Router](https://github.com/yewstack/yew/tree/master/examples/router)
|
|
@ -0,0 +1,243 @@
|
|||
---
|
||||
title: "`wasm-bindgen`"
|
||||
sidebar_label: wasm-bindgen
|
||||
slug: /concepts/wasm-bindgen
|
||||
---
|
||||
|
||||
[`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) is a library and tool to facilitate
|
||||
high-level interactions between Wasm modules and JavaScript; it is built with Rust by
|
||||
[The Rust and WebAssembly Working Group](https://rustwasm.github.io/).
|
||||
|
||||
Yew builds off `wasm-bindgen` and specifically uses the following of its crates:
|
||||
|
||||
- [`js-sys`](https://crates.io/crates/js-sys)
|
||||
- [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen)
|
||||
- [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
|
||||
- [`web-sys`](https://crates.io/crates/web-sys)
|
||||
|
||||
This section will explore some of these crates in a high level in order to make it easier to understand
|
||||
and use `wasm-bindgen` APIs with Yew. For a more in-depth guide into `wasm-bindgen` and it's associated
|
||||
crates then check out [The `wasm-bindgen` Guide](https://rustwasm.github.io/docs/wasm-bindgen/).
|
||||
|
||||
For documentation on the above crates check out [`wasm-bindgen docs.rs`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html).
|
||||
|
||||
:::tip
|
||||
Use the `wasm-bindgen` doc.rs search to find browser APIs and JavaScript types that have been imported
|
||||
over using `wasm-bindgen`.
|
||||
:::
|
||||
|
||||
## [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen)
|
||||
|
||||
This crate provides many of the building blocks for the rest of the crates above. In this section we
|
||||
are only going to cover two main areas of the `wasm-bindgen` crate and that is the macro and some
|
||||
types / traits you will see pop up again and again.
|
||||
|
||||
### `#[wasm_bindgen]` macro
|
||||
|
||||
The `#[wasm_bindgen]` macro, in a high level view, is your translator between Rust and JavaScript, it
|
||||
allows you to describe imported JavaScript types in terms of Rust and vice versa. Using this macro
|
||||
is more advanced, and you shouldn't need to reach for it unless you are trying to interop with an
|
||||
external JavaScript library. The `js-sys` and `web-sys` crates are essentially imported types using
|
||||
this macro for JavaScript types and the browser API respectively.
|
||||
|
||||
Let's go over a simple example of using the `#[wasm-bindgen]` macro to import some specific flavours
|
||||
of the [`console.log`](https://developer.mozilla.org/en-US/docs/Web/API/Console/log).
|
||||
|
||||
```rust ,no_run
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// First up let's take a look of binding `console.log` manually, without the
|
||||
// help of `web_sys`. Here we're writing the `#[wasm_bindgen]` annotations
|
||||
// manually ourselves, and the correctness of our program relies on the
|
||||
// correctness of these annotations!
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
|
||||
// Use `js_namespace` here to bind `console.log(..)` instead of just
|
||||
// `log(..)`
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
|
||||
// The `console.log` is quite polymorphic, so we can bind it with multiple
|
||||
// signatures. Note that we need to use `js_name` to ensure we always call
|
||||
// `log` in JS.
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
fn log_u32(a: u32);
|
||||
|
||||
// Multiple arguments too!
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
fn log_many(a: &str, b: &str);
|
||||
}
|
||||
|
||||
// using the imported functions!
|
||||
log("Hello from Rust!");
|
||||
log_u32(42);
|
||||
log_many("Logging", "many values!");
|
||||
```
|
||||
|
||||
_This example was adapted from [1.2 Using console.log of The `wasm-bindgen` Guide](https://rustwasm.github.io/docs/wasm-bindgen/examples/console-log.html)_.
|
||||
|
||||
### Simulating inheritance
|
||||
|
||||
Inheritance between JavaScript classes is a big part of the language and is a major part of how the
|
||||
Document Object Model (DOM). When types are imported using `wasm-bindgen` you can
|
||||
also add attributes that describe its inheritance.
|
||||
|
||||
In Rust this inheritance is simulated using the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html)
|
||||
and [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) traits. An example of this
|
||||
might help; so say you have three types `A`, `B`, and `C` where `C` extends `B` which in turn
|
||||
extends `A`.
|
||||
When importing these types the `#[wasm-bindgen]` macro will implement the `Deref` and `AsRef`
|
||||
traits in the following way:
|
||||
|
||||
- `C` can `Deref` to `B`
|
||||
- `B` can `Deref` to `A`
|
||||
- `C` can be `AsRef` to `B`
|
||||
- Both `C` & `B` can be `AsRef` to `A`
|
||||
|
||||
These implementations allow you to call a method from `A` on an instance of `C` and to use `C` as if
|
||||
it was `&B` or `&A`.
|
||||
|
||||
Its important to note that every single type imported using `#[wasm-bindgen]` has the same root type,
|
||||
you can think of it as the `A` in the example above, this type is [`JsValue`](#jsvalue) which has
|
||||
its own section
|
||||
below.
|
||||
|
||||
_[extends section in The `wasm-bindgen` Guide](https://rustwasm.github.io/docs/wasm-bindgen/reference/attributes/on-js-imports/extends.html)_
|
||||
|
||||
### [`JsValue`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html)
|
||||
|
||||
This is a representation of an object owned by JavaScript, this is a root catch-all type for `wasm-bindgen`.
|
||||
Any type that comes from `wasm-bindgen` is a `JsValue` and this is because JavaScript doesn't have
|
||||
a strong type system so any function that accepts a variable `x` doesn't define its type so `x` can be
|
||||
a valid JavaScript value; hence `JsValue`. So when you are working with imported functions or types that
|
||||
accept a `JsValue`, then any imported value is _technically_ valid.
|
||||
|
||||
`JsValue` can be accepted by a function but that function may still only expect certain types and this
|
||||
can lead to panics - so when using raw `wasm-bindgen` APIs check the documentation of the JavaScript
|
||||
being imported whether an exception will be caused if that value is not a certain type.
|
||||
|
||||
_[`JsValue` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html)._
|
||||
|
||||
### [`JsCast`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)
|
||||
|
||||
Rust has a strong type system and JavaScript...doesn't 😞 So in order for Rust to maintain these
|
||||
strong types but still be convenient the web assembly group came up with a pretty neat trait `JsCast`.
|
||||
Its job is to help you move from one JavaScript "type" to another, which sounds vague, but it means
|
||||
that if you have one type which you know is really another then you can use the functions of `JsCast`
|
||||
to jump from one type to the other. It's a nice trait to get to know when working with `web-sys`,
|
||||
`wasm_bindgen`, `js-sys` - you'll notice lots of types will implement `JsCast` from those crates.
|
||||
|
||||
`JsCast` provides both checked and unchecked methods of casting - so that at runtime if you are
|
||||
unsure what type a certain object is you can try to cast it which returns possible failure types like
|
||||
[`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) and
|
||||
[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html).
|
||||
|
||||
A common example of this in [`web-sys`](web-sys) is when you are trying to get the
|
||||
target of an event, you might know what the target element is but the
|
||||
[`web_sys::Event`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html) API will always return an [`Option<web_sys::EventTarget>`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html#method.target)
|
||||
so you will need to cast it to the element type. so you can call its methods.
|
||||
|
||||
```rust
|
||||
// need to import the trait.
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Event, EventTarget, HtmlInputElement, HtmlSelectElement};
|
||||
|
||||
fn handle_event(event: Event) {
|
||||
let target: EventTarget = event
|
||||
.target()
|
||||
.expect("I'm sure this event has a target!");
|
||||
|
||||
// maybe the target is a select element?
|
||||
if let Some(select_element) = target.dyn_ref::<HtmlSelectElement>() {
|
||||
// do something amazing here
|
||||
return;
|
||||
}
|
||||
|
||||
// if it wasn't a select element then I KNOW it's a input element!
|
||||
let input_element: HtmlInputElement = target.unchecked_into();
|
||||
}
|
||||
```
|
||||
|
||||
The [`dyn_ref`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_ref)
|
||||
method is a checked cast that returns an `Option<&T>` which means the original type
|
||||
can be used again if the cast failed and thus returned `None`. The
|
||||
[`dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into)
|
||||
method will consume `self`, as per convention for into methods in Rust, and the type returned is
|
||||
`Result<T, Self>` this means if the casting fails then the value in `Err` is so you can try again
|
||||
or do something else with the original type.
|
||||
|
||||
_[`JsCast` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html)._
|
||||
|
||||
### [`Closure`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html)
|
||||
|
||||
The `Closure` type provides a way to transfer Rust closures to JavaScript, the closures past to
|
||||
JavaScript must have a `'static` lifetime for soundness reasons.
|
||||
|
||||
This type is a "handle" in the sense that whenever it is dropped it will invalidate the JS
|
||||
closure that it refers to. Any usage of the closure in JS after the Closure has been dropped will
|
||||
raise an exception.
|
||||
|
||||
`Closure` is often used when you are working with a `js-sys` or `web-sys` API that accepts a type
|
||||
[`&js_sys::Function`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Function.html).
|
||||
An example of using a `Closure` in Yew can be found in the [Using `Closure` section](html/events#using-closure-verbose) on the [Events](html/events) page.
|
||||
|
||||
_[`Closure` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html)._
|
||||
|
||||
## [`js-sys`](https://crates.io/crates/js-sys)
|
||||
|
||||
The `js-sys` crate provides bindings / imports of JavaScript's standard, built-in objects, including
|
||||
their methods and properties.
|
||||
|
||||
This does not include any web APIs as this is what [`web-sys`](wasm-bindgen/web-sys) is for!
|
||||
|
||||
_[`js-sys` documentation](https://rustwasm.github.io/wasm-bindgen/api/js_sys/index.html)._
|
||||
|
||||
## [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
|
||||
|
||||
The `wasm-bindgen-futures` crate provides a bridge for working with JavaScript Promise types as a
|
||||
Rust Future, and similarly contains utilities to turn a rust Future into a JavaScript Promise.
|
||||
This can be useful when working with asynchronous or otherwise blocking work in Rust (wasm),
|
||||
and provides the ability to interoperate with JavaScript events and JavaScript I/O primitives.
|
||||
|
||||
There are three main interfaces in this crate currently:
|
||||
|
||||
1. [`JsFuture`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/struct.JsFuture.html) -
|
||||
A type that is constructed with a [`Promise`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Promise.html)
|
||||
and can then be used as a `Future<Output=Result<JsValue, JsValue>>`. This Rust future will resolve
|
||||
or reject with the value coming out of the `Promise`.
|
||||
|
||||
2. [`future_to_promise`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.future_to_promise.html) -
|
||||
Converts a Rust `Future<Output=Result<JsValue, JsValue>>` into a
|
||||
JavaScript `Promise`. The future’s result will translate to either a resolved or rejected
|
||||
`Promise` in JavaScript.
|
||||
|
||||
3. [`spawn_local`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html) -
|
||||
Spawns a `Future<Output = ()>` on the current thread. This is the best way
|
||||
to run a Future in Rust without sending it to JavaScript.
|
||||
|
||||
_[`wasm-bindgen-futures` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/index.html)._
|
||||
|
||||
### [`spawn_local`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html)
|
||||
|
||||
`spawn_local` is going to be the most commonly used part of the `wasm-bindgen-futures` crate in Yew
|
||||
as this helps when using libraries that have async APIs.
|
||||
|
||||
```rust ,no_run
|
||||
use web_sys::console;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
async fn my_async_fn() -> String { String::from("Hello") }
|
||||
|
||||
spawn_local(async {
|
||||
let mut string = my_async_fn().await;
|
||||
string.push_str(", world!");
|
||||
// console log "Hello, world!"
|
||||
console::log_1(&string.into());
|
||||
});
|
||||
```
|
||||
|
||||
Yew has also added support for futures in certain APIs, most notably you can create a
|
||||
`callback_future` which accepts an `async` block - this uses `spawn_local` internally.
|
||||
|
||||
_[`spawn_local` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html)._
|
|
@ -0,0 +1,241 @@
|
|||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
The [`web-sys` crate](https://crates.io/crates/web-sys) provides bindings for Web APIs. This is
|
||||
procedurally generated from browser WebIDL which is why some of the names are so long and why
|
||||
some of the types are vague.
|
||||
|
||||
## Features in `web-sys`
|
||||
|
||||
The `web-sys` crate with all of it's features enabled can add lots of bloat to a Wasm application,
|
||||
in order to get around this issue most types are feature gated so that you only include the types
|
||||
you require for your application. Yew includes a number of features from `web-sys` and
|
||||
exposes some types in it's public API, you will often need to add `web-sys` as a dependency yourself.
|
||||
|
||||
## Inheritance in `web-sys`
|
||||
|
||||
In the [Simulating inheritance section](../wasm-bindgen#simulating-inheritance) you can read how in
|
||||
general Rust provides an approach to simulate inheritance in JavaScript. This is very important in
|
||||
`web-sys` as understanding what methods are available on a type means understanding it's inheritance.
|
||||
|
||||
This section is going to look at a specific element and list out it's inheritance using Rust by
|
||||
calling [`Deref::deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html#tymethod.deref) until
|
||||
the value is [`JsValue`](../wasm-bindgen#jsvalue):
|
||||
|
||||
```rust
|
||||
use std::ops::Deref;
|
||||
use web_sys::{
|
||||
Element,
|
||||
EventTarget,
|
||||
HtmlElement,
|
||||
HtmlTextAreaElement,
|
||||
Node,
|
||||
};
|
||||
|
||||
fn inheritance_of_text_area(text_area: HtmlTextAreaElement) {
|
||||
// HtmlTextAreaElement is <textarea> in html.
|
||||
let html_element: &HtmlElement = text_area.deref();
|
||||
|
||||
let element: &Element = html_element.deref();
|
||||
|
||||
let node: &Node = element.deref();
|
||||
|
||||
let event_target: &EventTarget = node.deref();
|
||||
|
||||
// Notice we've moved from web-sys types now into built-in
|
||||
// JavaScript types which are in the js-sys crate.
|
||||
let object: &js_sys::Object = event_target.deref();
|
||||
|
||||
// Notice we've moved from js-sys type to the root JsValue from
|
||||
// the wasm-bindgen crate.
|
||||
let js_value: &wasm_bindgen::JsValue = object.deref();
|
||||
|
||||
// Using deref like this means we have to manually traverse
|
||||
// the inheritance tree, however, you can call JsValue methods
|
||||
// on the HtmlTextAreaElement type.
|
||||
// The `is_string` method comes from JsValue.
|
||||
assert!(!text_area.is_string());
|
||||
|
||||
// empty function just to prove we can pass HtmlTextAreaElement as a
|
||||
// &EventTarget.
|
||||
fn this_function_only_takes_event_targets(targets: &EventTarget) {};
|
||||
|
||||
// The compiler will walk down the deref chain in order to match the types here.
|
||||
this_function_only_takes_event_targets(&text_area);
|
||||
|
||||
// The AsRef implementations allow you to treat the HtmlTextAreaElement
|
||||
// as an &EventTarget.
|
||||
|
||||
let event_target: &EventTarget = text_area.as_ref();
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
_[Inheritance in `web-sys` in The `wasm-bindgen` Guide](https://rustwasm.github.io/wasm-bindgen/web-sys/inheritance.html)._
|
||||
|
||||
## The `Node` in `NodeRef`
|
||||
|
||||
Yew uses a [`NodeRef`](../components/refs) in order to provide a way for keeping a reference to
|
||||
a `Node` made by the [`html!`](../html) macro. The `Node` part of `NodeRef` is referring to
|
||||
[`web_sys::Node`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Node.html). The
|
||||
`NodeRef::get` method will return a `Option<Node>` value, however, most of the time in Yew you want
|
||||
to cast this value to a specific element so you can use it's specific methods. This casting
|
||||
can be done using [`JsCast`](../wasm-bindgen#JsCast) on the `Node` value, if present, but Yew
|
||||
provides the `NodeRef::cast` method to perform this casting for convenience and so that you don't
|
||||
necessarily have to include the `wasm-bindgen` dependency for the `JsCast` trait.
|
||||
|
||||
The two code blocks below do essentially the same thing, the first is using `NodeRef::cast` and
|
||||
the second is using [`JsCast::dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into)
|
||||
on the `web_sys::Node` returned from `NodeRef::get`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Using NodeRef::cast" label="Using NodeRef::cast">
|
||||
|
||||
```rust
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::NodeRef;
|
||||
|
||||
fn with_node_ref_cast(node_ref: NodeRef) {
|
||||
if let Some(input) = node_ref.cast::<HtmlInputElement>() {
|
||||
// do something with HtmlInputElement
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Using NodeRef::get" label="Using NodeRef::get">
|
||||
|
||||
```rust
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::NodeRef;
|
||||
|
||||
fn with_jscast(node_ref: NodeRef) {
|
||||
if let Some(input) = node_ref
|
||||
.get()
|
||||
.and_then(|node| node.dyn_into::<HtmlInputElement>().ok()) {
|
||||
// do something with HtmlInputElement
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## JavaScript example to Rust
|
||||
|
||||
This section is to help show that any examples that use JavaScript to interact with the Web APIs
|
||||
can be adapted and written using Rust with `web-sys`.
|
||||
|
||||
### JavaScript example
|
||||
|
||||
```js
|
||||
document.getElementById('mousemoveme').onmousemove = (e) => {
|
||||
// e = Mouse event.
|
||||
var rect = e.target.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left; //x position within the element.
|
||||
var y = e.clientY - rect.top; //y position within the element.
|
||||
console.log("Left? : " + x + " ; Top? : " + y + ".");
|
||||
}
|
||||
```
|
||||
|
||||
### `web-sys` example
|
||||
Using `web-sys` alone the above JavaSciprt example could be implemented like this:
|
||||
|
||||
```toml title=Cargo.toml
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
# We need to enable all the web-sys features we want to use!
|
||||
features = [
|
||||
"console",
|
||||
"Document",
|
||||
"HtmlElement",
|
||||
"MouseEvent",
|
||||
"DomRect",
|
||||
]
|
||||
```
|
||||
|
||||
```rust ,no_run
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
use web_sys::{console, Document, HtmlElement, MouseEvent};
|
||||
|
||||
let mousemove = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(|e| {
|
||||
let rect = e
|
||||
.target()
|
||||
.expect("mouse event doesn't have a target")
|
||||
.dyn_into::<HtmlElement>()
|
||||
.expect("event target should be of type HtmlElement")
|
||||
.get_bounding_client_rect();
|
||||
let x = (e.client_x() as f64) - rect.left();
|
||||
let y = (e.client_y() as f64) - rect.top();
|
||||
console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
|
||||
}));
|
||||
|
||||
Document::new()
|
||||
.expect("global document not set")
|
||||
.get_element_by_id("mousemoveme")
|
||||
.expect("element with id `mousemoveme` not present")
|
||||
.unchecked_into::<HtmlElement>()
|
||||
.set_onmousemove(mousemove.as_ref().dyn_ref());
|
||||
|
||||
// we now need to save the `mousemove` Closure so that when
|
||||
// this event fires the closure is still in memory.
|
||||
```
|
||||
|
||||
This version is much more verbose, but you will probably notice part of that is because of failure
|
||||
types reminding us that some of these function calls have invariants that must be held otherwise will
|
||||
cause a panic in Rust. Another part of the verbosity is the calls to `JsCast` in order to cast into
|
||||
different types so that you can call it's specific methods.
|
||||
|
||||
### Yew example
|
||||
|
||||
In Yew you will mostly be creating [`Callback`](../components/callbacks)s to use in the
|
||||
[`html!`](../html) macro so the example is going to use this approach instead of completely copying
|
||||
the approach above:
|
||||
|
||||
```toml title=Cargo.toml
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
# We need to enable the `DomRect` feature in order to use the
|
||||
# `get_bounding_client_rect` method.
|
||||
features = [
|
||||
"console",
|
||||
"HtmlElement",
|
||||
"MouseEvent",
|
||||
"DomRect",
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
```rust
|
||||
use web_sys::{console, HtmlElement, MouseEvent};
|
||||
use yew::{
|
||||
html,
|
||||
Callback, TargetCast,
|
||||
};
|
||||
|
||||
let onmousemove = Callback::from(|e: MouseEvent| {
|
||||
if let Some(target) = e.target_dyn_into::<HtmlElement>() {
|
||||
let rect = target.get_bounding_client_rect();
|
||||
let x = (e.client_x() as f64) - rect.left();
|
||||
let y = (e.client_y() as f64) - rect.top();
|
||||
console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
|
||||
}
|
||||
});
|
||||
|
||||
html! {
|
||||
<div id="mousemoveme" {onmousemove}></div>
|
||||
};
|
||||
```
|
||||
|
||||
## External libraries
|
||||
|
||||
`web-sys` is a raw binding to the Web API so it comes with some pain in Rust because it was not
|
||||
designed with Rust or even a strong type system in mind, this is where community crates come in to
|
||||
provide abstractions over `web-sys` to provide more idiomatic Rust APIs.
|
||||
|
||||
_[External libraries page](../../more/external-libs)_
|
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
title: "Build a sample app"
|
||||
---
|
||||
|
||||
## Create Project
|
||||
|
||||
To get started, create a new cargo project.
|
||||
|
||||
```bash
|
||||
cargo new yew-app
|
||||
```
|
||||
|
||||
Open the newly created directory.
|
||||
|
||||
```bash
|
||||
cd yew-app
|
||||
```
|
||||
|
||||
## Run a hello world example
|
||||
|
||||
To verify the Rust environment is setup, run the initial project using the cargo build tool. After output about the build process, you should see the expected "Hello World" message.
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
## Converting the project into a Yew web application
|
||||
|
||||
To convert this simple command line application to a basic Yew web application, a few changes are needed.
|
||||
|
||||
### Update Cargo.toml
|
||||
|
||||
Add `yew` to the list of dependencies.
|
||||
|
||||
```toml title=Cargo.toml
|
||||
[package]
|
||||
name = "yew-app"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
# you can check the latest version here: https://crates.io/crates/yew
|
||||
yew = "0.19"
|
||||
```
|
||||
|
||||
### Update main.rs
|
||||
|
||||
We need to generate a template which sets up a root Component called `Model` which renders a button that updates its value when clicked.
|
||||
Replace the contents of `src/main.rs` with the following code.
|
||||
|
||||
:::note
|
||||
The line `yew::start_app::<Model>()` inside `main()` starts your application and mounts it to the page's `<body>` tag.
|
||||
If you would like to start your application with any dynamic properties, you can instead use `yew::start_app_with_props::<Model>(..)`.
|
||||
:::
|
||||
|
||||
```rust ,no_run, title=main.rs
|
||||
use yew::prelude::*;
|
||||
|
||||
enum Msg {
|
||||
AddOne,
|
||||
}
|
||||
|
||||
struct Model {
|
||||
value: i64,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::AddOne => {
|
||||
self.value += 1;
|
||||
// the value has changed so we need to
|
||||
// re-render for it to appear on the page
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
// This gives us a component's "`Scope`" which allows us to send messages, etc to the component.
|
||||
let link = ctx.link();
|
||||
html! {
|
||||
<div>
|
||||
<button onclick={link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
|
||||
<p>{ self.value }</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
yew::start_app::<Model>();
|
||||
}
|
||||
```
|
||||
|
||||
### Create index.html
|
||||
|
||||
Finally, add an `index.html` file in the root directory of your app.
|
||||
|
||||
```html, title=index.html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Yew App</title>
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
## View your web application
|
||||
|
||||
Run the following command to build and serve the application locally.
|
||||
|
||||
```bash
|
||||
trunk serve
|
||||
```
|
||||
|
||||
Trunk will helpfully rebuild your application if you modify any of its files.
|
||||
|
||||
## Congratulations
|
||||
|
||||
You have now successfully setup your Yew development environment, and built your first web application.
|
||||
|
||||
Experiment with this application and review the [examples](./examples.md) to further your learning.
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: "Examples"
|
||||
---
|
||||
|
||||
The Yew repository is chock-full of [examples] \(in various states of maintenance\).
|
||||
We recommend perusing them to get a feel for how to use different features of the framework.
|
||||
We also welcome Pull Requests and issues for when they inevitably get neglected and need some ♥️
|
||||
|
||||
For more details including a list of examples, refer to the [README][examples].
|
||||
|
||||
[examples]: https://github.com/yewstack/yew/tree/master/examples
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: "Project Setup"
|
||||
sidebar_label: Introduction
|
||||
description: "Set yourself up for success"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Your local development environment will need a couple of tools to compile, build, package and debug your Yew application.
|
||||
|
||||
## Installing Rust
|
||||
|
||||
To install Rust, follow the [official instructions](https://www.rust-lang.org/tools/install).
|
||||
|
||||
:::important
|
||||
The minimum supported Rust version (MSRV) for Yew is `1.49.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages.
|
||||
You can check your toolchain version using `rustup show` (under "active toolchain") or alternatively `rustc --version`. To update your toolchain, run `rustup update`.
|
||||
:::
|
||||
|
||||
## Install WebAssembly target
|
||||
|
||||
Rust can compile source codes for different "targets" (e.g. different processors). The compilation target for browser-based WebAssembly is called "wasm32-unknown-unknown". The following command will add this target to your development environment.
|
||||
|
||||
```shell
|
||||
rustup target add wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
## Install Trunk
|
||||
|
||||
Trunk is the recommended tool for managing deployment and packaging, and will be used throughout the documentation and examples.
|
||||
See [Wasm Build Tools](./../../more/wasm-build-tools.md) for more information on packaging and alternatives.
|
||||
|
||||
```shell
|
||||
# note that this might take a while to install, because it compiles everything from scratch
|
||||
# Trunk also provides prebuilt binaries for a number of major package managers
|
||||
# See https://trunkrs.dev/#install for further details
|
||||
cargo install trunk
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Now that you have all the tools needed, we can [build a sample application](./../build-a-sample-app.md).
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: "Using trunk"
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
cargo install --locked trunk
|
||||
cargo install wasm-bindgen-cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Check out ["Build a sample app"](../build-a-sample-app.md) for a short guide on how to build Yew apps with Trunk.
|
||||
|
||||
You can also see it in action by looking at our [examples](https://github.com/yewstack/yew/tree/master/examples),
|
||||
all of which are built with Trunk.
|
||||
|
||||
Trunk builds your app based on the `index.html` file which serves as a config file of sorts.
|
||||
Unlike `wasm-pack`, this tool is actually designed to build apps.
|
||||
This means you don't need to add `cdylib` as a library target and you can use the `main` function
|
||||
as an entry point.
|
||||
|
||||
To build a simple Yew app you just need an `index.html` file at the root of your project:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Yew App</title>
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
The Trunk CLI provides several useful commands but during development `trunk serve` is certainly the most useful one.
|
||||
It runs a local server for you and automatically rebuilds the app when it detects changes.
|
||||
|
||||
When you're ready to release your app, you can just run `trunk build --release`.
|
||||
|
||||
This summary here doesn't nearly cover all of Trunk's features,
|
||||
be sure to check out the [README](https://github.com/thedodd/trunk)!
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: "Using wasm-pack"
|
||||
---
|
||||
|
||||
This tool was created by the Rust / Wasm Working Group for building WebAssembly applications. It supports packaging code into `npm` modules and has an accompanying [Webpack plugin](https://github.com/wasm-tool/wasm-pack-plugin) for easy integration with existing JavaScript applications. More information is given in [the `wasm-pack` documentation](https://rustwasm.github.io/docs/wasm-pack/introduction.html).
|
||||
|
||||
:::note
|
||||
`wasm-pack` requires that you set the crate-type explicitly to include `cdylib`:
|
||||
|
||||
```toml
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
This command will produce a bundle in the `./pkg` directory with your app's compiled WebAssembly
|
||||
along with a JavaScript wrapper which can be used to start your application.
|
||||
|
||||
```bash
|
||||
wasm-pack build --target web
|
||||
```
|
||||
|
||||
## Bundle
|
||||
|
||||
For more information on rollup.js visit this [guide](https://rollupjs.org/guide/en/#quick-start).
|
||||
|
||||
```bash
|
||||
rollup ./main.js --format iife --file ./pkg/bundle.js
|
||||
```
|
||||
|
||||
When using a bundler like rollup.js you can omit `--target web`.
|
||||
|
||||
## Serve
|
||||
|
||||
Feel free to use your preferred server. Here we use a simple Python server to serve the built app.
|
||||
|
||||
```bash
|
||||
python -m http.server 8000
|
||||
```
|
||||
|
||||
If you don't have Python installed, you can install and use the [`simple-http-server`](https://github.com/TheWaWaR/simple-http-server) crate instead.
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: "Starter templates"
|
||||
---
|
||||
|
||||
## `trunk`
|
||||
|
||||
- [Minimal Template](https://github.com/yewstack/yew-trunk-minimal-template) - A small application built with Trunk to get you started.
|
||||
|
||||
## `wasm-pack`
|
||||
|
||||
- [Minimal Template](https://github.com/yewstack/yew-wasm-pack-minimal) - Uses `wasm-pack` and
|
||||
`rollup` to build your application, and your own server to serve it. No bells or whistles here.
|
||||
|
||||
- [Webpack Template](https://github.com/yewstack/yew-wasm-pack-template) - Uses `wasm-pack` and the
|
||||
[`wasm-pack-plugin`](https://github.com/wasm-tool/wasm-pack-plugin) for Webpack to streamline development.
|
||||
|
||||
Unlike other tools, `wasm-pack` forces you to use a `lib`, not a `bin` crate,
|
||||
and the entry-point to your program is annotated with a `#[wasm_bindgen(start)]` attribute.
|
||||
|
||||
Your `Cargo.toml` also should specify that your crate's type is a "cdylib".
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "yew-app"
|
||||
version = "0.1.0"
|
||||
authors = ["Yew App Developer <name@example.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
# You should include "rlib" (the default crate type) otherwise your crate can't be used as a Rust library
|
||||
# which, among other things, breaks unit testing
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
yew = "0.17"
|
||||
wasm-bindgen = "0.2"
|
||||
```
|
||||
|
||||
## Other templates
|
||||
|
||||
- [Parcel Template](https://github.com/spielrs/yew-parcel-template) - Created by a community member
|
||||
and uses [Parcel](https://parceljs.org/)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: "From 0.0.0 to 0.1.0"
|
||||
---
|
||||
|
||||
This is the first release of `yew-agents` being separated from `yew`
|
||||
|
||||
The only thing you will need to do is change the import paths from `yew::*` to `yew_agents::*`
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: "From 0.15.0 to 0.16.0"
|
||||
---
|
||||
|
||||
The router API has been completely redone in `0.16.0`.
|
||||
|
||||
There would be to many things to list out here, so we highly recommend to read up on the [router documentation](./../../concepts/router) and adapt your app accordingly.
|
|
@ -0,0 +1,135 @@
|
|||
---
|
||||
title: "From 0.18.0 to 0.19.0"
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
`Yew 0.19.0` has changed a lot, thus this migration will not cover ALL of the changes.
|
||||
|
||||
Instead only the most impacting changes are mentioned and the rest should be picked up by cargo.
|
||||
|
||||
## html! requirement for braces around most props
|
||||
|
||||
Put it simply almost all the time you have to provide braces for props:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Invalid" label="Invalid">
|
||||
|
||||
```rust {4}, ignore
|
||||
let super_age = 1;
|
||||
html!{
|
||||
<JapaneseYew
|
||||
age=super_age // ! Will throw an error
|
||||
>
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Valid" label="Valid">
|
||||
|
||||
```rust {4}, ignore
|
||||
let super_age = 1;
|
||||
html!{
|
||||
<JapaneseYew
|
||||
age={super_age} // Correct
|
||||
>
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Shorthand" label="Shorthand">
|
||||
|
||||
Also now you can use a shorthand if prop and variable names are the same:
|
||||
|
||||
```rust {4}, ignore
|
||||
let age = 1;
|
||||
html!{
|
||||
<JapaneseYew
|
||||
{age}
|
||||
>
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
There is a community provided regex to help with this change, though we cant promise it will work all the time.
|
||||
|
||||
It for sure breaks when it encounters closures, specifically `|_|` syntax.
|
||||
|
||||
find with `=(?![{">=\s])([^\s></]*(\s!{0,1}[=|&]{2}\s[^\s></]*)*)`
|
||||
|
||||
replace with `={$1}`
|
||||
|
||||
## Function components
|
||||
|
||||
[Function components](./../../concepts/function-components) are a brand new way to write components that require less boilerplate than their structural counter part.
|
||||
|
||||
While this change does not force you to change your codebase, this migration time is a good opportunity to start using them in your codebase.
|
||||
|
||||
## Struct components lifecycle methods and ctx
|
||||
|
||||
[Struct components](./../../concepts/components) also received changes to their API.
|
||||
|
||||
### ShouldRender removed in favor of bool
|
||||
|
||||
`ShouldRender` removed in favor of `bool` and can be just find all - replaced throughout your code base.
|
||||
|
||||
### ctx, props, link
|
||||
|
||||
Struct components no longer own props and link, instead they receive `ctx: &Context<Self>` argument in lifetime methods that can later give you access to `ctx.props() -> &Properties` and `ctx.link() -> &Scope<Self>`.
|
||||
|
||||
You will need to remove `link` and `props` from your component struct fields as such all lifetime methods got updated.
|
||||
|
||||
### Lifetime methods in Component trait
|
||||
|
||||
For new API look in the [Component trait](https://github.com/yewstack/yew/blob/9b6bc96826d53ec38aa3ecc02e3a1e132692c411/packages/yew/src/html/component/mod.rs#L37-L97)
|
||||
|
||||
## `web-sys` is no longer re-exported
|
||||
|
||||
Add `web-sys` as your project dependency and one by one add the needed features like `Event` or `Window`.
|
||||
|
||||
## Services
|
||||
|
||||
During this update all services were removed in favor of community driven solutions like [gloo](https://github.com/rustwasm/gloo)
|
||||
|
||||
Remove this entirely. `yew-services` adds a layer a abstraction which makes it easier to call external resources. This is all well and good but this layer is supposed to be specific to Yew. It would be better if an framework agnostic abstraction existed instead.
|
||||
|
||||
- `ConsoleService`
|
||||
Use [gloo-console](https://crates.io/crates/gloo-console) or [`weblog`](https://crates.io/crates/weblog) instead.
|
||||
- `DialogService`
|
||||
Use [`gloo-dialogs`](https://docs.rs/gloo-dialogs/) instead.
|
||||
- `IntervalService`
|
||||
Use [`gloo-timers`](https://docs.rs/gloo-timers/) instead.
|
||||
- `KeyboardService`
|
||||
`on*` event handlers in yew already handle it. Using this service is even more cumbersome because it requires use of `NodeRef` in order to call any functions provided by it.
|
||||
|
||||
```rust ,ignore
|
||||
let onkeydown = Callback::from(|e| {
|
||||
e.prevent_default();
|
||||
todo!("use `e`, just like in service methods.");
|
||||
});
|
||||
html! {
|
||||
<input {onkeydown} />
|
||||
}
|
||||
```
|
||||
|
||||
- `ResizeService`
|
||||
Use [`gloo-events`](https://docs.rs/gloo-events) to attach the listener instead.
|
||||
- `StorageService`
|
||||
Use [`gloo-storage`](https://docs.rs/gloo-storage/) instead.
|
||||
- `TimeoutService`
|
||||
Use [`gloo-timers`](https://docs.rs/gloo-timers/) instead.
|
||||
- `WebSocketService`
|
||||
Use [`wasm-sockets`](https://github.com/scratchyone/wasm-sockets) or [`reqwasm`](https://github.com/hamza1311/reqwasm) instead.
|
||||
- `FetchService`
|
||||
Use [`reqwest`](https://crates.io/crates/reqwest) or [`reqwasm`](https://github.com/hamza1311/reqwasm) instead.
|
||||
|
||||
## New crate - yew-agent
|
||||
|
||||
Yew agents were removed to their separate crate, see [yew agents migration guide](./../yew-agent/from-0_0_0-to-0_1_0)
|
||||
|
||||
## Ending note
|
||||
|
||||
We are sorry if some things are not covered in this guide as it was truly a huge update and we hope that the uncovered issues will be clearly explained by cargo check/build/clippy.
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
title: "CSS"
|
||||
---
|
||||
|
||||
<TODO>
|
||||
|
||||
A proposal for integrated CSS support can be found here:
|
||||
[https://github.com/yewstack/yew/issues/533](https://github.com/yewstack/yew/issues/533)
|
||||
|
||||
This contains a lot of discussion about how to best integrate CSS support into Yew.
|
||||
|
||||
Currently, the approach we've adopted is to encourage developers to build a number of systems, before
|
||||
adopting the most popular one.
|
||||
|
||||
The community are currently developing a number of projects to make it easy to add styles to
|
||||
projects. A few are given below:
|
||||
|
||||
#### Component Libraries
|
||||
|
||||
* [yew_styles](https://github.com/spielrs/yew_styles) - A styling framework for Yew without any JavaScript dependencies.
|
||||
* [yew-mdc](https://github.com/Follpvosten/yew-mdc) - Material Design Components.
|
||||
* [muicss-yew](https://github.com/AlephAlpha/muicss-yew) - MUI CSS Components.
|
||||
* [Yewtify](https://github.com/yewstack/yewtify) – Implements the features provided by the Vuetify framework in Yew.
|
||||
|
||||
#### Styling Solutions
|
||||
|
||||
* [stylist](https://github.com/futursolo/stylist-rs) - A CSS-in-Rust styling solution for WebAssembly Applications.
|
||||
|
||||
:::important contribute
|
||||
If you're developing a project adding styles to Yew please submit a PR adding yourself to this list!
|
||||
|
||||
[Link to the file containing the list](https://github.com/yewstack/yew/blob/master/docs/more/css.md).
|
||||
:::
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Debugging"
|
||||
---
|
||||
|
||||
## Panics
|
||||
|
||||
The [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook) crate catches
|
||||
`panic!`s and outputs them to the console. Yew will automatically catch `panic!`s and log them to
|
||||
your browser's console.
|
||||
|
||||
## Console Logging
|
||||
|
||||
In general, Wasm web apps are able to interact with Browser APIs, and the `console.log` API is no
|
||||
exception. There are a few options available:
|
||||
|
||||
### [`wasm-logger`](https://crates.io/crates/wasm-logger)
|
||||
|
||||
This crate integrates with the familiar Rust `log` crate:
|
||||
|
||||
```rust ,ignore
|
||||
// setup
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
}
|
||||
|
||||
// usage
|
||||
log::info!("Update: {:?}", msg);
|
||||
```
|
||||
|
||||
## Source Maps
|
||||
|
||||
There is currently no first-class support for source maps for Rust / Wasm web apps. This, of course, is subject to change. If this is no longer true or if progress is made, please suggest a change!
|
||||
|
||||
### Latest Info
|
||||
|
||||
\[Dec 2019\] [Chrome DevTools update](https://developers.google.com/web/updates/2019/12/webassembly#the_future)
|
||||
|
||||
> There is still quite a bit of work to do though. For example, on the tooling side, Emscripten \(Binaryen\) and wasm-pack \(wasm-bindgen\) don’t support updating DWARF information on transformations they perform yet.
|
||||
|
||||
\[2020\] [Rust Wasm debugging guide](https://rustwasm.github.io/book/reference/debugging.html#using-a-debugger)
|
||||
|
||||
> Unfortunately, the debugging story for WebAssembly is still immature. On most Unix systems, [DWARF](http://dwarfstd.org/) is used to encode the information that a debugger needs to provide source-level inspection of a running program. There is an alternative format that encodes similar information on Windows. Currently, there is no equivalent for WebAssembly.
|
||||
|
||||
\[2019\] [Rust Wasm roadmap](https://rustwasm.github.io/rfcs/007-2019-roadmap.html#debugging)
|
||||
|
||||
> Debugging is tricky because much of the story is out of this working group's hands, and depends on both the WebAssembly standardization bodies and the folks implementing browser developer tools instead.
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
title: "Tips for developing Yew applications"
|
||||
---
|
||||
|
||||
:::important contribute
|
||||
This document only contains information for adding supporting in Jetbrains IDEs and VS Code.
|
||||
Feel free to contribute to add instructions for your editor of choice.
|
||||
:::
|
||||
|
||||
## Add a template for creating components
|
||||
|
||||
### Jetbrains IDEs
|
||||
|
||||
1. Navigate to File | Settings | Editor | Live Templates.
|
||||
2. Select Rust and click on + icon to add a new Live Template.
|
||||
3. Give it a name and description of your preference.
|
||||
4. Paste the following snippet in Template Text section
|
||||
5. Change the applicability on the lower right, select Rust > Item > Module
|
||||
```rust ,ignore
|
||||
use yew::prelude::*;
|
||||
|
||||
struct $NAME$;
|
||||
|
||||
enum Msg {
|
||||
}
|
||||
|
||||
impl Component for $NAME$ {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
$HTML$
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For functional components, use the template below. Additionaly:
|
||||
|
||||
1. Click on Edit Variable
|
||||
2. In the `func_name` row, set the Expression column to `snakeCase(NAME)` so that `ComponentName` will be automatically
|
||||
filled as `component_name`
|
||||
in the function definition.
|
||||
3. In the `func_name` row, check "skip if defined" so this autogenerated field won't be navigated to.
|
||||
4. (Optional) give `tag` a reasonable default value like "div", with double quotes.
|
||||
|
||||
```rust ,ignore
|
||||
#[derive(Properties, PartialEq, Clone)]
|
||||
pub struct $Name$Props {
|
||||
}
|
||||
|
||||
#[function_component($Name$)]
|
||||
pub fn $func_name$(props: &$Name$Props) -> Html {
|
||||
|
||||
|
||||
html! {
|
||||
<$tag$>$END$</$tag$>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### VS Code
|
||||
|
||||
1. Navigate to File > Preferences > User Snippets.
|
||||
2. Select Rust as the language.
|
||||
3. Add the following snippet in the snippet JSON file:
|
||||
|
||||
```json
|
||||
{
|
||||
"Create new Yew component": {
|
||||
"prefix": "YOUR PREFIX OF CHOICE",
|
||||
"body": [
|
||||
"use yew::prelude::*;",
|
||||
"",
|
||||
"pub struct ${1};",
|
||||
"",
|
||||
"pub enum Msg {",
|
||||
"}",
|
||||
"",
|
||||
"impl Component for ${1} {",
|
||||
" type Message = Msg;",
|
||||
" type Properties = ();",
|
||||
"",
|
||||
" fn create(ctx: &Context<Self>) -> Self {",
|
||||
" Self",
|
||||
" }",
|
||||
"",
|
||||
" fn view(&self, ctx: &Context<Self>) -> Html {",
|
||||
" html! {",
|
||||
" ${0}",
|
||||
" }",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"description": "Create a new Yew component without properties but with a message enum"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Support for the `html!` Macro
|
||||
|
||||
### Jetbrains IDEs
|
||||
|
||||
|
||||
|
||||
Since April 2021, Jetbrains has started to support proc-macro expansion as an experimental feature.
|
||||
The user has to manually enable it.
|
||||
[See the post here.](https://blog.jetbrains.com/rust/2021/04/08/intellij-rust-updates-for-2021-1/#proc-macros)
|
||||
|
||||
This still won't enable html autofill and formatting help, but fixes the bug in the plugin where
|
||||
the return type of `html!` can't be resolved.
|
||||
|
||||
### VS Code
|
||||
|
||||
There's no support for specialized syntax of `html!` but you can use the default HTML IntelliSense by adding the following snippet in your VS Code's `settings.json` file:
|
||||
|
||||
```json
|
||||
"emmet.includeLanguages": {
|
||||
"rust": "html",
|
||||
}
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "External libraries"
|
||||
description: "Libraries that can help with Yew development"
|
||||
---
|
||||
|
||||
## Malvolio
|
||||
|
||||
[Malvolio](https://crates.io/crates/malvolio) is a library with a "builder-syntax" for creating complex HTML documents
|
||||
with ease. It runs both on servers (and renders to strings) or in browsers (with Yew).
|
||||
|
||||
## Weblog
|
||||
|
||||
[weblog](https://crates.io/crates/weblog) is a crate that defines a set of macros for calling `console.log()`,
|
||||
`console.error()` and other members of the browser's console API when targeting WASM.
|
||||
|
||||
## Gloo
|
||||
|
||||
[Gloo](https://crates.io/crates/gloo) is a modular toolkit for building fast, reliable Web applications and
|
||||
libraries with Rust and Wasm. Gloo provides ergonomic Rust APIs for working with:
|
||||
|
||||
- [Console timers](https://crates.io/crates/gloo-console-timer)
|
||||
- [Dialogs](https://crates.io/crates/gloo-dialogs)
|
||||
- [Events](https://crates.io/crates/gloo-events)
|
||||
- [Files](https://crates.io/crates/gloo-file)
|
||||
- [Timers](https://crates.io/crates/gloo-timers)
|
||||
- [Web Storage](https://crates.io/crates/gloo-storage)
|
||||
|
||||
## Reqwasm
|
||||
|
||||
[Reqwasm](https://crates.io/crates/reqwasm) is an HTTP requests library for WASM Apps.
|
||||
It provides idiomatic Rust API for the browser's `fetch` and `WebSocket` API.
|
||||
|
||||
## Looking For
|
||||
|
||||
Libraries that the ecosystem needs, but doesn't have yet.
|
||||
|
||||
Bootstrap/MaterialUi/arbitrary css framework component wrappers.
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Roadmap"
|
||||
description: "The planned feature roadmap for the Yew framework"
|
||||
---
|
||||
|
||||
## Prioritization
|
||||
|
||||
The prioritization of upcoming features and focuses of the framework is determined by the community.
|
||||
In Spring 2020, a developer survey was sent out to collect feedback on the direction of the project.
|
||||
You can find the summary in the [Yew Wiki](https://github.com/yewstack/yew/wiki/Dev-Survey-%5BSpring-2020%5D).
|
||||
|
||||
:::note
|
||||
Status of all major initiatives can be tracked on the Yew Github [project board](https://github.com/yewstack/yew/projects)
|
||||
:::
|
||||
|
||||
## Focuses
|
||||
|
||||
1. Top Requested Features
|
||||
2. Production Readiness
|
||||
3. Documentation
|
||||
4. Pain Points
|
||||
|
||||
### Most requested features
|
||||
|
||||
1. [Functional Components](https://github.com/yewstack/yew/projects/3)
|
||||
2. [Component Library](https://github.com/yewstack/yew/projects/4)
|
||||
3. Better state management
|
||||
4. [Server side rendering](https://github.com/yewstack/yew/projects/5)
|
||||
|
||||
### Issues needed for production readiness
|
||||
|
||||
* Improve Yew test coverage
|
||||
* Reduce binary size
|
||||
* [Benchmark performance](https://github.com/yewstack/yew/issues/5)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Create tutorial
|
||||
* Simplify project setup
|
||||
|
||||
### Pain points
|
||||
|
||||
* [Component boilerplate](https://github.com/yewstack/yew/issues/830)
|
||||
* Fetch API
|
||||
* [Agents](https://github.com/yewstack/yew/projects/6)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
title: "Testing apps"
|
||||
description: "Testing your app"
|
||||
---
|
||||
|
||||
<TODO>
|
||||
:::info
|
||||
We're working on making it easy to test components, but this is currently a work in progress.
|
||||
|
||||
Support for [shallow rendering](https://github.com/yewstack/yew/issues/1413) and a proposal to
|
||||
[expose the code Yew uses internally for testing components](https://github.com/yewstack/yew/issues/1413)
|
||||
can be found in the GitHub repository.
|
||||
:::
|
||||
|
||||
## wasm\_bindgen\_test
|
||||
|
||||
The Rust Wasm working group maintains a crate called [`wasm_bindgen_test`](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html) which allows you to run tests in a browser in similar fashion to how
|
||||
the built-in `#[test]` procedural macro works. More information is given in the [Rust Wasm working group's documentation](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html)
|
||||
for this module.
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: "Wasm Build Tools"
|
||||
description: "Information about build tools"
|
||||
---
|
||||
|
||||
|
||||
## **Wasm Build Tools**
|
||||
|
||||
Extra tooling is needed to facilitate the interop between WebAssembly and JavaScript. Additionally,
|
||||
depending on the tool you choose, they can help make deployment and packaging much less of a
|
||||
headache by generating all of the JavaScript code necessary to load and run your app's `.wasm`
|
||||
binary in a browser.
|
||||
|
||||
### [**`trunk`**](https://github.com/thedodd/trunk/)
|
||||
|
||||
A tool practically made for building Yew apps.
|
||||
It can build any `wasm-bindgen` based app and its design is inspired by rollup.js.
|
||||
With Trunk you don't need to have Node.js installed or touch any JavaScript code for that matter.
|
||||
It can bundle assets for your app and even ships with a Sass compiler.
|
||||
|
||||
All of our examples are built with Trunk.
|
||||
|
||||
[Getting started with `trunk`](../getting-started/project-setup/using-trunk.md)
|
||||
|
||||
### [**`wasm-pack`**](https://rustwasm.github.io/docs/wasm-pack/)
|
||||
|
||||
A CLI tool developed by the Rust / Wasm Working Group for packaging up WebAssembly. Best used
|
||||
together with the [`wasm-pack-plugin`](https://github.com/wasm-tool/wasm-pack-plugin) for Webpack.
|
||||
The primary purpose of `wasm-pack` is building Wasm libraries for use in JavaScript.
|
||||
Because of this, it can only build libraries and doesn't provide useful tools like a development server or automatic rebuilds.
|
||||
|
||||
[Get started with `wasm-pack`](../getting-started/project-setup/using-wasm-pack.md)
|
||||
|
||||
### Comparison
|
||||
|
||||
| | `trunk` | `wasm-pack` |
|
||||
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) |
|
||||
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. |
|
||||
| Local Server | Supported | Only with webpack plugin |
|
||||
| Auto rebuild on local changes | Supported | Only with webpack plugin |
|
||||
| Asset handling | Supported | Only with webpack plugin |
|
||||
| Headless Browser Testing | [In Progress](https://github.com/thedodd/trunk/issues/20) | [Supported](https://rustwasm.github.io/wasm-pack/book/commands/test.html) |
|
||||
| Supported Targets | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul><li><code>wasm32-unknown-unknown</code></li></ul> |
|
||||
| Example Usage | [Sample app](./../getting-started/build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) |
|
|
@ -0,0 +1,584 @@
|
|||
---
|
||||
title: "Tutorial"
|
||||
slug: /tutorial
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
In this hands-on tutorial, we will take a look at how we can use Yew to build web applications.
|
||||
**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for building front-end web apps using [WebAssembly](https://webassembly.org/).
|
||||
Yew encourages a reusable, maintainable, and well-structured architecture by leveraging Rust's powerful type system.
|
||||
A large ecosystem of community-created libraries, known in Rust as [crates](https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html),
|
||||
provide components for commonly-used patterns such as state management.
|
||||
[Cargo](https://doc.rust-lang.org/cargo/), the package manager for Rust, allows us to take advantage of the
|
||||
numerous crates available on [crates.io](https://crates.io), such as Yew.
|
||||
|
||||
### What we are going to build
|
||||
|
||||
Rustconf is an intergalactic gathering of the Rust community that happens annually.
|
||||
Rustconf 2020 had a plethora of talks that provided a good amount of information.
|
||||
In this hands-on tutorial, we will be building a web application to help fellow Rustaceans
|
||||
get an overview of the talks and watch them all from one page.
|
||||
|
||||
## Setting up
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To get started, let's make sure we have an up-to-date development environment.
|
||||
We will need the following tools:
|
||||
- [Rust](https://www.rust-lang.org/)
|
||||
- [`trunk`](https://trunkrs.dev/)
|
||||
- `wasm32-unknown-unknown` target, the WASM compiler and build target for Rust.
|
||||
|
||||
This tutorial also assumes you're already familiar with Rust. If you're new to Rust,
|
||||
the free [Rust Book](https://doc.rust-lang.org/book/ch00-00-introduction.html) offers a great starting point for
|
||||
beginners and continues to be an excellent resource even for experienced Rust developers.
|
||||
|
||||
|
||||
Ensure the latest version of Rust is installed by running `rustup update` or by
|
||||
[installing rust](https://www.rust-lang.org/tools/install) if you haven't already done so already.
|
||||
|
||||
After installing Rust, you can use Cargo to install `trunk` by running:
|
||||
|
||||
```bash
|
||||
cargo install trunk
|
||||
```
|
||||
|
||||
We will also need to add the WASM build target by running:
|
||||
|
||||
```bash
|
||||
rustup target add wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
### Setting up the project
|
||||
|
||||
First, create a new cargo project:
|
||||
|
||||
```bash
|
||||
cargo new yew-app
|
||||
cd yew-app
|
||||
```
|
||||
|
||||
To verify the Rust environment is set up properly, run the initial project using the cargo build tool.
|
||||
After output about the build process, you should see the expected "Hello, world!" message.
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
## Our first static page
|
||||
|
||||
To convert this simple command line application to a basic Yew web application, a few changes are needed.
|
||||
Update the files as follows:
|
||||
|
||||
```toml title="Cargo.toml" {7}
|
||||
[package]
|
||||
name = "yew-app"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
+ yew = { git = "https://github.com/yewstack/yew/" }
|
||||
```
|
||||
|
||||
```rust ,no_run title="src/main.rs"
|
||||
use yew::prelude::*;
|
||||
|
||||
#[function_component(App)]
|
||||
fn app() -> Html {
|
||||
html! {
|
||||
<h1>{ "Hello World" }</h1>
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
yew::start_app::<App>();
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's create an `index.html` at the root of the project.
|
||||
|
||||
```html title="index.html"
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Start the development server
|
||||
|
||||
Run the following command to build and serve the application locally.
|
||||
|
||||
```bash
|
||||
trunk serve --open
|
||||
```
|
||||
|
||||
Trunk will open your application in your default browser, watch the project directory and helpfully rebuild your
|
||||
application if you modify any source files. If you are curious, you can run `trunk help` and `trunk help <subcommand>`
|
||||
for more details on what's happening.
|
||||
|
||||
### Congratulations
|
||||
|
||||
You have now successfully set up your Yew development environment and built your first Yew web application.
|
||||
|
||||
## Building HTML
|
||||
|
||||
Yew makes use of Rust's procedural macros and provides us with a syntax similar to JSX (an extension to JavaScript
|
||||
which allows you to write HTML-like code inside of JavaScript) to create the markup.
|
||||
|
||||
### Converting classic HTML
|
||||
|
||||
Since we already have a pretty good idea of what our website will look like, we can simply translate our mental draft
|
||||
into a representation compatible with `html!`. If you're comfortable writing simple HTML, you should have no problem
|
||||
writing marking inside `html!`. It is important to note that the macro does differ from HTML in a few ways:
|
||||
|
||||
1. Expressions must be wrapped in curly braces (`{ }`)
|
||||
2. There must only be one root node. If you want to have multiple elements without wrapping them in a container,
|
||||
an empty tag/fragment (`<> ... </>`) is used
|
||||
3. Elements must be closed properly.
|
||||
|
||||
We want to build a layout that looks something like this in raw HTML:
|
||||
|
||||
```html
|
||||
<h1>RustConf Explorer</h1>
|
||||
<div>
|
||||
<h3>Videos to watch</h3>
|
||||
<p>John Doe: Building and breaking things</p>
|
||||
<p>Jane Smith: The development process</p>
|
||||
<p>Matt Miller: The Web 7.0</p>
|
||||
<p>Tom Jerry: Mouseless development</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>John Doe: Building and breaking things</h3>
|
||||
<img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail">
|
||||
</div>
|
||||
```
|
||||
|
||||
Now, let's convert this HTML into `html!`. Type (or copy/paste) the following snippet into the body of `app` function
|
||||
such that the value of `html!` is returned by the function
|
||||
|
||||
```rust ,ignore
|
||||
html! {
|
||||
<>
|
||||
<h1>{ "RustConf Explorer" }</h1>
|
||||
<div>
|
||||
<h3>{"Videos to watch"}</h3>
|
||||
<p>{ "John Doe: Building and breaking things" }</p>
|
||||
<p>{ "Jane Smith: The development process" }</p>
|
||||
<p>{ "Matt Miller: The Web 7.0" }</p>
|
||||
<p>{ "Tom Jerry: Mouseless development" }</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>{ "John Doe: Building and breaking things" }</h3>
|
||||
<img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
Refresh the browser page, and you should see the following output displayed:
|
||||
|
||||
**TODO: Add screenshot**
|
||||
|
||||
### Using Rust language constructs in the markup
|
||||
|
||||
A big advantage of writing markup in Rust is that we get all the coolness of Rust in our markup.
|
||||
Now, instead of hardcoding the list of videos, let's actually define them as a `Vec` of Rust objects.
|
||||
We'll create a simple `struct` (in `main.rs` or any file of our choice) which will hold our data.
|
||||
|
||||
```rust
|
||||
struct Video {
|
||||
id: usize,
|
||||
title: String,
|
||||
speaker: String,
|
||||
url: String,
|
||||
}
|
||||
```
|
||||
|
||||
Next, we will create instances of this struct in our `app` function and use those instead of hardcoding the data:
|
||||
|
||||
```rust
|
||||
use website_test::tutorial::Video;
|
||||
|
||||
let videos = vec![
|
||||
Video {
|
||||
id: 1,
|
||||
title: "Building and breaking things".to_string(),
|
||||
speaker: "John Doe".to_string(),
|
||||
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
|
||||
},
|
||||
Video {
|
||||
id: 2,
|
||||
title: "The development process".to_string(),
|
||||
speaker: "Jane Smith".to_string(),
|
||||
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
|
||||
},
|
||||
Video {
|
||||
id: 3,
|
||||
title: "The Web 7.0".to_string(),
|
||||
speaker: "Matt Miller".to_string(),
|
||||
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
|
||||
},
|
||||
Video {
|
||||
id: 4,
|
||||
title: "Mouseless development".to_string(),
|
||||
speaker: "Tom Jerry".to_string(),
|
||||
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
In order to display them, we need to convert these `Vec`s into `Html`. We can do that by creating an iterator,
|
||||
mapping it to `html!` and collecting it as `Html`:
|
||||
|
||||
```rust ,ignore
|
||||
let videos = videos.iter().map(|video| html! {
|
||||
<p>{format!("{}: {}", video.speaker, video.title)}</p>
|
||||
}).collect::<Html>();
|
||||
```
|
||||
|
||||
And finally we need to replace the hardcoded list of videos with the `Html` we created from data:
|
||||
|
||||
```rust ,ignore {6-10}
|
||||
html! {
|
||||
<>
|
||||
<h1>{ "RustConf Explorer" }</h1>
|
||||
<div>
|
||||
<h3>{ "Videos to watch" }</h3>
|
||||
- <p>{ "John Doe: Building and breaking things" }</p>
|
||||
- <p>{ "Jane Smith: The development process" }</p>
|
||||
- <p>{ "Matt Miller: The Web 7.0" }</p>
|
||||
- <p>{ "Tom Jerry: Mouseless development" }</p>
|
||||
+ { videos }
|
||||
</div>
|
||||
// ...
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
Components are the building blocks of Yew applications. By combining components, which can be made of other components,
|
||||
we build our application. By structuring our components for re-usability and keeping them generic, we will be able to use
|
||||
them in multiple parts of our application without having to duplicate code or logic.
|
||||
|
||||
In fact, the `app` function we have been using so far is a component, called `App`. It is a "function component".
|
||||
There are two different types of components in Yew.
|
||||
1. Struct Components
|
||||
2. Function Components
|
||||
|
||||
In this tutorial, we will be using function components.
|
||||
|
||||
Now, let's split up our `App` component into smaller components. We'll begin by extracting the videos list into
|
||||
its own component.
|
||||
|
||||
```rust ,compile_fail
|
||||
use yew::prelude::*;
|
||||
|
||||
struct Video {
|
||||
id: usize,
|
||||
title: String,
|
||||
speaker: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct VideosListProps {
|
||||
videos: Vec<Video>,
|
||||
}
|
||||
|
||||
#[function_component(VideosList)]
|
||||
fn videos_list(VideosListProps { videos }: &VideosListProps) -> Html {
|
||||
videos.iter().map(|video| html! {
|
||||
<p>{format!("{}: {}", video.speaker, video.title)}</p>
|
||||
}).collect()
|
||||
}
|
||||
```
|
||||
|
||||
Notice the parameters of our `VideosList` function component. A function component takes only one argument which
|
||||
defines its "props" (short for "properties"). Props are used to pass data down from a parent component to a child component.
|
||||
In this case, `VideosListProps` is a struct which defines the props.
|
||||
|
||||
:::important
|
||||
The struct used for props must implement `Properties` by deriving it.
|
||||
:::
|
||||
|
||||
In order for the above code to compile, we need to modify the `Video` struct like:
|
||||
|
||||
```rust {1}
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct Video {
|
||||
id: usize,
|
||||
title: String,
|
||||
speaker: String,
|
||||
url: String,
|
||||
}
|
||||
```
|
||||
|
||||
Now, we can update our `App` component to make use of `VideosList` component.
|
||||
|
||||
```rust ,ignore {4-7,13-14}
|
||||
#[function_component(App)]
|
||||
fn app() -> Html {
|
||||
// ...
|
||||
- let videos = videos.iter().map(|video| html! {
|
||||
- <p>{format!("{}: {}", video.speaker, video.title)}</p>
|
||||
- }).collect::<Html>();
|
||||
-
|
||||
html! {
|
||||
<>
|
||||
<h1>{ "RustConf Explorer" }</h1>
|
||||
<div>
|
||||
<h3>{"Videos to watch"}</h3>
|
||||
- { videos }
|
||||
+ <VideosList videos={videos} />
|
||||
</div>
|
||||
// ...
|
||||
</>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By looking at the browser window, we can verify that the lists are rendered as they should be.
|
||||
We have moved the rendering logic of lists to its own component. This shortens the `App` component’s source code,
|
||||
making it easier for us to read and understand.
|
||||
|
||||
### Making it interactive
|
||||
|
||||
The final goal here is to display the selected video. In order to do that, `VideosList` component needs to "notify" its
|
||||
parent when a video is selected, which is done via a `Callback`. This concept is called "passing handlers".
|
||||
We modify its props to take an `on_click` callback:
|
||||
|
||||
```rust ,ignore {4}
|
||||
#[derive(Clone, Properties, PartialEq)]
|
||||
struct VideosListProps {
|
||||
videos: Vec<Video>,
|
||||
+ on_click: Callback<Video>
|
||||
}
|
||||
```
|
||||
|
||||
Then we modify the `VideosList` component to pass the "emit" the selected video to the callback.
|
||||
|
||||
```rust ,ignore {5-11,14-15}
|
||||
#[function_component(VideosList)]
|
||||
fn videos_list(VideosListProps { videos, on_click }: &VideosListProps) -> Html {
|
||||
let on_click = on_click.clone();
|
||||
videos.iter().map(|video| {
|
||||
+ let on_video_select = {
|
||||
+ let on_click = on_click.clone();
|
||||
+ let video = video.clone();
|
||||
+ Callback::from(move |_| {
|
||||
+ on_click.emit(video.clone())
|
||||
+ })
|
||||
+ };
|
||||
|
||||
html! {
|
||||
- <p>{format!("{}: {}", video.speaker, video.title)}</p>
|
||||
+ <p onclick={on_video_select}>{format!("{}: {}", video.speaker, video.title)}</p>
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
```
|
||||
|
||||
Next, we need to modify the usage of `VideosList` to pass that callback. But before doing that, we should create
|
||||
a new component, `VideoDetails`, component that is displayed when a video is clicked.
|
||||
|
||||
```rust
|
||||
use website_test::tutorial::Video;
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Clone, Properties, PartialEq)]
|
||||
struct VideosDetailsProps {
|
||||
video: Video,
|
||||
}
|
||||
|
||||
#[function_component(VideoDetails)]
|
||||
fn video_details(VideosDetailsProps { video }: &VideosDetailsProps) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<h3>{ video.title.clone() }</h3>
|
||||
<img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, modify the `App` component to display `VideoDetails` component whenever a video is selected.
|
||||
|
||||
```rust ,ignore {4,6-11,13-15,24-29}
|
||||
#[function_component(App)]
|
||||
fn app() -> Html {
|
||||
// ...
|
||||
+ let selected_video = use_state(|| None);
|
||||
|
||||
+ let on_video_select = {
|
||||
+ let selected_video = selected_video.clone();
|
||||
+ Callback::from(move |video: Video| {
|
||||
+ selected_video.set(Some(video))
|
||||
+ })
|
||||
+ };
|
||||
|
||||
+ let details = selected_video.as_ref().map(|video| html! {
|
||||
+ <VideoDetails video={video.clone()} />
|
||||
+ });
|
||||
|
||||
html! {
|
||||
<>
|
||||
<h1>{ "RustConf Explorer" }</h1>
|
||||
<div>
|
||||
<h3>{"Videos to watch"}</h3>
|
||||
<VideosList videos={videos} on_click={on_video_select.clone()} />
|
||||
</div>
|
||||
+ { for details }
|
||||
- <div>
|
||||
- <h3>{ "John Doe: Building and breaking things" }</h3>
|
||||
- <img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
|
||||
- </div>
|
||||
- </>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Don't worry about the `use_state` right now, we will come back to that later.
|
||||
Note the trick we pulled with `{ for details }`. `Option<_>` implements `Iterator` so we can use it to display the only
|
||||
element returned by the `Iterator` with the `{ for ... }` syntax.
|
||||
|
||||
### Handling state
|
||||
|
||||
Remember the `use_state` used earlier? That is a special function, called a "hook". Hooks are used to "hook" into
|
||||
lifecycle of a function component and perform actions. You can learn more about this hook, and others
|
||||
[here](concepts/function-components/pre-defined-hooks#use_state)
|
||||
|
||||
:::note
|
||||
Struct components act differently. See [the documentation](concepts/components) to learn about those.
|
||||
:::
|
||||
|
||||
## Fetching data (using external REST API)
|
||||
|
||||
In a real world application, data will usually come from an API instead of being hardcoded. Let's fetch our
|
||||
videos list from external source. For this we will need to add the following crates:
|
||||
- [`reqwasm`](https://crates.io/crates/reqwasm)
|
||||
For making the fetch call.
|
||||
- [`serde`](https://serde.rs) with derive features
|
||||
For de-serializing the JSON response
|
||||
- [`wasm-bindgen-futures`](https://crates.io/crates/wasm-bindgen-futures)
|
||||
For executing Rust Future as a Promise
|
||||
|
||||
Let's update the dependencies in `Cargo.toml` file:
|
||||
|
||||
```toml title="Cargo.toml"
|
||||
[dependencies]
|
||||
reqwasm = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
```
|
||||
|
||||
Update the `Video` struct to derive the `Deserialize` trait:
|
||||
|
||||
```rust ,ignore {1-2}
|
||||
- #[derive(Clone, PartialEq)]
|
||||
+ #[derive(Clone, PartialEq, Deserialize)]
|
||||
struct Video {
|
||||
id: usize,
|
||||
title: String,
|
||||
speaker: String,
|
||||
url: String,
|
||||
}
|
||||
```
|
||||
|
||||
Now as the last step, we need to update our `App` component to make the fetch request instead of using hardcoded data
|
||||
|
||||
```rust ,ignore {3-23,32-33}
|
||||
#[function_component(App)]
|
||||
fn app() -> Html {
|
||||
- let videos = vec![
|
||||
- // ...
|
||||
- ]
|
||||
+ let videos = use_state(|| vec![]);
|
||||
+ {
|
||||
+ let videos = videos.clone();
|
||||
+ use_effect_with_deps(move |_| {
|
||||
+ let videos = videos.clone();
|
||||
+ wasm_bindgen_futures::spawn_local(async move {
|
||||
+ let fetched_videos: Vec<Video> = Request::get("https://yew.rs/tutorial/data.json")
|
||||
+ .send()
|
||||
+ .await
|
||||
+ .unwrap()
|
||||
+ .json()
|
||||
+ .await
|
||||
+ .unwrap();
|
||||
+ videos.set(fetched_videos);
|
||||
+ });
|
||||
+ || ()
|
||||
+ }, ());
|
||||
+ }
|
||||
|
||||
// ...
|
||||
|
||||
html! {
|
||||
<>
|
||||
<h1>{ "RustConf Explorer" }</h1>
|
||||
<div>
|
||||
<h3>{"Videos to watch"}</h3>
|
||||
- <VideosList videos={videos} on_click={on_video_select.clone()} />
|
||||
+ <VideosList videos={(*videos).clone()} on_click={on_video_select.clone()} />
|
||||
</div>
|
||||
{ for details }
|
||||
</>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
We're using `unwrap`s here because this is a demo application. In a real world app, you would likely want to have
|
||||
[proper error handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html).
|
||||
:::
|
||||
|
||||
Now look at the browser to see everything working as expected... which would've been the case if it weren't for CORS.
|
||||
In order to fix that, we need a proxy server. Luckily trunk provides that.
|
||||
|
||||
Update the following line:
|
||||
|
||||
```rust ,ignore {2-3}
|
||||
// ...
|
||||
- let fetched_videos: Vec<Video> = Request::get("https://yew.rs/tutorial/data.json")
|
||||
+ let fetched_videos: Vec<Video> = Request::get("/tutorial/data.json")
|
||||
// ...
|
||||
```
|
||||
|
||||
Now, rerun the server with the following command:
|
||||
|
||||
```bash
|
||||
trunk serve --proxy-backend=https://yew.rs/tutorial
|
||||
```
|
||||
|
||||
Refresh the tab and everything should work as expected.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
Congratulations! You’ve created a web application that fetches data from an external API and displays a list of videos.
|
||||
|
||||
## What's next
|
||||
|
||||
Obviously, this application is very far from perfect or useful. After going through this tutorial,
|
||||
you can use it as a jumping-off point to explore more advanced topics.
|
||||
|
||||
### Styles
|
||||
|
||||
Our apps look very ugly. There's no CSS, or any kind of styles.
|
||||
Unfortunately, Yew doesn't offer a built-in way to style components. See [Trunk's assets](https://trunkrs.dev/assets/)
|
||||
to learn how to add style sheets.
|
||||
|
||||
### More libraries
|
||||
|
||||
Our app made use of only a few external dependencies. There are lots of crates out there that can be used.
|
||||
See [external libraries](more/external-libs) for more details.
|
||||
|
||||
### Learning more about Yew
|
||||
|
||||
Read our [official documentation](/docs). It explains a lot of concepts in much more details.
|
||||
To learn more about our the Yew API, see our [API docs](https://docs.rs/yew).
|
|
@ -1,14 +1,33 @@
|
|||
{
|
||||
"version-0.18.0/sidebar": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/intro"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"type": "category",
|
||||
"label": "Getting Started",
|
||||
"items": [
|
||||
{
|
||||
"collapsed": true,
|
||||
"type": "category",
|
||||
"label": "Project Setup",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/using-trunk"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/using-wasm-pack"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/using-cargo-web"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/build-a-sample-app"
|
||||
|
@ -24,29 +43,6 @@
|
|||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/starter-templates"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"type": "category",
|
||||
"label": "Project Setup",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/using-trunk"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/using-wasm-pack"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/getting-started/project-setup/using-cargo-web"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -62,7 +58,7 @@
|
|||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/concepts/components"
|
||||
"id": "version-0.18.0/concepts/components/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
|
@ -89,7 +85,7 @@
|
|||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/concepts/wasm-bindgen"
|
||||
"id": "version-0.18.0/concepts/wasm-bindgen/wasm-bindgen"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
|
@ -104,7 +100,7 @@
|
|||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/concepts/html"
|
||||
"id": "version-0.18.0/concepts/html/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
|
@ -139,7 +135,7 @@
|
|||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.18.0/concepts/services"
|
||||
"id": "version-0.18.0/concepts/services/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
{
|
||||
"version-0.19.0/sidebar": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Getting Started",
|
||||
"items": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Project Setup",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/getting-started/project-setup/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/getting-started/project-setup/using-trunk"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/getting-started/project-setup/using-wasm-pack"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/getting-started/build-a-sample-app"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/getting-started/examples"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/getting-started/starter-templates"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Concepts",
|
||||
"items": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "wasm-bindgen",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/wasm-bindgen/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/wasm-bindgen/web-sys"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Components",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/components/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/components/callbacks"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/components/scope"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/components/properties"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/components/children"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/components/refs"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "HTML",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/components"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/elements"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/events"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/classes"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/fragments"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/lists"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/html/literals-and-expressions"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Function Components",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/function-components/introduction"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/function-components/attribute"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/function-components/pre-defined-hooks"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/function-components/custom-hooks"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/agents"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/contexts"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/concepts/router"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Advanced topics",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/advanced-topics/how-it-works"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/advanced-topics/optimizations"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/advanced-topics/portals"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "More",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/debugging"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/development-tips"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/external-libs"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/css"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/testing"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/roadmap"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/more/wasm-build-tools"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Migration guides",
|
||||
"items": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "yew",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/migration-guides/yew/from-0_18_0-to-0_19_0"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "yew-agent",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/migration-guides/yew-agent/from-0_0_0-to-0_1_0"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "yew-router",
|
||||
"items": [
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/migration-guides/yew-router/from-0_15_0-to-0_16_0"
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
}
|
||||
],
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "version-0.19.0/tutorial"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
[
|
||||
"0.19.0",
|
||||
"0.18.0"
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue