mirror of https://github.com/yewstack/yew
Prepare for 0.21 release (#3412)
* Update CHANGELOG Changelog run: https://github.com/yewstack/yew/actions/runs/6283917852/job/17064800916 * docusaurus docs:version 0.21 * migration guide * blog post * prettier * make website warnings go away * make GA work * Apply suggestions from code review * Apply suggestion from review Co-authored-by: Kaede Hoshikawa <futursolo@users.noreply.github.com> * prettier --------- Co-authored-by: Kaede Hoshikawa <futursolo@users.noreply.github.com>
This commit is contained in:
parent
30e2d548ef
commit
5e823e729d
66
CHANGELOG.md
66
CHANGELOG.md
|
@ -1,5 +1,71 @@
|
|||
# Changelog
|
||||
|
||||
## ✨ yew **0.21.0** *(2023-09-23)*
|
||||
|
||||
#### Changelog
|
||||
|
||||
## 🛠 Fixes
|
||||
|
||||
- Fix rust-analyzer non_camel_case_types warning. [[@Sean Bruton](https://github.com/Sean Bruton), [#3388](https://github.com/yewstack/yew/pull/3388)]
|
||||
- Fix incorrect text escaping during SSR. [[@Tim Kurdov](https://github.com/Tim Kurdov), [#3381](https://github.com/yewstack/yew/pull/3381)]
|
||||
- Fix top-level reconciliation in portals. [[@WorldSEnder](https://github.com/WorldSEnder), [#3020](https://github.com/yewstack/yew/pull/3020)]
|
||||
- Fix clippy::let_unit_value lint in propless components. [[@WorldSEnder](https://github.com/WorldSEnder), [#2970](https://github.com/yewstack/yew/pull/2970)]
|
||||
|
||||
## ⚡️ Features
|
||||
|
||||
- Updated the docs of `set_event_bubbling`. [[@Tim Kurdov](https://github.com/Tim Kurdov), [#3391](https://github.com/yewstack/yew/pull/3391)]
|
||||
- feat: support arrays for Classes/classes!(). [[@Pouriya](https://github.com/Pouriya), [#3393](https://github.com/yewstack/yew/pull/3393)]
|
||||
- Mark VNode as #[must_use]. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3387](https://github.com/yewstack/yew/pull/3387)]
|
||||
- Add `IntoPropValue` implementation to convert from `Cow`s to `AttrValue`. [[@Tim Kurdov](https://github.com/Tim Kurdov), [#3346](https://github.com/yewstack/yew/pull/3346)]
|
||||
- Remove compatibility code before 1.64. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3379](https://github.com/yewstack/yew/pull/3379)]
|
||||
- Keep checked attribute for elements without special handling. [[@Muhammad Hamza](https://github.com/Muhammad Hamza), [#3373](https://github.com/yewstack/yew/pull/3373)]
|
||||
- feat: implement hydration for vraw. [[@Dillen Meijboom](https://github.com/Dillen Meijboom), [#3245](https://github.com/yewstack/yew/pull/3245)]
|
||||
- Add webkitdirectory to the boolean attributes list. [[@Julius Lungys](https://github.com/Julius Lungys), [#3214](https://github.com/yewstack/yew/pull/3214)]
|
||||
- Incremental performance improvements to element creation. [[@Greg Johnston](https://github.com/Greg Johnston), [#3169](https://github.com/yewstack/yew/pull/3169)]
|
||||
- Make VList's children Rc'ed. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3050](https://github.com/yewstack/yew/pull/3050)]
|
||||
- Update dependencies. [[@Muhammad Hamza](https://github.com/Muhammad Hamza), [#3171](https://github.com/yewstack/yew/pull/3171)]
|
||||
- Pass string types to Html props. [[@Cecile Tonglet](https://github.com/Cecile Tonglet), [#2872](https://github.com/yewstack/yew/pull/2872)]
|
||||
- Implement an internal DomSlot for positioning instead of NodeRef. [[@WorldSEnder](https://github.com/WorldSEnder), [#3048](https://github.com/yewstack/yew/pull/3048)]
|
||||
- Prefer pop_first if it is available. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3084](https://github.com/yewstack/yew/pull/3084)]
|
||||
- Add method map() on Children to wrap easily. [[@Cecile Tonglet](https://github.com/Cecile Tonglet), [#3039](https://github.com/yewstack/yew/pull/3039)]
|
||||
- Reentrant event listeners. [[@WorldSEnder](https://github.com/WorldSEnder), [#3037](https://github.com/yewstack/yew/pull/3037)]
|
||||
- Add impl IntoIterator on &Classes. [[@Cecile Tonglet](https://github.com/Cecile Tonglet), [#3038](https://github.com/yewstack/yew/pull/3038)]
|
||||
- Assert there are no circular references. [[@WorldSEnder](https://github.com/WorldSEnder), [#3025](https://github.com/yewstack/yew/pull/3025)]
|
||||
## 🚨 Breaking changes
|
||||
|
||||
- Remove special handing of struct fields of type `Option` in `derive(Properties)`. [[@Tim Kurdov](https://github.com/Tim Kurdov), [#3398](https://github.com/yewstack/yew/pull/3398)]
|
||||
- Agent v2. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#2773](https://github.com/yewstack/yew/pull/2773)]
|
||||
- Update signature of use_prepared_state/use_transitive_state. [[@Muhammad Hamza](https://github.com/Muhammad Hamza), [#3376](https://github.com/yewstack/yew/pull/3376)]
|
||||
- Make signature of use_future_with consistent. [[@Muhammad Hamza](https://github.com/Muhammad Hamza), [#3372](https://github.com/yewstack/yew/pull/3372)]
|
||||
- Allow any type to be used as Children (take 2). [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3289](https://github.com/yewstack/yew/pull/3289)]
|
||||
- Enable PartialEq for all virtual dom types. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3206](https://github.com/yewstack/yew/pull/3206)]
|
||||
- Pass hook dependencies as the first function argument. [[@Arniu Tseng](https://github.com/Arniu Tseng), [#2861](https://github.com/yewstack/yew/pull/2861)]
|
||||
- Make Classes cheap to clone. [[@Cecile Tonglet](https://github.com/Cecile Tonglet), [#3021](https://github.com/yewstack/yew/pull/3021)]
|
||||
|
||||
## ✨ yew-router **0.18.0** *(2023-09-xx)*
|
||||
|
||||
#### Changelog
|
||||
|
||||
## ⚡️ Features
|
||||
|
||||
- Update dependencies. [[@Muhammad Hamza](https://github.com/Muhammad Hamza), [#3171](https://github.com/yewstack/yew/pull/3171)]
|
||||
- Only handle "normal" clicks on <Link>s. [[@Kai Salmon](https://github.com/Kai Salmon), [#3056](https://github.com/yewstack/yew/pull/3056)]
|
||||
|
||||
## 🚨 Breaking changes
|
||||
|
||||
- Encode Path Parameters in `yew-router`. [[@Jedd Dryden](https://github.com/Jedd Dryden), [#3187](https://github.com/yewstack/yew/pull/3187)]
|
||||
- Pass hook dependencies as the first function argument. [[@Arniu Tseng](https://github.com/Arniu Tseng), [#2861](https://github.com/yewstack/yew/pull/2861)]
|
||||
|
||||
## ✨ yew-agent **0.3.0** *(2023-09-xx)*
|
||||
|
||||
#### Changelog
|
||||
|
||||
## 🚨 Breaking changes
|
||||
|
||||
- Agent v2. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#2773](https://github.com/yewstack/yew/pull/2773)]
|
||||
|
||||
----
|
||||
|
||||
## ✨ yew **0.20.0** *(2022-11-xx)*
|
||||
|
||||
#### Changelog
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: Announcing Yew 0.21
|
||||
authors: [hamza]
|
||||
---
|
||||
|
||||
The Yew development team is thrilled to unveil Yew 0.21.0, a significant milestone in the journey of empowering developers to create dependable and high-performance web applications with Rust.
|
||||
Let's dive into the major highlights of this release.
|
||||
|
||||
## What's new
|
||||
|
||||
### 1. Changing Signatures: A Shift in Hook Dependencies
|
||||
|
||||
One of the significant changes in Yew 0.21 is the adjustment to the signature of hooks that accept dependencies.
|
||||
Dependencies used to be passed as the second argument after the closure. However, now they're passed as the first argument before the closure.
|
||||
|
||||
```rust
|
||||
use_effect_with_deps(deps, move |deps: Vec<i32>| {
|
||||
// Do something with dependencies
|
||||
});
|
||||
```
|
||||
|
||||
The reason behind swapping the order of dependencies in the code snippet is to address a specific use case.
|
||||
In situations where the same value is needed both to compute a dependency and to be moved by value into the closure, the new order simplifies the code and improves readability and ergonomics.
|
||||
|
||||
This is a big breaking change so we've provided [a way to automate the refactor](https://yew.rs/docs/migration-guides/yew/from-0_20_0-to-0_21_0#automated-refactor)
|
||||
|
||||
### 2. Versatile Child Types
|
||||
|
||||
Yew now allows you to use any type as children within your components. This means you're no longer limited to just the `Children` type.
|
||||
Instead, you can use any type, even `Html` or closures, unlocking patterns such as:
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<Comp>
|
||||
{|p: RenderProps| html!{<>{"Hello, "}{p.name}</>}}
|
||||
</Comp>
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Agents: A Complete Rewrite
|
||||
|
||||
Yew 0.21 brings a complete rewrite of `yew-agent`. This streamlines and simplifies the way workers operate. Here's what you need to know:
|
||||
|
||||
- **Introducing Providers:** Say goodbye to the old `Worker::bridge()` method. Now, you can use the use `WorkerProvider` / `ReactorProvider` / `OneshotProvider` as per your need, by creating them using the hooks.
|
||||
|
||||
- **WorkerLink to WorkerScope:** We've removed WorkerLink in favor of WorkerScope. This change simplifies the worker architecture, making it more straightforward to manage and maintain.
|
||||
|
||||
There are now 3 types of agents to be used, depending upon the situation:
|
||||
|
||||
- **Worker Agent:** The original agent that uses an actor model, designed to handle complex states.
|
||||
|
||||
- **Oneshot Agent:** Designed for scenarios where you expect a single input and a single output for each agent.
|
||||
|
||||
- **Reactor Agent:** Ideal for situations where multiple inputs and multiple outputs are needed for each agent.
|
||||
|
||||
Learn more in [documentation of yew-agent](https://docs.rs/yew-agent/latest/yew_agent/)
|
||||
|
||||
### 4. Performance Improvements: A Faster and Smoother Experience
|
||||
|
||||
Yew 0.21 brings substantial performance improvements. Your web applications will run faster and more efficiently, thanks to optimizations that reduce memory usage and enhance rendering.
|
||||
|
||||
## Call for Contributors
|
||||
|
||||
The Yew project thrives on community involvement, and we welcome contributors with open arms. Whether you're an experienced Rust developer or just starting your journey, there are plenty of ways to get involved and make a meaningful impact on Yew's growth.
|
||||
|
||||
Here are some areas where you can contribute:
|
||||
|
||||
- **Code Contributions:** If you're passionate about web development with Rust, consider contributing code to Yew. Whether it's fixing bugs, adding new features, or improving documentation, your code can help make Yew even better.
|
||||
|
||||
- **Documentation:** Clear and comprehensive documentation is vital for any project's success. You can contribute by improving documentation, writing tutorials, or creating examples that help others understand and use Yew effectively.
|
||||
|
||||
- **Testing and Bug Reporting:** Testing Yew and reporting bugs you encounter is a valuable contribution. Your feedback helps us identify and fix issues, ensuring a more stable framework for everyone.
|
||||
|
||||
- **Community Support:** Join discussions, chat rooms (we have our own Discord and Matrix!), or social media to assist other developers using Yew. Sharing your knowledge and helping others solve problems is a fantastic way to contribute.
|
||||
|
||||
Contributing to open-source projects like Yew is not only a way to give back to the community but also an excellent opportunity to learn, collaborate, and enhance your skills.
|
||||
|
||||
To get started, check out the Yew GitHub repository and the contribution guidelines. Your contributions are highly appreciated and play a crucial role in shaping the future of Yew. Join us in this exciting journey!
|
||||
|
||||
## Thanks!
|
||||
|
||||
Many people came together to create Yew 0.21. We couldn't have done it without all of you. Thanks!
|
||||
|
||||
See [the full changelog](https://github.com/yewstack/yew/blob/master/CHANGELOG.md)
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
title: 'From 0.20.0 to 0.21.0'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
## Dependencies as first hook argument and `use_effect_with`
|
||||
|
||||
- Replace `use_effect_with_deps` with new `use_effect_with`
|
||||
- `use_effect_with`, `use_callback`, `use_memo` now take dependencies as their first argument
|
||||
|
||||
### Automated refactor
|
||||
|
||||
With the help of [https://ast-grep.github.io](https://ast-grep.github.io/guide/quick-start.html)
|
||||
Here are commands that can do the refactoring for you.
|
||||
|
||||
```bash
|
||||
sg --pattern 'use_effect_with_deps($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_effect_with($$$DEPENDENCIES, $CALLBACK)' -l rs -i
|
||||
sg --pattern 'use_effect_with($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_effect_with($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
|
||||
sg --pattern 'use_callback($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_callback($$$DEPENDENCIES, $CALLBACK)' -l rs -i
|
||||
sg --pattern 'use_callback($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_callback($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
|
||||
sg --pattern 'use_memo($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_memo($$$DEPENDENCIES, $CALLBACK)' -l rs -i
|
||||
sg --pattern 'use_memo($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_memo($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
|
||||
sg --pattern 'use_future_with_deps($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_effect_with($$$DEPENDENCIES, $CALLBACK)' -l rs -i
|
||||
sg --pattern 'use_future_with($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_effect_with($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
|
||||
sg --pattern 'use_transitive_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_transitive_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
sg --pattern 'use_transitive_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_transitive_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
|
||||
sg --pattern 'use_prepared_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_prepared_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
sg --pattern 'use_prepared_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_prepared_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
|
||||
```
|
||||
|
||||
### Reasoning
|
||||
|
||||
This will enable more ergonomic use of hooks, consider:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="before" label="Before" default>
|
||||
|
||||
```rust ,ignore
|
||||
impl SomeLargeStruct {
|
||||
fn id(&self) -> u32; // Only need to use the id as cache key
|
||||
}
|
||||
let some_dep: SomeLargeStruct = todo!();
|
||||
|
||||
{
|
||||
let id = some_dep.id(); // Have to extract it in advance, some_dep is moved already in the second argument
|
||||
use_effect_with_dep(move |_| { todo!(); drop(some_dep); }, id);
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After">
|
||||
|
||||
```rust ,ignore
|
||||
impl SomeLargeStruct {
|
||||
fn id(&self) -> u32; // Only need to use the id as cache key
|
||||
}
|
||||
let some_dep: SomeLargeStruct = todo!();
|
||||
|
||||
use_effect_with(some_dep.id(), move |_| { todo!(); drop(some_dep); });
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
|
@ -119,8 +119,8 @@ module.exports = {
|
|||
prism: {
|
||||
additionalLanguages: ['rust', 'toml'],
|
||||
},
|
||||
googleAnalytics: {
|
||||
trackingID: 'UA-141789564-1',
|
||||
gtag: {
|
||||
trackingID: 'G-DENCL8P4YP',
|
||||
anonymizeIP: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"version.label": {
|
||||
"message": "0.21",
|
||||
"description": "The label for version 0.21"
|
||||
},
|
||||
"sidebar.docs.category.Getting Started": {
|
||||
"message": "Getting Started",
|
||||
"description": "The label for category Getting Started in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts": {
|
||||
"message": "Concepts",
|
||||
"description": "The label for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts.link.generated-index.title": {
|
||||
"message": "Yew concepts",
|
||||
"description": "The generated-index page title for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts.link.generated-index.description": {
|
||||
"message": "Learn about the important Yew concepts!",
|
||||
"description": "The generated-index page description for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew": {
|
||||
"message": "Using Basic Web Technologies In Yew",
|
||||
"description": "The label for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew.link.generated-index.title": {
|
||||
"message": "Yew's Take on Basic Web Technologies",
|
||||
"description": "The generated-index page title for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew.link.generated-index.description": {
|
||||
"message": "Yew centrally operates on the idea of keeping everything that a reusable piece of UI may needin one place - rust files, while also keeping the underlying technology accessible where necessary. Explore further to fully grasp what we mean by these statements:",
|
||||
"description": "The generated-index page description for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Components": {
|
||||
"message": "Components",
|
||||
"description": "The label for category Components in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Hooks": {
|
||||
"message": "Hooks",
|
||||
"description": "The label for category Hooks in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.HTML": {
|
||||
"message": "HTML",
|
||||
"description": "The label for category HTML in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics": {
|
||||
"message": "Advanced topics",
|
||||
"description": "The label for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics.link.generated-index.title": {
|
||||
"message": "Advanced topics",
|
||||
"description": "The generated-index page title for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics.link.generated-index.description": {
|
||||
"message": "Learn about the advanced topics and inner workings of Yew!",
|
||||
"description": "The generated-index page description for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Struct Components": {
|
||||
"message": "Struct Components",
|
||||
"description": "The label for category Struct Components in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.More": {
|
||||
"message": "More",
|
||||
"description": "The label for category More in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.More.link.generated-index.title": {
|
||||
"message": "Miscellaneous",
|
||||
"description": "The generated-index page title for category More in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Migration guides": {
|
||||
"message": "Migration guides",
|
||||
"description": "The label for category Migration guides in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew": {
|
||||
"message": "yew",
|
||||
"description": "The label for category yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew-agent": {
|
||||
"message": "yew-agent",
|
||||
"description": "The label for category yew-agent in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew-router": {
|
||||
"message": "yew-router",
|
||||
"description": "The label for category yew-router in sidebar docs"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: How it works
|
||||
description: Low level details about the framework
|
||||
---
|
||||
|
||||
# 低レベルなライブラリの中身
|
||||
|
||||
コンポーネントのライフサイクルの状態機械、VDOM の異なるアルゴリズム
|
|
@ -0,0 +1,171 @@
|
|||
---
|
||||
title: Optimizations
|
||||
description: Make your app faster
|
||||
---
|
||||
|
||||
# 最適化とベストプラクティス
|
||||
|
||||
## neq_assign
|
||||
|
||||
親コンポーネントから props を受け取った際、`change`メソッドが呼ばれます。
|
||||
これはコンポーネントの状態を更新することができるのに加え、コンポーネントが props が変わった際に再レンダリングするかどうかを決める
|
||||
`ShouldRender`という真偽値を返すことができます。
|
||||
|
||||
再レンダリングはコストがかかるもので、もし避けられるのであれば避けるべきです。
|
||||
一般的なルールとして props が実際に変化した際にのみ再レンダリングすれば良いでしょう。
|
||||
以下のコードブロックはこのルールを表しており、props が前と変わったときに`true`を返します。
|
||||
|
||||
```rust
|
||||
use yew::ShouldRender;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct ExampleProps;
|
||||
|
||||
struct Example {
|
||||
props: ExampleProps,
|
||||
};
|
||||
|
||||
impl Example {
|
||||
fn change(&mut self, props: ExampleProps) -> ShouldRender {
|
||||
if self.props != props {
|
||||
self.props = props;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
しかし我々は先に進んでいけます!
|
||||
この 6 行のボイラープレードは`PartialEq`を実装したものにトレイトとブランケットを用いることで 1 行のコードへと落とし込むことができます。
|
||||
[こちら](https://docs.rs/yewtil/*/yewtil/trait.NeqAssign.html)にて`yewtil`クレートの`NewAssign`トレイトを見てみてください。
|
||||
|
||||
## 効果的にスマートポインタを使う
|
||||
|
||||
**注意: このセクションで使われている用語がわからなければ Rust book は
|
||||
[スマートポインタについての章](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html)
|
||||
があり、非常に有用です。**
|
||||
|
||||
再レンダリングの際に props を作るデータを大量にコピーしないために、スマートポインタを用いてデータ自体ではなくデータへの参照だけを
|
||||
コピーできます。
|
||||
props や子コンポーネントで関連するデータに実データではなく参照を渡すと、子コンポーネントでデータを変更する必要がなければ
|
||||
データのコピーを避けることができます。
|
||||
その際、`Rc::make_mut`によって変更したいデータの変更可能な参照を得ることができます。
|
||||
|
||||
これにより、props が変更されたときにコンポーネントが再レンダリングされるかどうかを決めるかで`Component::change`に更なる恩恵があります。
|
||||
なぜなら、データの値を比較する代わりに元々のポインタのアドレス (つまりデータが保管されている機械のメモリの場所) を比較できるためです。
|
||||
2 つのポインターが同じデータを指す場合、それらのデータの値は同じでなければならないのです。
|
||||
ただし、その逆は必ずしも成り立たないことに注意してください!
|
||||
もし 2 つのポインタが異なるのであれば、そのデータは同じである可能性があります。
|
||||
この場合はデータを比較するべきでしょう。
|
||||
|
||||
この比較においては`PartialEq`ではなく`Rc::ptr_eq`を使う必要があります。
|
||||
`PartialEq`は等価演算子`==`を使う際に自動的に使われます。
|
||||
Rust のドキュメントには[`Rc::ptr_eq`についてより詳しく書いてあります](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq)。
|
||||
|
||||
この最適化は`Copy`を実装していないデータの型に対して極めて有効なものです。
|
||||
もしデータを簡単に変更できるのであれば、スマートポインタに取り換える必要はありません。
|
||||
しかし`Vec`や`HashMap`、`String`などのような重たいデータの構造体に対してはスマートポインタを使うことで
|
||||
パフォーマンスを改善することができるでしょう。
|
||||
|
||||
この最適化は値がまだ一度も子によって更新されていない場合に極めて有効で、親からほとんど更新されない場合においてもかなり有効です。
|
||||
これにより、`Rc<_>s`が純粋なコンポーネントに対してプロパティの値をラップする良い一手となります。
|
||||
|
||||
## View 関数
|
||||
|
||||
コードの可読性の理由から`html!`の部分を関数へと移植するのは意味があります。
|
||||
これは、インデントを減らすのでコードを読みやすくするだけでなく、良いデザインパターンを産むことにも繋がるのです。
|
||||
これらの関数は複数箇所で呼ばれて書くべきコード量を減らせるため、分解可能なアプリケーションを作ることができるのです。
|
||||
|
||||
## 純粋なコンポーネント
|
||||
|
||||
純粋なコンポーネントは状態を変化せず、ただ中身を表示してメッセージを普通の変更可能なコンポーネントへ渡すコンポーネントのことです。
|
||||
View 関数との違いとして、純粋なコンポーネントは式の構文\(`{some_view_function()}`\)ではなく
|
||||
コンポーネントの構文\(`<SomePureComponent />`\)を使うことで`html!`マクロの中で呼ばれる点、
|
||||
そして実装次第で記憶され (つまり、一度関数が呼ばれれば値は"保存"され、
|
||||
同じ引数でもう一度呼ばれても値を再計算する必要がなく最初に関数が呼ばれたときの保存された値を返すことができる)、
|
||||
先述の`neq_assign`ロジックを使う別々の props で再レンダリングを避けられる点があります。
|
||||
|
||||
Yew は純粋な関数やコンポーネントをサポートしていませんが、外部のクレートを用いることで実現できます。
|
||||
|
||||
## 関数型コンポーネント (a.k.a フック)
|
||||
|
||||
関数型コンポーネントはまだ開発中です!
|
||||
開発状況については[プロジェクトボード](https://github.com/yewstack/yew/projects/3)に詳しく書いてあります。
|
||||
|
||||
## キー付き DOM ノード
|
||||
|
||||
## ワークスペースでコンパイル時間を減らす
|
||||
|
||||
間違いなく Yew を使う上での最大の欠点はコンパイルに時間がかかる点です。
|
||||
プロジェクトのコンパイルにかかる時間は`html!`マクロに渡されるコードの量に関係しています。
|
||||
これは小さなプロジェクトにはそこまで問題ないようですが、大きなアプリではコードを複数クレートに分割することでアプリに変更が加られた際に
|
||||
コンパイラの作業量を減らすのが有効です。
|
||||
|
||||
一つ可能なやり方として、ルーティングとページ洗濯を担当するメインのクレートを作り、それぞれのページに対して別のクレートを作ることです。
|
||||
そうして各ページは異なるコンポーネントか、`Html`を生成する大きな関数となります。
|
||||
アプリの異なる部分を含むクレート同士で共有されるコードはプロジェクト全体で依存する分離したクレートに保存されます。
|
||||
理想的には 1 回のコンパイルでコード全てを再ビルドせずメインのクレートかどれかのページのクレートを再ビルドするだけにすることです。
|
||||
最悪なのは、"共通"のクレートを編集して、はじめに戻ってくることです:
|
||||
共有のクレートに依存している全てのコード、恐らく全てのコードをコンパイルすることです。
|
||||
|
||||
もしメインのクレートが重たすぎる、もしくは深くネストしたページ (例えば別ページのトップでレンダリングされるページ)
|
||||
で速く繰り返したい場合、クレートの例を用いてメインページの実装をシンプルにしたりトップで動かしているコンポーネントをレンダリングできます。
|
||||
|
||||
## バイナリサイズを小さくする
|
||||
|
||||
- Rust のコードを最適化する
|
||||
- `cargo.toml` \( release profile を定義 \)
|
||||
- `wasm-opt`を用いて wasm のコードを最適化する
|
||||
|
||||
**注意: バイナリサイズを小さくするのについては[Rust Wasm Book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size)に詳しく書いてあります。**
|
||||
|
||||
### Cargo.toml
|
||||
|
||||
`Cargo.toml`で`[profile.release]`のセクションに設定を書き込むことでリリースビルドを小さくすることが可能です。
|
||||
|
||||
```text
|
||||
[profile.release]
|
||||
# バイナリに含むコードを少なくする
|
||||
panic = 'abort'
|
||||
# コードベース全体での最適化 ( 良い最適化だがビルドが遅くなる)
|
||||
codegen-units = 1
|
||||
# サイズの最適化( よりアグレッシブに )
|
||||
opt-level = 'z'
|
||||
# サイズの最適化
|
||||
# opt-level = 's'
|
||||
# プログラム全体の分析によるリンク時最適化
|
||||
lto = true
|
||||
```
|
||||
|
||||
### wasm-opt
|
||||
|
||||
更に`wasm`のコードのサイズを最適化することができます。
|
||||
|
||||
The Rust Wasm Book には Wasm バイナリのサイズを小さくすることについてのセクションがあります:
|
||||
[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html)
|
||||
|
||||
- `wasm-pack`でデフォルトの`wasm`のコードをリリースビルド時に最適化する
|
||||
- `wasm-opt`によって直接`wasm`ファイルを最適化する
|
||||
|
||||
```text
|
||||
wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm
|
||||
```
|
||||
|
||||
#### yew/examples/にある例を小さなサイズでビルドする
|
||||
|
||||
注意: `wasm-pack`は Rust と Wasm のコードへの最適化を組み合わせます。`wasm-bindgen`はこの例では Rust のサイズ最適化を用いていません。
|
||||
|
||||
| 使用したツール | サイズ |
|
||||
| :-------------------------- | :----- |
|
||||
| wasm-bindgen | 158KB |
|
||||
| wasm-bindgen + wasm-opt -Os | 116KB |
|
||||
| wasm-pack | 99 KB |
|
||||
|
||||
## 参考文献:
|
||||
|
||||
- [The Rust Book のスマートポインタに関する章](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html)
|
||||
- [the Rust Wasm Book でのバイナリサイズを小さくすることについて](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size)
|
||||
- [Rust profiles についてのドキュメント](https://doc.rust-lang.org/cargo/reference/profiles.html)
|
||||
- [binaryen プロジェクト](https://github.com/WebAssembly/binaryen)
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: Callbacks
|
||||
description: ComponentLink and Callbacks
|
||||
---
|
||||
|
||||
”リンク”コンポーネントはコンポーネントがコールバックを登録できて自身を更新することができるメカニズムです。
|
||||
|
||||
## ComponentLink API
|
||||
|
||||
### callback
|
||||
|
||||
実行時にコンポーネントの更新メカニズムにメッセージを送信するコールバックを登録します。
|
||||
これは、渡されたクロージャから返されるメッセージで `send_self` を呼び出します。
|
||||
`Fn(IN) -> Vec<COMP::Message>`が渡され、`Callback<IN>`が返されます。
|
||||
|
||||
### send_message
|
||||
|
||||
現在のループが終了した直後にコンポーネントにメッセージを送信し、別の更新ループを開始します。
|
||||
|
||||
### send_message_batch
|
||||
|
||||
実行時に一度に多数のメッセージを一括して送信するコールバックを登録します。
|
||||
メッセージによってコンポーネントが再レンダリングされる場合、バッチ内のすべてのメッセージが処理された後、コンポーネントは再レンダリングされます。
|
||||
`Fn(IN) -> COMP::Message`が渡され、`Callback<IN>`が返されます。
|
||||
|
||||
## コールバック
|
||||
|
||||
_\(This might need its own short page.\)_
|
||||
|
||||
コールバックは、Yew 内のサービス、エージェント、親コンポーネントとの通信に使われます。
|
||||
これらは単に `Fn` を `Rc` でラップしただけであり、クローンを作成できるようにするためのものです。
|
||||
|
||||
これらの関数には `emit` 関数があり、`<IN>` 型を引数に取り、それをアドレスが欲しいメッセージに変換します。
|
||||
親からのコールバックが子コンポーネントに props で提供されている場合、子は `update` ライフサイクルフックで `emit` をコールバックに呼び出して親にメッセージを返すことができます。
|
||||
マクロ内で props として提供されたクロージャや関数は自動的にコールバックに変換されます。
|
|
@ -0,0 +1,198 @@
|
|||
---
|
||||
title: Introduction
|
||||
description: Components and their lifecycle hooks
|
||||
---
|
||||
|
||||
## コンポーネントとは?
|
||||
|
||||
コンポーネントは Yew を構成するブロックです。
|
||||
コンポーネントは状態を管理し、自身を DOM へレンダリングすることができます。
|
||||
コンポーネントはライフサイクルの機能がある`Component`トレイトを実装することによって作られます。
|
||||
|
||||
## ライフサイクル
|
||||
|
||||
:::important contribute
|
||||
`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/docs/issues/22)
|
||||
:::
|
||||
|
||||
## ライフサイクルのメソッド
|
||||
|
||||
### Create
|
||||
|
||||
コンポーネントが作られると、`ComponentLink`と同様に親コンポーネントからプロパティを受け取ります。
|
||||
プロパティはコンポーネントの状態を初期化するのに使われ、"link"はコールバックを登録したりコンポーネントにメッセージを送るのに使われます。
|
||||
|
||||
props と link をコンポーネント構造体に格納するのが一般的です。
|
||||
例えば:
|
||||
|
||||
```rust
|
||||
pub struct MyComponent {
|
||||
props: Props,
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Properties = Props;
|
||||
// ...
|
||||
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
MyComponent { props, link }
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
コンポーネントは`view()`メソッドによってレイアウトを宣言します。
|
||||
Yew は`html!`マクロによって HTML と SVG ノード、リスナー、子コンポーネントを宣言できます。
|
||||
マクロは React の JSX のような動きをしますが、JavaScript の代わりに Rust の式を用います。
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let onclick = self.link.callback(|_| Msg::Click);
|
||||
html! {
|
||||
<button {onclick}>{ self.props.button_text }</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使い方については[`html!`ガイド](concepts/html/introduction.mdx)をご確認ください。
|
||||
|
||||
### Rendered
|
||||
|
||||
`rendered()`コンポーネントのライフサイクルのメソッドは`view()`が処理されたて Yew がコンポーネントをレンダリングした後、
|
||||
ブラウザがページを更新する前に呼ばれます。
|
||||
コンポーネントは、コンポーネントが要素をレンダリングした後にのみ実行できるアクションを実行するため、このメソッドを実装したい場合があります。
|
||||
コンポーネントが初めてレンダリングされたかどうかは `first_render` パラメータで確認できます。
|
||||
|
||||
```rust
|
||||
use stdweb::web::html_element::InputElement;
|
||||
use stdweb::web::IHtmlElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct MyComponent {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<input ref={self.node_ref.clone()} type="text" />
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, first_render: bool) {
|
||||
if first_render {
|
||||
if let Some(input) = self.node_ref.try_into::<InputElement>() {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip note
|
||||
ライフサイクルメソッドは実装の必要がなく、デフォルトでは何もしません。
|
||||
:::
|
||||
|
||||
### Update
|
||||
|
||||
コンポーネントは動的で、非同期メッセージを受信するために登録することができます。
|
||||
ライフサイクルメソッド `update()` はメッセージごとに呼び出されます。
|
||||
これにより、コンポーネントはメッセージが何であったかに基づいて自身を更新し、自身を再レンダリングする必要があるかどうかを判断することができます。
|
||||
メッセージは、HTML 要素リスナーによってトリガーされたり、子コンポーネント、エージェント、サービス、または Futures によって送信されたりします。
|
||||
|
||||
`update()`がどのようなのかについての例は以下の通りです:
|
||||
|
||||
```rust
|
||||
pub enum Msg {
|
||||
SetInputEnabled(bool)
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
|
||||
// ...
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::SetInputEnabled(enabled) => {
|
||||
if self.input_enabled != enabled {
|
||||
self.input_enabled = enabled;
|
||||
true // Re-render
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change
|
||||
|
||||
コンポーネントは親によって再レンダリングされることがあります。
|
||||
このような場合、新しいプロパティを受け取り、再レンダリングを選択する可能性があります。
|
||||
この設計では、プロパティを変更することで、親から子へのコンポーネントの通信が容易になります。
|
||||
|
||||
典型的な実装例は以下の通りです:
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
if self.props != props {
|
||||
self.props = props;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Destroy
|
||||
|
||||
コンポーネントが DOM からアンマウントされた後、Yew は `destroy()` ライフサイクルメソッドを呼び出し、必要なクリーンアップ操作をサポートします。
|
||||
このメソッドはオプションで、デフォルトでは何もしません。
|
||||
|
||||
## Associated Types
|
||||
|
||||
`Component`トレイトは 2 つの関連型があります: `Message`と`Properties`です。
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`Message`はコンポーネントによって処理され、何らかの副作用を引き起こすことができるさまざまなメッセージを表します。
|
||||
例えば、API リクエストをトリガーしたり、UI コンポーネントの外観を切り替えたりする `Click` メッセージがあります。
|
||||
コンポーネントのモジュールで `Msg` という名前の列挙型を作成し、それをコンポーネントのメッセージ型として使用するのが一般的です。
|
||||
"message"を"msg"と省略するのも一般的です。
|
||||
|
||||
```rust
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
```
|
||||
|
||||
`Properties`は、親からコンポーネントに渡される情報を表します。
|
||||
この型は Properties trait を実装していなければならず\(通常はこれを派生させることで\)、特定のプロパティが必須かオプションかを指定することができます。
|
||||
この型は、コンポーネントの作成・更新時に使用されます。
|
||||
コンポーネントのモジュール内に `Props` という構造体を作成し、それをコンポーネントの `Properties` 型として使用するのが一般的です。
|
||||
”Properties”を"props"に短縮するのが一般的です。
|
||||
Props は親コンポーネントから継承されるので、アプリケーションのルートコンポーネントは通常`()`型の`Properties`を持ちます。
|
||||
ルートコンポーネントのプロパティを指定したい場合は、`App::mount_with_props`メソッドを利用します。
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: Properties
|
||||
description: Parent to child communication
|
||||
---
|
||||
|
||||
プロパティは、子コンポーネントと親コンポーネントが互いに通信できるようにします。
|
||||
|
||||
## マクロの継承
|
||||
|
||||
`Properties`を自分で実装しようとせず、代わりに`#[derive(Properties)]`を使ってください。
|
||||
|
||||
:::note
|
||||
`Properties`を継承した型は`Clone`を実装していなければいけません。
|
||||
これは`#[derive(Properties, Clone)`か`Clone`を手で実装することで可能です。
|
||||
:::
|
||||
|
||||
### 必要な属性
|
||||
|
||||
デフォルトでは、`Properties` を導出する構造体内のフィールドは必須です。
|
||||
フィールドが欠落していて `html!` マクロでコンポーネントが作成された場合、コンパイラエラーが返されます。
|
||||
オプションのプロパティを持つフィールドについては、`#[prop_or_default]` 属性を使用して、prop が指定されていない場合はその型のデフォルト値を使用します。
|
||||
値を指定するには `#[prop_or(value)]` 属性を用います。
|
||||
ここで value はプロパティのデフォルト値、あるいは代わりに `#[prop_or_else(function)]` を使用して、`function` はデフォルト値を返します。
|
||||
例えば、ブール値のデフォルトを `true` とするには、属性 `#[prop_or(true)]` を使用します。オプションのプロパティでは、デフォルト値 `None` を持つ `Option` 列挙型を使うのが一般的です。
|
||||
|
||||
### PartialEq
|
||||
|
||||
もし可能なら props で `PartialEq` を継承するのが良いかもしれません。
|
||||
`PartialEq`を使うことで、不必要な再レンダリングを避けることができます
|
||||
(これについては、**最適化とベストプラクティス**のセクションで説明しています)。
|
||||
|
||||
## プロパティを使用する際のメモリと速度のオーバーヘッド
|
||||
|
||||
`Compoenent::view`ではコンポーネントの状態への参照を取り、それを使って `Html` を作成します。
|
||||
しかし、プロパティは自身の値です。
|
||||
つまり、それらを作成して子コンポーネントに渡すためには、`view` 関数で提供される参照を所有する必要があるのです。
|
||||
これは所有する値を取得するためにコンポーネントに渡される参照を暗黙のうちにクローンすることで行われます。
|
||||
|
||||
これは、各コンポーネントが親から受け継いだ状態の独自のコピーを持っていることを意味し、コンポーネントを再レンダリングするときはいつでも、再レンダリングしたコンポーネントのすべての子コンポーネントの props がクローンされなければならないことを意味します。
|
||||
|
||||
このことの意味するところは、もしそうでなければ*大量の*データ \(10KB もあるような文字列\) を props として渡してしまうのであれば、子コンポーネントを親が呼び出す `Html` を返す関数にすることを考えた方がいいかもしれないということです。
|
||||
|
||||
props を介して渡されたデータを変更する必要がない場合は、実際のデータそのものではなく、データへの参照カウントされたポインタのみが複製されるように `Rc` でラップすることができます。
|
||||
|
||||
## 例
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use yew::Properties;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum LinkColor {
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
Black,
|
||||
Purple,
|
||||
}
|
||||
|
||||
impl Default for LinkColor {
|
||||
fn default() -> Self {
|
||||
// The link color will be blue unless otherwise specified.
|
||||
LinkColor::Blue
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
/// The link must have a target.
|
||||
href: String,
|
||||
/// If the link text is huge, this will make copying the string much cheaper.
|
||||
/// This isn't usually recommended unless performance is known to be a problem.
|
||||
text: Rc<String>,
|
||||
/// Color of the link.
|
||||
#[prop_or_default]
|
||||
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,
|
||||
}
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: Refs
|
||||
description: Out-of-band DOM access
|
||||
---
|
||||
|
||||
`ref`は、任意の HTML 要素やコンポーネントの内部で、割り当てられている DOM`Element`を取得するために使用することができます。
|
||||
これは、`view` ライフサイクルメソッドの外で DOM に変更を加えるために使用できます。
|
||||
|
||||
これは、キャンバスの要素を取得したり、ページの異なるセクションにスクロールしたりするのに便利です。
|
||||
|
||||
構文は以下の通りです:
|
||||
|
||||
```rust
|
||||
// In create
|
||||
self.node_ref = NodeRef::default();
|
||||
|
||||
// In view
|
||||
html! {
|
||||
<div ref={self.node_ref.clone()}></div>
|
||||
}
|
||||
|
||||
// In update
|
||||
let has_attributes = self.node_ref.try_into::<Element>().has_attributes();
|
||||
```
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: Agents
|
||||
description: Yew's Actor System
|
||||
---
|
||||
|
||||
エージェントは Angular の[サービス](https://angular.io/guide/architecture-services)に似ており\(ただし依存性インジェクションはありません\)、
|
||||
[アクターモデル](https://en.wikipedia.org/wiki/Actor_model)を提供します。
|
||||
エージェントはコンポーネント階層のどこに位置するかに関わらず、コンポーネント間でメッセージをルーティングしたり、共有状態を作成したり、UI をレンダリングするメインスレッドから計算量の多いタスクをオフロードするために使用することができます。
|
||||
また、Yew アプリケーションがタブをまたいで通信できるようにするためのエージェントのサポートも\(将来的には\)計画されています。
|
||||
|
||||
エージェントが並行に動くように Yew は[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)を使用しています。
|
||||
|
||||
## ライフサイクル
|
||||
|
||||

|
||||
|
||||
## エージェントの種類
|
||||
|
||||
### Reaches
|
||||
|
||||
- Context - Context エージェントのインスタンスは、常に最大 1 つ存在します。
|
||||
Bridges は、UI スレッド上で既にスポーンされたエージェントをスポーンするか、接続します。
|
||||
これは、コンポーネントまたは他のエージェント間の状態を調整するために使用することができます。
|
||||
このエージェントに Bridges が接続されていない場合、このエージェントは消滅します。
|
||||
|
||||
- Job - 新しいブリッジごとに UI スレッド上で新しいエージェントをスポーンします。
|
||||
これは、ブラウザと通信する共有されているが独立した動作をコンポーネントの外に移動させるのに適しています。
|
||||
(TODO 確認) タスクが完了すると、エージェントは消えます。
|
||||
|
||||
- Public - Context と同じですが、独自の web worker で動作します。
|
||||
|
||||
- Private - Job と同じですが、独自の web worker で動作します。
|
||||
|
||||
- Global \(WIP\)
|
||||
|
||||
## エージェントとコンポーネントのやり取り
|
||||
|
||||
### Bridges
|
||||
|
||||
Bridge は、エージェントとコンポーネント間の双方向通信を可能にします。
|
||||
また、Bridge はエージェント同士の通信を可能にします。
|
||||
|
||||
### Dispatchers
|
||||
|
||||
Dispatcher は、コンポーネントとエージェント間の一方向通信を可能にします。
|
||||
Dispatcher は、コンポーネントがエージェントにメッセージを送信することを可能にします。
|
||||
|
||||
## オーバーヘッド
|
||||
|
||||
独自の独立した web worker(プライベートとパブリック)にあるエージェントは、送受信するメッセージにシリアライズするオーバーヘッドが発生します。
|
||||
他のスレッドとの通信には[bincode](https://github.com/servo/bincode)を使用するので、関数を呼び出すよりもコストはかなり高くなります。
|
||||
計算コストがメッセージの受け渡しコストを上回る場合を除き、ロジックを UI スレッドエージェント\(Job または Context\)に格納する必要があります。
|
||||
|
||||
## 参考資料
|
||||
|
||||
- [web_worker_fib](https://github.com/yewstack/yew/tree/master/examples/web_worker_fib)の例でコンポーネントがどのようにエージェントと通信させているかがわかります。
|
|
@ -0,0 +1,108 @@
|
|||
---
|
||||
title: Components
|
||||
description: Create complex layouts with component hierarchies
|
||||
---
|
||||
|
||||
## 基本
|
||||
|
||||
`Component`を実装しているあらゆる型は`html!`マクロの中で使えます:
|
||||
|
||||
```rust
|
||||
html!{
|
||||
<>
|
||||
// No properties
|
||||
<MyComponent />
|
||||
|
||||
// With Properties
|
||||
<MyComponent prop1="lorem" prop2="ipsum" />
|
||||
|
||||
// With the whole set of props provided at once
|
||||
<MyComponent ..props />
|
||||
|
||||
// With Properties from a variable and specific values overridden
|
||||
<MyComponent prop2="lorem" ..props />
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## ネスト
|
||||
|
||||
`children`フィールドが`Properties`の中にある場合はコンポーネントは子に渡されます。
|
||||
|
||||
```rust title="parent.rs"
|
||||
html! {
|
||||
<Container>
|
||||
<h4>{ "Hi" }</h4>
|
||||
<div>{ "Hello" }</div>
|
||||
</Container>
|
||||
}
|
||||
```
|
||||
|
||||
```rust title="container.rs"
|
||||
pub struct Container(Props);
|
||||
|
||||
#[derive(Properties, Clone)]
|
||||
pub struct Props {
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
impl Component for Container {
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div id="container">
|
||||
{ self.0.children.clone() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
`Properties`を継承した型は`Clone`を実装していなければいけません。
|
||||
これは`#[derive(Properties, Clone)]`を使うか手で`Clone`を実装すれば良いです。
|
||||
:::
|
||||
|
||||
## Props とネストした子コンポーネント
|
||||
|
||||
ネストしたコンポーネントのプロパティは格納しているコンポーネントの型が子である場合はアクセス可能、または変更可能です。
|
||||
以下の例では`List`コンポーネントは`ListItem`コンポーネントをラップできています。
|
||||
実際の使用においてこのパターンの例については`yew-router`のソースコードを確認してみてください。
|
||||
より進んだ例としては Yew のメインのリポジトリにある`nested-list`を確認してみてください。
|
||||
|
||||
```rust title="parent.rs"
|
||||
html! {
|
||||
<List>
|
||||
<ListItem value="a" />
|
||||
<ListItem value="b" />
|
||||
<ListItem value="c" />
|
||||
</List>
|
||||
}
|
||||
```
|
||||
|
||||
```rust title="list.rs"
|
||||
pub struct List(Props);
|
||||
|
||||
#[derive(Properties, Clone)]
|
||||
pub struct Props {
|
||||
pub children: ChildrenWithProps<ListItem>,
|
||||
}
|
||||
|
||||
impl Component for List {
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html!{{
|
||||
for self.0.children.iter().map(|mut item| {
|
||||
item.props.value = format!("item-{}", item.props.value);
|
||||
item
|
||||
})
|
||||
}}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,373 @@
|
|||
---
|
||||
title: Elements
|
||||
description: Both HTML and SVG elements are supported
|
||||
---
|
||||
|
||||
## タグ構造
|
||||
|
||||
要素のタグは`<... />`のような自己完結タグか、開始タグに対応した終了タグを持っている必要があります。
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Open - Close-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div id="my_div"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Invalid-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div id="my_div"> // <- MISSING CLOSE TAG
|
||||
}
|
||||
```
|
||||
|
||||
<!--Self-closing-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<input id="my_input" />
|
||||
}
|
||||
```
|
||||
|
||||
<!--Invalid-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<input id="my_input"> // <- MISSING SELF-CLOSE
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
:::note
|
||||
便利さのために、*普通は*終了タグを必要とする要素は自己完結タグとすることが**できます**。
|
||||
例えば`html! { <div class="placeholder" /> }`と書くのは有効です。
|
||||
:::
|
||||
|
||||
## 子
|
||||
|
||||
複雑にネストした HTML や SVG のレイアウトを書くのには以下のようにするのが楽です:
|
||||
\*\*
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--HTML-->
|
||||
|
||||
```rust
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
<!--SVG-->
|
||||
|
||||
```rust
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## クラス
|
||||
|
||||
要素へのクラスを特定する便利なやり方はたくさんあります:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Literal-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class="container"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Multiple-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class="container center-align"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Interpolated-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={format!("{}-container", size)}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Expression-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={self.classes()}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Tuple-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={("class-1", "class-2")}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Vector-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={vec!["class-1", "class-2"]}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## リスナー
|
||||
|
||||
リスナー属性はクロージャのラッパーである`Callback`に渡される必要があります。
|
||||
コールバックをどのように作るかはアプリをリスナーイベントにどう反応させたいかによります。
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Component handler-->
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
MyComponent { link }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Click => {
|
||||
// Handle Click
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// Create a callback from a component link to handle it in a component
|
||||
let click_callback = self.link.callback(|_: ClickEvent| Msg::Click);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--Agent Handler-->
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
worker: Dispatcher<MyWorker>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
MyComponent {
|
||||
worker: MyWorker::dispatcher()
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// Create a callback from a worker to handle it in another context
|
||||
let click_callback = self.worker.callback(|_: ClickEvent| WorkerMsg::Process);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--Other Cases-->
|
||||
|
||||
```rust
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
MyComponent
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// Create an ephemeral callback
|
||||
let click_callback = Callback::from(|| {
|
||||
ConsoleService::log("clicked!");
|
||||
});
|
||||
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## イベントの型
|
||||
|
||||
:::note
|
||||
以下のテーブルにある全てのイベントの型は`yew::events`で再エクスポートされています。
|
||||
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`
|
||||
or `stdweb` as dependencies in your crate because you won't end up using a version which conflicts with
|
||||
the version Yew specifies.
|
||||
:::
|
||||
|
||||
| イベント名 | `web_sys` イベント型 |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `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` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.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) |
|
||||
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.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) |
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: Introduction
|
||||
description: The procedural macro for generating HTML and SVG
|
||||
slug: /concepts/html
|
||||
---
|
||||
|
||||
`html!`マクロによって HTML と SVG のコードを宣言的に書くことができます。
|
||||
JSX \(HTML のようなコードを JavaScript 内部に書くことができる JavaScript の拡張\) に似ています。
|
||||
|
||||
**重要な注意**
|
||||
|
||||
1. `html!`マクロはルートの HTML ノードのみ受け付けます \([フラグメントかイテレータを使う](./lists.mdx)ことでやり取りできます\)
|
||||
2. 空の`html! {}`の呼び出しは可能ですが何もレンダリングしません
|
||||
3. リテラルはクオーテーションがつけられ、ブレースで囲う必要があります: `html! { "Hello, World" }`
|
||||
|
||||
:::note
|
||||
`html!`マクロはコンパイラのデフォルトの再帰の上限に簡単に達してしまいます。
|
||||
もしコンパイラエラーに遭遇した場合はその値を押し出すといいかもしれません。
|
||||
クレートのルート\(つまり、`lib.rs`か`main.rs`\)で`#![recursion_limit="1024"]`のような属性を使えば解決します。
|
||||
|
||||
詳しくは[公式ドキュメント](https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute)と[Stack Overflow の質問](https://stackoverflow.com/questions/27454761/what-is-a-crate-attribute-and-where-do-i-add-it)を見てみてください。
|
||||
:::
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Lists
|
||||
---
|
||||
|
||||
## フラグメント
|
||||
|
||||
`html!`マクロは常にルートノードが 1 つであることを要求します。
|
||||
この制限のために、空のタグを使って内容をラップすると良いでしょう。
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Valid-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<>
|
||||
<div></div>
|
||||
<p></p>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Invalid-->
|
||||
|
||||
```rust
|
||||
/* error: only one root html element allowed */
|
||||
|
||||
html! {
|
||||
<div></div>
|
||||
<p></p>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## イテレータ
|
||||
|
||||
Yew はイテレータから HTML をビルドするのに 2 つの方法をサポートしています。
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Syntax Type 1-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ self.props.items.iter().map(renderItem).collect::<Html>() }
|
||||
</ul>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Syntax Type 2-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ for self.props.items.iter().map(renderItem) }
|
||||
</ul>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Literals and Expressions
|
||||
---
|
||||
|
||||
## リテラル
|
||||
|
||||
式が`Display`を実装した型を解決する場合、文字列に変換されて DOM に[Text](https://developer.mozilla.org/en-US/docs/Web/API/Text)ノードとして挿入されます。
|
||||
|
||||
テキストは式として処理されるため、全ての表示される内容は`{}`ブロックによって囲まれる必要があります。
|
||||
これは Yew のアプリと通常の HTML の構文で最も異なる点です。
|
||||
|
||||
```rust
|
||||
let text = "lorem ipsum";
|
||||
html!{
|
||||
<>
|
||||
<div>{text}</div>
|
||||
<div>{"dolor sit"}</div>
|
||||
<span>{42}</span>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## 式
|
||||
|
||||
HTML に`{}`ブロックを使って式を挿入することができます。
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div>
|
||||
{
|
||||
if show_link {
|
||||
html! {
|
||||
<a href="https://example.com">{"Link"}</a>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
式を関数やクロージャに分離するのはコードの可読性の観点から有効なことがあります。
|
||||
|
||||
```rust
|
||||
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,95 @@
|
|||
---
|
||||
title: Router
|
||||
description: Yew's official router
|
||||
---
|
||||
|
||||
[crates.io にあるルータ](https://crates.io/crates/yew-router)
|
||||
|
||||
シングルページアプリケーション\(SPA\)におけるルータは URL よってページを出し分けます。
|
||||
リンクがクリックされたときに異なるリソースを要求するというデフォルトの動作の代わりに、ルータはアプリケーション内の有効なルートを指すように URL をローカルに設定します。
|
||||
ルータはこの変更を検出してから、何をレンダリングするかを決定します。
|
||||
|
||||
## コアとなる要素
|
||||
|
||||
### `Route`
|
||||
|
||||
URL 内のドメインの後のすべてを表す文字列と、オプションで history API に保存されている状態を含みます。
|
||||
|
||||
### `RouteService`
|
||||
|
||||
ブラウザとやりとりしてルーティングを決めます。
|
||||
|
||||
### `RouteAgent`
|
||||
|
||||
RouteService を所有し、ルートが変更された際の更新を調整するために使用します。
|
||||
|
||||
### `Switch`
|
||||
|
||||
`Switch`トレイトは`Route`をトレイトの実装する側の間で変換するために用いられます。
|
||||
|
||||
### `Router`
|
||||
|
||||
Router コンポーネントは RouteAgent とやり取りし、エージェントがどうスイッチするか Routes を自動的に解決します。
|
||||
これは、結果として得られるスイッチがどのように Html に変換されるかを指定できるようにするため、props を介して公開されます。
|
||||
|
||||
## ルータをどのように使うか
|
||||
|
||||
まず、アプリケーションのすべての状態を表す型を作成します。
|
||||
これは通常は列挙型ですが、構造体もサポートされており、`Switch` を実装した他のアイテムを内部に入れ子にすることができることに注意してください。
|
||||
|
||||
次に、`Switch`を型に継承させなければいけません。
|
||||
列挙型の場合は全ての variant は`#[to = "/some/route"]`とアノテーションされている必要があり、代わり構造体を用いている場合は構造体宣言が外部から見えるようにしてなければいけません。
|
||||
|
||||
```rust
|
||||
#[derive(Switch)]
|
||||
enum AppRoute {
|
||||
#[to="/login"]
|
||||
Login,
|
||||
#[to="/register"]
|
||||
Register,
|
||||
#[to="/delete_account"]
|
||||
Delete,
|
||||
#[to="/posts/{id}"]
|
||||
ViewPost(i32),
|
||||
#[to="/posts/view"]
|
||||
ViewPosts,
|
||||
#[to="/"]
|
||||
Home
|
||||
}
|
||||
```
|
||||
|
||||
:::caution
|
||||
`Switch`用の派生マクロによって生成された実装は、各 variant を最初から最後までの順にマッチさせようとするので、指定した`to`アノテーションのうち 2 つのルートにマッチする可能性がある場合は、最初のルートがマッチし、2 つ目のルートは試行されないことに注意してください。例えば、以下の`Switch`を定義した場合、マッチするルートは`AppRoute::Home`だけになります。
|
||||
|
||||
```rust
|
||||
#[derive(Switch)]
|
||||
enum AppRoute {
|
||||
#[to="/"]
|
||||
Home,
|
||||
#[to="/login"]
|
||||
Login,
|
||||
#[to="/register"]
|
||||
Register,
|
||||
#[to="/delete_account"]
|
||||
Delete,
|
||||
#[to="/posts/{id}"]
|
||||
ViewPost(i32),
|
||||
#[to="/posts/view"]
|
||||
ViewPosts,
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
また、`#[to = ""]`アノテーションの中で`{}`のバリエーションを使ってセクションをキャプチャすることもできます。
|
||||
`{}`は、次の区切り文字\(コンテキストに応じて "/", "?", "&", "#" のいずれか\) までのテキストをキャプチャします。
|
||||
`{*}`は、次の文字が一致するまでテキストをキャプチャすることを意味します。
|
||||
`{<number>}`は、指定した数の区切り文字が見つかるまでテキストをキャプチャすることを意味します
|
||||
\(例: `{2}`は区切り文字が 2 つ見つかるまでキャプチャします\)。
|
||||
|
||||
名前付きフィールドを持つ構造体や列挙型の場合は、キャプチャグループ内で以下のようにフィールドの名前を指定する必要があります。
|
||||
`{user_name}` または `{*:age}` のように、キャプチャグループ内でフィールドの名前を指定しなければなりません。
|
||||
|
||||
Switch トレイトは文字列よりも構造化されたキャプチャグループで動作します。
|
||||
`Switch`を実装した任意の型を指定することができます。
|
||||
そのため、キャプチャグループが `usize` であることを指定することができ、URL のキャプチャ部分がそれに変換できない場合、variant はマッチしません。
|
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
title: Build a sample app
|
||||
---
|
||||
|
||||
はじめに、Rust の新規ライブラリを作りましょう(**重要:** `--lib`フラグを渡すことで*バイナリ*ではなく*ライブラリ*を作ってください)
|
||||
|
||||
```bash
|
||||
cargo new --lib yew-app && cd yew-app
|
||||
```
|
||||
|
||||
依存ライブラリに`yew`と`wasm-bindgen`を追加してください \(最新バージョンについては[こちら](https://docs.rs/yew)を参照してください\)
|
||||
|
||||
```toml title="Cargo.toml"
|
||||
[package]
|
||||
name = "yew-app"
|
||||
version = "0.1.0"
|
||||
authors = ["Yew App Developer <name@example.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
yew = "0.17"
|
||||
wasm-bindgen = "0.2"
|
||||
```
|
||||
|
||||
以下のテンプレートを `src/lib.rs`ファイルにコピーしてください:
|
||||
|
||||
```rust title="src/lib.rs"
|
||||
use wasm_bindgen::prelude::*;
|
||||
use yew::prelude::*;
|
||||
|
||||
struct Model {
|
||||
link: ComponentLink<Self>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
AddOne,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Self {
|
||||
link,
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::AddOne => self.value += 1
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
||||
// Should only return "true" if new properties are different to
|
||||
// previously received properties.
|
||||
// This component has no properties so we will always return "false".
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<button onclick={self.link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
|
||||
<p>{ self.value }</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run_app() {
|
||||
App::<Model>::new().mount_to_body();
|
||||
}
|
||||
```
|
||||
|
||||
このテンプレートはルートに`Component`をセットアップし、`Model`と呼ばれるクリックしたら更新するボタンを作ります。
|
||||
`main()`の中にある`App::<Model>::new().mount_to_body()`がアプリをスタートしてページの`<body>`タグをマウントすることに特に注意してください。
|
||||
動的なプロパティでアプリをスタートしたい場合は代わりに`App::<Model>::new().mount_to_body_with_props(..)`を使うことで実現できます。
|
||||
|
||||
最後に、アプリの中の`static`という名前のフォルダに`index.html`ファイルを追加してください。
|
||||
|
||||
```bash
|
||||
mkdir static
|
||||
```
|
||||
|
||||
```markup title="index.html"
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Yew Sample App</title>
|
||||
<script type="module">
|
||||
import init from "./wasm.js"
|
||||
init()
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## アプリを動かす!
|
||||
|
||||
[`wasm-pack`](https://rustwasm.github.io/docs/wasm-pack/)を使うのがアプリを動かすのに推奨される方法です。
|
||||
まだ`wasm-pack`をインストールしていない場合、`cargo install wasm-pack`でインストールして開発サーバーを動かしてみましょう:
|
||||
|
||||
```bash
|
||||
wasm-pack build --target web --out-name wasm --out-dir ./static
|
||||
```
|
||||
|
||||
`wasm-pack`はコンパイルされた WebAssembly と JavaScript ラッパーをまとめたものを`./static`ディレクトリに作り、
|
||||
アプリの WebAssembly バイナリを読み込んで動かします。
|
||||
|
||||
そして、`./static`以下で好きなサーバーをファイルをサーブしてみましょう。
|
||||
例えば:
|
||||
|
||||
```bash
|
||||
cargo +nightly install miniserve
|
||||
miniserve ./static --index index.html
|
||||
```
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: Examples
|
||||
---
|
||||
|
||||
Yew のリポジトリは[例](https://github.com/yewstack/yew/tree/v0.17/examples)がたくさんあります
|
||||
\(メンテナンス状況は様々\)。
|
||||
様々なフレームワークの機能の使い方を知るのにはそれらの例に取り組むのを勧めます。
|
||||
プルリクエストや Issue はウェルカムです。
|
||||
|
||||
- [**Todo アプリ** ](https://github.com/yewstack/yew/tree/v0.17/examples/todomvc)
|
||||
- [**カスタムコンポーネント**](https://github.com/yewstack/yew/tree/v0.17/examples/custom_components)
|
||||
- [**マルチスレッド\(エージェント\)**](https://github.com/yewstack/yew/tree/v0.17/examples/multi_thread)
|
||||
- [**タイマーサービス**](https://github.com/yewstack/yew/tree/v0.17/examples/timer)
|
||||
- [**ネストしたコンポーネント**](https://github.com/yewstack/yew/tree/v0.16.0/examples/nested_list)
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: CSS
|
||||
---
|
||||
|
||||
# CSS
|
||||
|
||||
<TODO>
|
||||
|
||||
統合的な CSS サポートについての提案はこちらにあります: [https://github.com/yewstack/yew/issues/533](https://github.com/yewstack/yew/issues/533)
|
||||
|
||||
## スタイルフレームワーク:
|
||||
|
||||
今のところ、コミュニティメンバーは以下のスタイルフレームワークを開発しています。
|
||||
|
||||
- [yew_styles](https://github.com/spielrs/yew_styles) - JavaScript に依存しない Yew のスタイルフレームワーク
|
||||
- [yew-mdc](https://github.com/Follpvosten/yew-mdc) - マテリアルデザインのコンポーネント
|
||||
- [muicss-yew](https://github.com/AlephAlpha/muicss-yew) - MUI の CSS コンポーネント
|
||||
- [Yewtify](https://github.com/yewstack/yewtify) – Yew で Vuetify フレームワークで提供されている機能の実装
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: Debugging
|
||||
---
|
||||
|
||||
# デバッグ
|
||||
|
||||
## パニック
|
||||
|
||||
Rust シンボルで良いスタックトレースをするには
|
||||
[`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook)クレートを使用してください。
|
||||
注意として、`cargo-web`でビルドされたものとは互換性がありません。
|
||||
|
||||
## コンソールでのログ
|
||||
|
||||
一般的に、Wasm の Web アプリはブラウザの API と連携することができ、`console.log`の API も例外ではありません。
|
||||
いつくかの選択肢があります:
|
||||
|
||||
### [`wasm-logger`](https://crates.io/crates/wasm-logger)
|
||||
|
||||
このクレートは Rust の`log`クレートと親和性があります。
|
||||
|
||||
```rust
|
||||
// セットアップ
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
log::info!("Update: {:?}", msg);
|
||||
```
|
||||
|
||||
### [`ConsoleService`](https://docs.rs/yew/latest/yew/services/console/struct.ConsoleService.html)
|
||||
|
||||
このサービスは Yew に含まれており、`"services"`の機能が有効化されている場合は利用可能です。
|
||||
|
||||
```rust
|
||||
// 使用方法
|
||||
ConsoleService::info(format!("Update: {:?}", msg).as_ref());
|
||||
```
|
||||
|
||||
## ソースマップ
|
||||
|
||||
今のところは Rust/Wasm の Web アプリにはソースマップへの第一級のサポートがありません。
|
||||
もちろん、これは変更される可能性があります。これが当てはまらない場合、または進捗が見られる場合は、変更を提案してください!
|
||||
|
||||
### 最新情報
|
||||
|
||||
\[2019 年 12 月\] [Chrome DevTools update](https://developers.google.com/web/updates/2019/12/webassembly#the_future)
|
||||
|
||||
> やらなければいけないことがまだたくさんあります。例えばツール側では Emscripten\(Binaryen\)と wasm-pack\(wasm-bindgen\)がそれらが実行する変換に関する DWARF 情報の更新をまだサポートしていません。
|
||||
|
||||
\[2020\] [Rust Wasm デバッグガイド](https://rustwasm.github.io/book/reference/debugging.html#using-a-debugger)
|
||||
|
||||
> 残念なことに、WebAssembly のデバッグの物語はまだ未成熟です。ほとんどの Unix のシステムでは[DWARF](http://dwarfstd.org/)は実行中のプログラムをソースレベルで検査するためにデバッガに必要な情報をエンコードするために使用されます。Windows には同様の情報をエンコードする代替形式があります。現在、WebAssembly に相当するものはありません。
|
||||
|
||||
\[2019\] [Rust Wasm ロードマップ](https://rustwasm.github.io/rfcs/007-2019-roadmap.html#debugging)
|
||||
|
||||
> デバッグはトリッキーです。なぜなら、多くの話はこの活動チームの手の届かないところにあり、WebAssembly の標準化団体とブラウザ開発者ツールを実装している人たちの両方に依存しているからです。
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Roadmap
|
||||
description: The planned feature roadmap for the Yew framework
|
||||
---
|
||||
|
||||
# ロードマップ
|
||||
|
||||
## 優先順位
|
||||
|
||||
フレームワークの今後の機能やフォーカスの優先順位は、コミュニティによって決定されます。2020 年の春には、プロジェクトの方向性についてのフィードバックを集めるために開発者アンケートが行われました。その概要は [Yew Wiki](https://github.com/yewstack/yew/wiki/Dev-Survey-%5BSpring-2020%5D) で見ることができます。
|
||||
|
||||
:::note
|
||||
主要な取り組みの状況は、Yew の Github の[Project board](https://github.com/yewstack/yew/projects)で確認できます。
|
||||
:::
|
||||
|
||||
## 焦点
|
||||
|
||||
1. Top Requested Features
|
||||
2. Production Readiness
|
||||
3. Documentation
|
||||
4. Pain Points
|
||||
|
||||
### Top Requested Features
|
||||
|
||||
1. [関数型コンポーネント](https://github.com/yewstack/yew/projects/3)
|
||||
2. [Component ライブラリ](https://github.com/yewstack/yew/projects/4)
|
||||
3. より良い状態管理
|
||||
4. [サーバーサイドでのレンダリング](https://github.com/yewstack/yew/projects/5)
|
||||
|
||||
### Production Readiness
|
||||
|
||||
- テストカバレッジの向上
|
||||
- バイナリサイズ
|
||||
- [ベンチマークのパフォーマンス](https://github.com/yewstack/yew/issues/5)
|
||||
|
||||
### Documentation
|
||||
|
||||
- チュートリアルを作る
|
||||
- プロジェクトのセットアップをシンプルにする
|
||||
|
||||
### Pain Points
|
||||
|
||||
- [Component のボイラープレート](https://github.com/yewstack/yew/issues/830)
|
||||
- Fetch API
|
||||
- [エージェント](https://github.com/yewstack/yew/projects/6)
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Testing apps
|
||||
description: Testing your app
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
<TODO>
|
||||
|
||||
## wasm_bindgen_test
|
||||
|
||||
Rust Wasm ワーキンググループは wasm_bindgen_test というフレームワークをメンテナンスしており、組み込みの #[test] プロシージャルマクロの動作と同様の方法でブラウザでテストを実行することができます。詳細は、[Rust Wasm 活動グループのドキュメント](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html)に記載されています。
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"version.label": {
|
||||
"message": "0.21",
|
||||
"description": "The label for version 0.21"
|
||||
},
|
||||
"sidebar.docs.category.Getting Started": {
|
||||
"message": "Getting Started",
|
||||
"description": "The label for category Getting Started in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts": {
|
||||
"message": "Concepts",
|
||||
"description": "The label for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts.link.generated-index.title": {
|
||||
"message": "Yew concepts",
|
||||
"description": "The generated-index page title for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts.link.generated-index.description": {
|
||||
"message": "Learn about the important Yew concepts!",
|
||||
"description": "The generated-index page description for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew": {
|
||||
"message": "Using Basic Web Technologies In Yew",
|
||||
"description": "The label for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew.link.generated-index.title": {
|
||||
"message": "Yew's Take on Basic Web Technologies",
|
||||
"description": "The generated-index page title for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew.link.generated-index.description": {
|
||||
"message": "Yew centrally operates on the idea of keeping everything that a reusable piece of UI may needin one place - rust files, while also keeping the underlying technology accessible where necessary. Explore further to fully grasp what we mean by these statements:",
|
||||
"description": "The generated-index page description for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Components": {
|
||||
"message": "Components",
|
||||
"description": "The label for category Components in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Hooks": {
|
||||
"message": "Hooks",
|
||||
"description": "The label for category Hooks in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.HTML": {
|
||||
"message": "HTML",
|
||||
"description": "The label for category HTML in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics": {
|
||||
"message": "Advanced topics",
|
||||
"description": "The label for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics.link.generated-index.title": {
|
||||
"message": "Advanced topics",
|
||||
"description": "The generated-index page title for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics.link.generated-index.description": {
|
||||
"message": "Learn about the advanced topics and inner workings of Yew!",
|
||||
"description": "The generated-index page description for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Struct Components": {
|
||||
"message": "Struct Components",
|
||||
"description": "The label for category Struct Components in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.More": {
|
||||
"message": "More",
|
||||
"description": "The label for category More in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.More.link.generated-index.title": {
|
||||
"message": "Miscellaneous",
|
||||
"description": "The generated-index page title for category More in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Migration guides": {
|
||||
"message": "Migration guides",
|
||||
"description": "The label for category Migration guides in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew": {
|
||||
"message": "yew",
|
||||
"description": "The label for category yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew-agent": {
|
||||
"message": "yew-agent",
|
||||
"description": "The label for category yew-agent in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew-router": {
|
||||
"message": "yew-router",
|
||||
"description": "The label for category yew-router in sidebar docs"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
description: 有关框架的底层细节
|
||||
---
|
||||
|
||||
# 底层库的内部细节
|
||||
|
||||
组件生命周期状态机,虚拟 dom diff 算法。
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
description: 加速你的应用程序
|
||||
---
|
||||
|
||||
# 性能优化与最佳实践
|
||||
|
||||
## neq_assign
|
||||
|
||||
当组件从它的父组件接收 props 时,`change` 方法将被调用。除了允许你更新组件的状态,还允许你返回一个布尔类型的值 `ShouldRender` 来指示组件是否应该响应 props 的更改而重新渲染自身。
|
||||
|
||||
重新渲染的开销很大,你应该尽量避免。一个通用的法则是,你只应该在 props 实际更改时重新渲染。以下代码块展示了此法则,如果 props 和先前的 props 不同,则返回 `true`:
|
||||
|
||||
```rust
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
if self.props != &props {
|
||||
*self.props = props;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但是我们可以更进一步!对于任何实现了 `PartialEq` 的项,可以使用一个 trait 和一个 blanket implementation 将这六行样板代码减少到一行。
|
||||
|
||||
{% code title="neq\_assign.rs" %}
|
||||
|
||||
```rust
|
||||
pub trait NeqAssign {
|
||||
fn neq_assign(&mut self, new: Self) -> ShouldRender;
|
||||
}
|
||||
impl<T: PartialEq> NeqAssign for T {
|
||||
fn neq_assign(&mut self, new: T) -> ShouldRender {
|
||||
if self != &new {
|
||||
*self = new;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
self.props.neq_assign(props)
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
该 trait 称为 `NeqAssign` 是因为如果目标值和新值不相等,它将赋为新值。
|
||||
|
||||
这比简单的实现还要短:
|
||||
|
||||
```rust
|
||||
// 不要这样做,除非你无法避免。
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
self.props = props;
|
||||
true
|
||||
}
|
||||
```
|
||||
|
||||
你不仅限在 `change` 函数中使用它。通常,在 `update` 函数中执行此操作也是有意义的,尽管性能提升在那里不太明显。
|
||||
|
||||
## RC
|
||||
|
||||
为了避免在重新渲染时为了创建 props 而克隆大块数据,我们可以使用智能指针来只克隆指针。如果在 props 和子组件中使用 `Rc<_>` 而不是普通未装箱的值,则可以延迟克隆直到需要修改子组件中的数据为止,在该组件中可以使用 `Rc::make_mut` 来对要更改数据进行克隆和获取可变引用。通过在要修改前不进行克隆,子组件可以在几乎没有性能成本的情况下拒绝与它们在 `Component::change` 中拥有状态的 props 相同的 props,这与数据本身需要先复制到父级 props 结构体中,然后在子级中进行比较和拒绝的情况相反。
|
||||
|
||||
对于不是 `Copy` 类型的数据,这种优化是最有用的。如果你能轻松地拷贝数据,那么将其放入智能指针中可能是不值得的。对于可以包含大量数据的结构,例如 `Vec`,`HashMap` 和 `String`,这种优化应该是值得的。
|
||||
|
||||
如果子组件从不更新组件的值,则这种优化效果最好,如果父组件很少更新组件的值,则效果更好。这使得 `Rc<_>s` 是包装纯组件属性值的不错选择。
|
||||
|
||||
## 视图函数
|
||||
|
||||
出于代码可读性的原因,将 `html!` 各个部分的代码迁移到他们自己的函数中通常是有意义的,这样就可以避免在深层嵌套的 HTML 中出现代码块向右偏移。
|
||||
|
||||
## 纯组件 / 函数式组件
|
||||
|
||||
纯组件是不会修改它们状态的组件,它们仅展示内容和向普通可变组件传递消息。它们与视图函数不同之处在于他们可以使用组件语法(`<SomePureComponent />`)而不是表达式语法(`{some_view_function()}`)来在 `html!` 宏中使用,并且根据它们的实现,它们可以被记忆化 - 使用前面提到的 `neq_assign` 逻辑来防止因为相同的 props 而重新渲染。
|
||||
|
||||
Yew 没有原生支持纯组件或者函数式组件,但是可以通过外部库获取它们。
|
||||
|
||||
函数式组件尚不存在,但是从理论上来讲,可以通过使用 proc 宏和标注函数生成纯组件。
|
||||
|
||||
## Keyed DOM nodes when they arrive
|
||||
|
||||
## 使用 Cargo Workspaces 进行编译速度优化
|
||||
|
||||
可以说,使用 Yew 的最大缺点是编译时间长。编译时间似乎与 `html!` 宏块中的代码量相关。对于较小的项目,这通常不是什么大问题,但是对于跨多个页面的 web 应用程序,将代码拆分为多个 crates 以最大程度地减少编译器要做的工作通常是有意义的。
|
||||
|
||||
你应该尝试让主 crate 处理路由和页面选择,将所有公用的代码移动到另一个 crate,然后为每一个页面创建一个不同的 crate,其中每个页面可能是一个不同的组件,或者只是一个产生 `Html` 的大函数。在最好的情况下,你将从重新构建所有代码到只重新构建主 crate 和一个页面的 crate。在最糟糕的情况下,当你在“公共” crate 中编辑内容时,你将回到起点:编译所有依赖此公用 crate 的代码,这可能就是除此之外的所有代码。
|
||||
|
||||
如果你的主 crate 过于庞大,或者你想在深层嵌套的页面(例如,在另一个页面顶部渲染的页面)中快速迭代,则可以使用一个示例 crate 创建一个更简单的主页面实现并在之上渲染你正在开发的组件。
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
description: ComponentLink 和 Callbacks.
|
||||
---
|
||||
|
||||
# 回调(Callbacks)
|
||||
|
||||
组件“link”是一种机制,通过该机制,组件可以注册回调并自行更新。
|
||||
|
||||
## ComponentLink API
|
||||
|
||||
### callback
|
||||
|
||||
注册一个回调,该回调将在执行时将消息发送到组件的更新机制。在内部,它将使用提供的闭包返回的消息调用 `send_self`。提供 `Fn(IN) -> Vec<COMP::Message>`,返回 `Callback<IN>`。
|
||||
|
||||
### send_message
|
||||
|
||||
当前循环结束后立即向组件发送消息,导致另一个更新循环启动。
|
||||
|
||||
### send_message_batch
|
||||
|
||||
注册一个回调,该回调在执行时立即发送一批消息。如果其中任何一个消息将导致组件重新渲染,那么组件会在该批次所有消息被处理后重新渲染。提供 `Fn(IN) -> COMP::Message`,返回 `Callback<IN>`。
|
||||
|
||||
## Callbacks
|
||||
|
||||
Callbacks 用于与 Yew 中的 services,agents 和父组件进行通信。它们仅仅是个 `Fn`,并由 `Rc` 包裹以允许被克隆。
|
||||
|
||||
它们有一个 `emit` 函数,该函数将它的 `<IN>` 类型作为参数并将其转换为目标所期望的消息。如果一个回调从父组件中通过 props 提供给子组件,则子组件可以在其 `update` 生命周期钩子中对该回调调用 `emit`,以将消息发送回父组件。在 `html!` 宏内被提供作为 props 的闭包或函数会自动转换为 Callbacks。
|
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
description: 组件及其生命周期钩子
|
||||
---
|
||||
|
||||
# 组件(Components)
|
||||
|
||||
## 组件是什么?
|
||||
|
||||
组件是 Yew 的基石。它们管理自己的状态,并可以渲染为 DOM。组件是通过实现描述组件生命周期的 `Component` trait 来创建的。
|
||||
|
||||
## 生命周期
|
||||
|
||||
:::note
|
||||
`为我们的文档做出贡献:`[添加组件的生命周期图示](https://github.com/yewstack/docs/issues/22)
|
||||
:::
|
||||
|
||||
## 生命周期方法
|
||||
|
||||
### Create
|
||||
|
||||
当一个组件被创建时,它会从其父组件以及一个 `ComponentLink` 接收属性(properties)。属性(properties)可用于初始化组件的状态,“link”可用于注册回调或向组件发送消息。
|
||||
|
||||
通常将 props 和 link 存储在组件的结构体中,如下所示:
|
||||
|
||||
```rust
|
||||
pub struct MyComponent {
|
||||
props: Props,
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Properties = Props;
|
||||
// ...
|
||||
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
MyComponent { props, link }
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
组件在 `view()` 方法中声明它的布局。Yew 提供了 `html!` 宏来声明 HTML 和 SVG 节点和它们的监听器及其子组件。这个宏的行为很像 React 中的 JSX,但是使用的是 Rust 表达式而不是 JavaScript。
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let onclick = self.link.callback(|_| Msg::Click);
|
||||
html! {
|
||||
<button {onclick}>{ self.props.button_text }</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有关用法的详细信息,请查看 [`html!` 宏指南](concepts/html/introduction.mdx)]
|
||||
|
||||
### Mounted
|
||||
|
||||
`mounted()` 组件生命周期方法调用是在 `view()` 被处理并且 Yew 已经把组件挂载到 DOM 上之后,浏览器刷新页面之前。组件通常希望实现此方法以执行只能在组件渲染元素之后才能执行的操作。如果你想在做出一些更改后重新渲染组件,返回 `true` 就可以了。
|
||||
|
||||
```rust
|
||||
use stdweb::web::html_element::InputElement;
|
||||
use stdweb::web::IHtmlElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct MyComponent {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<input ref={self.node_ref.clone()} type="text" />
|
||||
}
|
||||
}
|
||||
|
||||
fn mounted(&mut self) -> ShouldRender {
|
||||
if let Some(input) = self.node_ref.cast::<InputElement>() {
|
||||
input.focus();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
请注意,此生命周期方法不要求必须实现,默认情况下不会执行任何操作。
|
||||
:::
|
||||
|
||||
### Update
|
||||
|
||||
组件是动态的,可以注册以接收异步信息。`update()` 生命周期方法对于每个消息都会被调用。这使得组件可以根据消息的内容来更新自身,并决定是否需要重新渲染自己。消息可以由 HTML 元素监听器触发,或者由子组件,Agents,Services 或 Futures 发送。
|
||||
|
||||
`update()` 可能看起来像下面这个例子:
|
||||
|
||||
```rust
|
||||
pub enum Msg {
|
||||
SetInputEnabled(bool)
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
|
||||
// ...
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::SetInputEnabled(enabled) => {
|
||||
if self.input_enabled != enabled {
|
||||
self.input_enabled = enabled;
|
||||
true // 重新渲染
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change
|
||||
|
||||
组件可能被其父节点重新渲染。发生这种情况时,它们可以接收新的属性(properties)并选择重新渲染。这种设计通过更改属性(properties)来促进父子组件之间的通信。你不是必须实现 `change()`,但是如果想在组件被创建后通过 props 来更新组件,则可能要这么做。
|
||||
|
||||
一个原始的实现可能看起来像:
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
self.props = props;
|
||||
true // 当提供了新的 props 将始终重新渲染。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Destroy
|
||||
|
||||
组件从 DOM 上被卸载后,Yew 调用 `destroy()` 生命周期方法来支持任何必要的清理操作。这个方法是可选的,默认情况下不执行任何操作。
|
||||
|
||||
## 关联类型
|
||||
|
||||
`Component` trait 有两个关联类型:`Message` 和 `Properties`。
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`Message` 表示组件可以处理以触发某些副作用的各种消息。例如,你可能有一条 `Click` 消息,该消息触发 API 请求或者切换 UI 组件的外观。通常的做法是在组件模块中创建一个叫做 `Msg` 的枚举并将其用作组件中的消息类型。通常将“message”缩写为“msg”。
|
||||
|
||||
```rust
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
```
|
||||
|
||||
`Properties` 表示从父级传递到组件的信息。此类型必须实现 `Properties` trait(通常通过派生),并且可以指定某些属性(properties)是必需的还是可选的。创建和更新组件时使用此类型。通常的做法是在组件模块中创建一个叫做 `Props` 的结构体并将其用作组件的 `Properties` 类型。通常将“properties”缩写为“props”。由于 props 是从父组件传递下来的,因此应用程序的根组件通常有一个类型为 `()` 的 `Properties`。如果你希望为根组件指定属性(properties),请使用 `App::mount_with_props` 方法。
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
description: 父组件到子组件的通信
|
||||
---
|
||||
|
||||
# 属性(Properties)
|
||||
|
||||
如“组件(Components)”页面所述,Properties 用于父级到子组件的通信。
|
||||
|
||||
## 派生宏
|
||||
|
||||
不要尝试自己去实现 `Properties`,而是通过使用 `#[derive(Properties)]` 来派生它。
|
||||
|
||||
### 必需属性
|
||||
|
||||
默认情况下,实现了 `Properties` 的结构体中的字段是必需的。当缺少了该字段并且在 `html!` 宏中创建了组件时,将返回编译错误。对于具有可选属性的字段,使用 `#[prop_or_default]` 来使用该类型的默认值。要指定一个值,请使用 `#[prop_or_else(value)]`,其中 value 是该属性的默认值。例如,要将一个布尔值的默认值设置为 `true`,请使用属性 `#[prop_or_else(true)]`。可选属性通常使用 `Option`,其默认值为 `None`。
|
||||
|
||||
### PartialEq
|
||||
|
||||
如果可以的话,在你的 props 上派生 `PartialEq` 通常是很有意义的。这使用了一个**性能优化与最佳实践**部分解释了的技巧,可以更轻松地避免重新渲染。
|
||||
|
||||
## Properties 的内存/速度开销
|
||||
|
||||
记住组件的 `view` 函数签名:
|
||||
|
||||
```rust
|
||||
fn view(&self) -> Html
|
||||
```
|
||||
|
||||
你对组件的状态取了一个引用,并用来创建 `Html`。但是 properties 是有所有权的值(owned values)。这意味着为了创造它们并且将它们传递给子组件,我们需要获取 `view` 函数里提供的引用的所有权。这是在将引用传递给组件时隐式克隆引用完成的,以获得构成其 props 的有所有权的值。
|
||||
|
||||
这意味着每个组件都有从其父级传递来的状态的独特副本,而且,每当你重新渲染一个组件时,该重新渲染组件的所有子组件的 props 都将被克隆。
|
||||
|
||||
这意味着如果你将 _大量_ 数据作为 props(大小为 10 KB 的字符串)向下传递,则可能需要考虑将子组件转换为在父级运行返回 `Html` 的函数,因为这样就不会被强制克隆你的数据。
|
||||
|
||||
另外,如果你不需要修改作为 props 传递的大数据,而只需要显示它,则可以将其包装在 `Rc` 中,以便仅克隆一个引用计数的指针,而不是数据本身。
|
||||
|
||||
## 示例
|
||||
|
||||
```rust
|
||||
pub struct LinkColor {
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
Black,
|
||||
Purple,
|
||||
}
|
||||
|
||||
impl Default for LinkColor {
|
||||
fn default() -> Self {
|
||||
// 除非另有说明,否则链接的颜色将为蓝色
|
||||
LinkColor::Blue
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
/// 链接必须有一个目标地址
|
||||
href: String,
|
||||
/// 如果链接文本很大,这将使得复制字符串开销更小
|
||||
/// 除非有性能问题,否则通常不建议这么做
|
||||
text: Rc<String>,
|
||||
/// 链接的颜色
|
||||
#[prop_or_default]
|
||||
color: LinkColor,
|
||||
/// 如果为 None,则 view 函数将不指定大小
|
||||
#[prop_or_default]
|
||||
size: Option<u32>
|
||||
/// 当 view 函数没有指定 active,其默认为 true
|
||||
#[prop_or_else(true)]
|
||||
active: bool,
|
||||
}
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: Refs
|
||||
description: 超出界限的 DOM 访问
|
||||
---
|
||||
|
||||
`ref` 关键词可被用在任何 HTML 元素或组件内部以获得该项所附加到的 DOM 元素。这可被用于在 `view` 生命周期方法之外来对 DOM 进行更改。
|
||||
|
||||
这对于获取 canvas 元素或者滚动到页面的不同部分是有用的。
|
||||
|
||||
语法如下:
|
||||
|
||||
```rust
|
||||
// 在 create 中
|
||||
self.node_ref = NodeRef::default();
|
||||
|
||||
// 在 view 中
|
||||
html! {
|
||||
<div ref={self.node_ref.clone()}></div>
|
||||
}
|
||||
|
||||
// 在 update 中
|
||||
let has_attributes = self.node_ref.cast::<Element>().unwrap().has_attributes();
|
||||
```
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: Agents
|
||||
description: Yew 的 Actor 系统
|
||||
---
|
||||
|
||||
Agents 和 Angular 的 [Services](https://angular.io/guide/architecture-services) 相似(但没有依赖注入),给 Yew 提供了 [Actor 模型](https://en.wikipedia.org/wiki/Actor_model)。Agents 可以用于在组件之间路由消息,而与它们在组件层次结构中的位置无关,或者可以用于协调全局状态,或者可以用于从主 UI 线程上卸载计算密集型任务,或者在不同的标签页间通信(在未来)。
|
||||
|
||||
Agents 使用 [web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) 同时运行来实现并发。
|
||||
|
||||
## 生命周期
|
||||
|
||||

|
||||
|
||||
## Agents 的类型
|
||||
|
||||
#### Reaches
|
||||
|
||||
- Job - 在 UI 线程上为每个新的 Bridge 生成一个新的 Agent。这对于将与浏览器通信的共享但独立的行为移出组件是很有用的。(待验证)任务完成后,Agent 将消失。
|
||||
- Context - Bridges 将生成或连接到 UI 线程上的 agent。这可用于在组件和其它 Agents 之间协调状态。当没有 Bridge 连接到该 Agent 时,Agent 将消失。
|
||||
- Private - 与 Job 相同,但运行在自己的 web worker 中。
|
||||
- Public - 与 Context 相同,但运行在自己的 web worker 中。
|
||||
- Global \(WIP\)
|
||||
|
||||
## Agent 通信
|
||||
|
||||
### Bridges
|
||||
|
||||
Bridges 将连接到一个 Agent 并且允许双向通信。
|
||||
|
||||
### Dispatchers
|
||||
|
||||
Dispatchers 和 Bridges 类似,但是他们只能发送消息给 Agents。
|
||||
|
||||
## 开销
|
||||
|
||||
Agents 通过使用二进制码 bincode 序列化其消息来进行通信。因此,存在比仅调用函数相比更高的性能消耗。除非计算成本或者在任意组件间协调的需求超过消息传递的成本,否则你应该尽可能地在函数中包含你的应用逻辑。
|
||||
|
||||
## Further reading
|
||||
|
||||
- The [web_worker_fib](https://github.com/yewstack/yew/tree/master/examples/web_worker_fib) example shows how components can use agents to communicate with each other.
|
|
@ -0,0 +1,121 @@
|
|||
---
|
||||
title: 自定义钩子(Custom Hooks)
|
||||
description: 定义你自己的 Hooks
|
||||
---
|
||||
|
||||
## 定义自定义钩子
|
||||
|
||||
组件中与状态有关的逻辑可以通过创建自定义 Hooks 提取到函数中。
|
||||
|
||||
假设我们有一个组件,它订阅了一个代理(agent)并且会显示发送给它的消息。
|
||||
|
||||
```rust
|
||||
#[function_component(ShowMessages)]
|
||||
pub fn show_messages() -> Html {
|
||||
let (state, set_state) = use_state(|| vec![]);
|
||||
|
||||
{
|
||||
let mut state = Rc::clone(&state);
|
||||
use_effect(move || {
|
||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||
let mut messages = (*state).clone();
|
||||
messages.push(msg);
|
||||
set_state(messages)
|
||||
}));
|
||||
|
||||
|| drop(producer)
|
||||
});
|
||||
}
|
||||
|
||||
let output = state.iter().map(|it| html! { <p>{ it }</p> });
|
||||
html! { <div>{ for output }</div> }
|
||||
}
|
||||
```
|
||||
|
||||
这段代码有一个问题:逻辑不能被另一个组件重用。如果我们构建另一个跟踪消息的组件,我们可以将逻辑移动到自定义钩子中,而不是复制代码。
|
||||
|
||||
我们将首先创建一个名为`use_subscribe`的新函数。 `use_`前缀通常表示此函数是一个钩子。这个函数将不接受任何参数并返回`Rc<RefCell<Vec<String>>>` 。
|
||||
|
||||
```rust
|
||||
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
钩子的逻辑在`use_hook`的回调中。 `use_hook`指的是自定义 Hook 的处理函数。它接受 2 个参数: `hook_runner`和`initial_state_producer` 。
|
||||
|
||||
`hook_runner`中包含了所有钩子的逻辑,它的回调的返回值又会被`use_hook`返回。 `hook_runner`需要 2 个参数:分别是对钩子和`hook_callback`它们两个的内部状态的可变引用。 而`hook_callback`同样也要 2 个参数:一个回调和一个 bool,回调接受`internal_state` ,也就是对内部状态实例的可变引用,并且会调执行实际的更改,还会返回表示`ShouldRender`的布尔值,第二个参数 bool 的用处是指示它是否在组件渲染后运行。`use_hook`的第二个参数`initial_state_producer`接受用于创建内部状态实例的回调。这里说的内部状态指的是一个实现了`Hook` trait 的结构体。
|
||||
|
||||
现在让我们为`use_subscribe`钩子创建状态(state struct)。
|
||||
|
||||
```rust
|
||||
/// `use_subscribe` internal state
|
||||
struct UseSubscribeState {
|
||||
/// holds all the messages received
|
||||
pub messages: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl Hook for UseSubscribeState {}
|
||||
```
|
||||
|
||||
接下来我们为`use_subscribe`添加实际逻辑。
|
||||
|
||||
```rust
|
||||
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
|
||||
use_hook(
|
||||
// hook's handler. all the logic goes in here
|
||||
|state: &mut UseSubscribeState, hook_callback| {
|
||||
// calling other Hooks inside a hook
|
||||
use_effect(move || {
|
||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||
hook_callback(
|
||||
// where the mutations of state are performed
|
||||
|state| {
|
||||
(*state.messages).borrow_mut().deref_mut().push(msg);
|
||||
true // should re-render
|
||||
}, false // run post-render
|
||||
)
|
||||
}));
|
||||
|
||||
|| drop(producer)
|
||||
});
|
||||
|
||||
// return from hook
|
||||
state.messages.clone()
|
||||
},
|
||||
// initial state producer
|
||||
|| UseSubscribeState { messages: Rc::new(RefCell::new(vec![])) },
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
现在我们可以使用自定义钩子了:
|
||||
|
||||
```rust
|
||||
#[function_component(ShowMessages)]
|
||||
pub fn show_messages() -> Html {
|
||||
let state = use_subscribe();
|
||||
let output = state.borrow().deref().into_iter().map(|it| html! { <p>{ it }</p> });
|
||||
|
||||
html! { <div>{ for output }</div> }
|
||||
}
|
||||
```
|
||||
|
||||
需要特别注意的是创建自定义钩子时`use_hook`不是必须的,它们只是用来包含其他钩子。通常应避免使用`use_hook`。
|
||||
|
||||
```rust
|
||||
fn use_subscribe() -> Rc<Vec<String>> {
|
||||
let (state, set_state) = use_state(Vec::new);
|
||||
|
||||
use_effect(move || {
|
||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||
let mut messages = (*state).clone();
|
||||
messages.push(msg);
|
||||
set_state(messages)
|
||||
}));
|
||||
|| drop(producer)
|
||||
});
|
||||
|
||||
state
|
||||
}
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: 函数式组件
|
||||
sidebar_label: 简介
|
||||
description: 介绍函数式组件
|
||||
slug: /concepts/function-components
|
||||
---
|
||||
|
||||
函数式组件是普通组件的简化版本。它们由一个接收 props 的函数组成,并通过返回`Html`来确定应该呈现什么。基本上,它是一个简化为`view`方法的组件。就其本身而言,这将是相当有限的,因为您只能创建纯组件,而这就是 Hook 大展身手的地方。Hook 允许函数组件无需实现`Component` trait,就可以使用状态(state)和其他 Yew 功能。
|
||||
|
||||
## 创建函数式组件
|
||||
|
||||
创建函数式组件的最简单方法是在函数前添加`#[function_component]`属性。
|
||||
|
||||
```rust
|
||||
#[function_component(HelloWorld)]
|
||||
fn hello_world() -> Html {
|
||||
html! { "Hello world" }
|
||||
}
|
||||
```
|
||||
|
||||
### 更多细节
|
||||
|
||||
函数式组件由两部分组成。首先, `FunctionProvider` trait 与`Component` trait 差不多,但它只有一个名为`run`方法。之后是`FunctionComponent`结构体,它封装了`FunctionProvider`类型并将其转换为实际的`Component` 。 `#[function_component]`属性本质上只是`FunctionProvider`并将其暴露在`FunctionComponent` 。
|
||||
|
||||
### 钩子(Hooks)
|
||||
|
||||
钩子(Hooks)就是让您“钩住”组件的状态(state)和/或生命周期并执行操作的函数。 除了 Yew 自带的一些预定义的 Hook。您也可以创建自己的。
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
description: 使用具有层次结构的组件来创建复杂的布局
|
||||
---
|
||||
|
||||
# 组件
|
||||
|
||||
## 基础
|
||||
|
||||
任何实现了 `Component` trait 的类型都可被用在 `html!` 宏中:
|
||||
|
||||
```rust
|
||||
html!{
|
||||
<>
|
||||
// 没有属性
|
||||
<MyComponent />
|
||||
|
||||
// 具有属性
|
||||
<MyComponent prop1="lorem" prop2="ipsum" />
|
||||
|
||||
// 同时提供全套的 props
|
||||
<MyComponent ..props />
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## 嵌套
|
||||
|
||||
如果组件的 `Properties` 中有 `children` 字段,则可以被传递子组件。
|
||||
|
||||
{% code title="parent.rs" %}
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<Container>
|
||||
<h4>{ "Hi" }</h4>
|
||||
<div>{ "Hello" }</div>
|
||||
</Container>
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
{% code title="container.rs" %}
|
||||
|
||||
```rust
|
||||
pub struct Container(Props);
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
impl Component for Container {
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div id="container">
|
||||
{ self.0.children.clone() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
## 拥有 Props 的嵌套子组件
|
||||
|
||||
如果包含组件标注了 children 的类型,则可以访问和更改嵌套组件的属性。在下面的示例中,`List` 组件可以包含 `ListItem` 组件。有关此模式的真实示例,请查看 `yew-router` 的源码。有关更高级的示例,请在 yew 主仓库中查看 `nested-list` 示例代码。
|
||||
|
||||
{% code title="parent.rs" %}
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<List>
|
||||
<ListItem value="a" />
|
||||
<ListItem value="b" />
|
||||
<ListItem value="c" />
|
||||
</List>
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
{% code title="list.rs" %}
|
||||
|
||||
```rust
|
||||
pub struct List(Props);
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
pub children: ChildrenWithProps<ListItem>,
|
||||
}
|
||||
|
||||
impl Component for List {
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html!{{
|
||||
for self.0.children.iter().map(|mut item| {
|
||||
item.props.value = format!("item-{}", item.props.value);
|
||||
item
|
||||
})
|
||||
}}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
|
@ -0,0 +1,264 @@
|
|||
---
|
||||
description: HTML 和 SVG 元素均受支持
|
||||
---
|
||||
|
||||
# 元素
|
||||
|
||||
## 标签结构
|
||||
|
||||
元素标签必须是自闭合的 `<... />`,或是每个标签都有一个对应的闭合标签。
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--标签 - 闭合标签-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div id="my_div"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--无效-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div id="my_div"> // <- 缺少闭合标签
|
||||
}
|
||||
```
|
||||
|
||||
<!--自闭合-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<input id="my_input" />
|
||||
}
|
||||
```
|
||||
|
||||
<!--无效-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<input id="my_input"> // <- 没有自闭合
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
:::note
|
||||
为方便起见,一些 _通常_ 需要闭合标签的元素是被**允许**自闭合的。例如,`html! { <div class="placeholder" /> }` 这样写是有效的。
|
||||
:::
|
||||
|
||||
## Children
|
||||
|
||||
轻松创建复杂的嵌套 HTML 和 SVG 布局:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--HTML-->
|
||||
|
||||
```rust
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
<!--SVG-->
|
||||
|
||||
```rust
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## Classes
|
||||
|
||||
有许多方便的选项可用于元素指定 classes:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--常量-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class="container"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--多个属性-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class="container center-align"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--插值-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={format!("{}-container", size)}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--表达式-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={self.classes()}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--元组-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={("class-1", "class-2")}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Vector-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={vec!["class-1", "class-2"]}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## 监听器
|
||||
|
||||
监听器属性需要传递一个由闭包包裹的 `Callback`。创建回调的方式取决于你希望你的应用程序如何响应监听器事件:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Component 处理器-->
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
MyComponent { link }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Click => {
|
||||
// 处理 Click
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// 从组件 link 中创建回调来在组件中处理它
|
||||
let click_callback = self.link.callback(|_: ClickEvent| Msg::Click);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--Agent 处理器-->
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
worker: Dispatcher<MyWorker>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
MyComponent {
|
||||
worker: MyWorker::dispatcher()
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// 从 worker 中创建回调来在另一个上下文中处理它
|
||||
let click_callback = self.worker.callback(|_: ClickEvent| WorkerMsg::Process);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--其他情况-->
|
||||
|
||||
```rust
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
MyComponent
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// 创建一个短暂的回调
|
||||
let click_callback = Callback::from(|| {
|
||||
ConsoleService::log("clicked!");
|
||||
});
|
||||
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
description: 用于生成 HTML 和 SVG 的宏程序
|
||||
slug: /concepts/html
|
||||
---
|
||||
|
||||
# 使用 html! 宏
|
||||
|
||||
`html!` 宏允许你为组件编写声明式的 HTML 和 SVG。如果你使用过 React 的 JSX,将会感觉到非常熟悉。
|
||||
|
||||
**重要提示**
|
||||
|
||||
1. `html!` 宏调用中只能有一个根节点
|
||||
2. 空的 `html! {}` 宏调用是有效的但不会渲染任何内容
|
||||
3. 常量必须始终被引号括起来并被包含在大括号里:`html! { "Hello, World" }`
|
|
@ -0,0 +1,57 @@
|
|||
# 列表
|
||||
|
||||
## Fragments
|
||||
|
||||
`html!` 宏总是要求一个单一的根节点。为了绕开这个限制,把内容包裹在一个空标签内是有效的:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--有效-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<>
|
||||
<div></div>
|
||||
<p></p>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
<!--无效-->
|
||||
|
||||
```rust
|
||||
/* 错误:只允许一个 html 根元素 */
|
||||
|
||||
html! {
|
||||
<div></div>
|
||||
<p></p>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## 迭代器
|
||||
|
||||
Yew 支持两种从迭代器构建 html 的语法:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--语法类型 1-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ self.props.items.iter().map(renderItem).collect::<Html>() }
|
||||
</ul>
|
||||
}
|
||||
```
|
||||
|
||||
<!--语法类型 2-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ for self.props.items.iter().map(renderItem) }
|
||||
</ul>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
@ -0,0 +1,57 @@
|
|||
# 常量和表达式
|
||||
|
||||
## 常量
|
||||
|
||||
如果一个表达式的类型本身实现了 `Display` (一个标准库中的 Trait),他们将会被转化成字符串并且作为一个 [Text](https://developer.mozilla.org/en-US/docs/Web/API/Text) 节点插入 DOM 中。
|
||||
|
||||
所有的需要显示的文本必须被 `{}` 块包含,因为这些文本会被当做一个 Rust 表达式来处理。这一点上,Yew 中使用 HTML 的方式和正常 HTML 语法有巨大的区别。
|
||||
|
||||
```rust
|
||||
let text = "lorem ipsum";
|
||||
html!{
|
||||
<>
|
||||
<div>{text}</div>
|
||||
<div>{"dolor sit"}</div>
|
||||
<span>{42}</span>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## 表达式
|
||||
|
||||
你可以在 HTML 中使用 `{}` 块来插入 Rust 表达式,只要这些表达式最终可以被解析成 `Html`
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div>
|
||||
{
|
||||
if show_link {
|
||||
html! {
|
||||
<a href="https://example.com">{"Link"}</a>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
通常我们会把这些表达式写进函数或者闭包中来增加可读性:
|
||||
|
||||
```rust
|
||||
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,45 @@
|
|||
---
|
||||
description: Yew 的官方 Router
|
||||
---
|
||||
|
||||
# Router
|
||||
|
||||
[https://crates.io/crates/yew-router](https://crates.io/crates/yew-router)
|
||||
|
||||
Routers 在单页应用(SPA)中根据 URL 的不同显示不同的页面。当点击一个链接时,Router 在本地设置 URL 以指向应用程序中有效的路由,而不是默认请求一个不同的远程资源。然后 Router 检测到此更改后决定要渲染的内容。
|
||||
|
||||
## 核心元素
|
||||
|
||||
### Route
|
||||
|
||||
包含一个字符串,该字符串表示网址中域名之后的所有内容,还可以选择表示存储在 history api 中的状态。
|
||||
|
||||
### RouteService
|
||||
|
||||
与浏览器通信以获取和设置路由。
|
||||
|
||||
### RouteAgent
|
||||
|
||||
拥有一个 RouteService,并用于当路由改变时协调更新,无论更新是来自应用程序自身逻辑还是来自浏览器触发的事件。
|
||||
|
||||
### Switch
|
||||
|
||||
`Switch` trait 用于在该 trait 的实现者之间转换 `Route`。
|
||||
|
||||
### Router
|
||||
|
||||
Router 组件同 `RouterAgent` 进行通信,并将自动把它从 Agent 那里获得的 Routes 解析为 Switches,并通过 `render` 属性暴露该 Switch,该属性允许指定将生成的 Switch 转换为 `HTML` 的方式。
|
||||
|
||||
## 如何使用 Router
|
||||
|
||||
首先,你要创建一个表征你的应用程序所有状态的类型。请注意,虽然这通常是一个枚举,但也支持结构体,并且你可以在内部嵌套实现了 `Switch` trait 的其他项。
|
||||
|
||||
然后你应该为了你创建的类型派生 `Switch`。对于枚举,每一个成员都必须用 `#[to = "/some/route"]` 进行标注,如果你使用结构体,则标注必须出现在结构体声明之外。
|
||||
|
||||
请注意,由派生宏为 `Switch` 生成的实现,将尝试从头到尾依次创建每个成员,因此,如果任何路由可能与你指定的两个 `to` 标注相匹配,那么第一个会被匹配,第二个将永远不会被尝试。
|
||||
|
||||
你还可以在 `#[to = ""]` 标注中使用 `{}` 的变体来捕获片段。`{}` 表示捕获文本直到下一个分隔符(根据上下文可能是"/","?","&" 或 "\#")。`{*}` 表示捕获文本直到后续字符匹配为止,如果不存在任何字符,则它将匹配任何内容。`{<number>}` 表示捕获文本直到遇到指定数目的分隔符为止(例如:`{2}` 将一直捕获文本直到遇到两个分隔符为止)。
|
||||
|
||||
对于具有命名字段的结构体和枚举,你必须在捕获组中指定字段的名称,例如:`{user_name}` 或 `{*:age}`。
|
||||
|
||||
Switch trait 适用于比字符串更结构化的捕获组。你可以指定实现了 `Switch` trait 的任何类型。因此,你可以指定捕获组为 `usize`,并且如果 URL 的捕获部分无法转换为它,则该成员不会被匹配。
|
|
@ -0,0 +1,87 @@
|
|||
# 第一个简单的 App
|
||||
|
||||
首先创建一个二进制项目:
|
||||
|
||||
```bash
|
||||
cargo new --bin yew-app && cd yew-app
|
||||
```
|
||||
|
||||
添加 `yew` 到你的依赖库中([这里](https://docs.rs/yew) 可以查看最新版本的 Yew)
|
||||
|
||||
{% code title="Cargo.toml" %}
|
||||
|
||||
```text
|
||||
[package]
|
||||
name = "yew-app"
|
||||
version = "0.1.0"
|
||||
authors = ["Yew App Developer <name@example.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
yew = { version = "0.14.3", features = ["std_web"] }
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
将这份代码复制到你的 `src/main.rs` 文件中:
|
||||
|
||||
{% code title="src/main.rs" %}
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
|
||||
struct Model {
|
||||
link: ComponentLink<Self>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
AddOne,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Self {
|
||||
link,
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::AddOne => self.value += 1
|
||||
}
|
||||
true // 指示组件应该重新渲染
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<button onclick={self.link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
|
||||
<p>{ self.value }</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
yew::initialize();
|
||||
App::<Model>::new().mount_to_body();
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
这份代码将构建你的称为 `Model` 的 `Component` 根组件,它会显示一个按钮,当你点击它时,`Model` 将会更新自己的状态。特别注意 `main()` 中的 `App::<Model>::new().mount_to_body()`,它会启动你的应用并将其挂载到页面的 `<body>` 标签中。如果你想使用任何动态属性来启动应用程序,则可以使用 `App::<Model>::new().mount_to_body_with_props(..)`。
|
||||
|
||||
## 运行你的应用程序!
|
||||
|
||||
启动并运行你的应用的最快方式就是使用 [`cargo-web`](https://github.com/koute/cargo-web)。如果你还没有的话,请用 `cargo install cargo-web` 命令来安装这个工具然后通过运行下述命令来构建和启动一个开发服务器:
|
||||
|
||||
```bash
|
||||
cargo web start
|
||||
```
|
||||
|
||||
`cargo-web` 将会自动为你添加 `wasm32-unknown-unknown` 作为目标代码,然后构建你的应用,你的应用将默认在 [http://\[::1\]:8000](http://[::1]:8000) 被访问。可以通过 `cargo web start --help` 命令来获取更多选项和帮助。
|
|
@ -0,0 +1,11 @@
|
|||
# 通过例子学习
|
||||
|
||||
Yew 的 github 项目中就包含了各种各样的示例(这些项目在不同程度的维护中)。我们建议仔细地学习它们, 了解如何使用不同的框架特性. 我们在书中有纰漏和错误的时候也欢迎 pull-requests 和提交 issues ♥️
|
||||
|
||||
- [**Todo App(代办事项)\(stdweb\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/std_web/todomvc)
|
||||
- [**Todo App(代办事项)\(web_sys\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/web_sys/todomvc)
|
||||
- [**Custom Components(自定义 Component 组件)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/custom_components)
|
||||
- [**Multi-threading \(Agents\)(多线程 Agents)\(stdweb\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/std_web/multi_thread)
|
||||
- [**Multi-threading \(Agents\)(多线程 Agents)\(web_sys\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/web_sys/multi_thread)
|
||||
- [**Timer Service(计时器)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/timer)
|
||||
- [**Nested Components(嵌套 Component 组件)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/nested_list)
|
|
@ -0,0 +1,5 @@
|
|||
# CSS
|
||||
|
||||
<TODO>
|
||||
|
||||
对适当的 CSS 支持的提案可以在这里找到:[https://github.com/yewstack/yew/issues/533](https://github.com/yewstack/yew/issues/533)
|
|
@ -0,0 +1,50 @@
|
|||
# Debugging
|
||||
|
||||
## Panics
|
||||
|
||||
Please use the [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook) crate for nicer stacktraces with Rust symbols. Note, that it is not compatible with apps built with `cargo-web`.
|
||||
|
||||
## 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
|
||||
// setup
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
}
|
||||
|
||||
// usage
|
||||
log::info!("Update: {:?}", msg);
|
||||
```
|
||||
|
||||
### **\`\`**[**`ConsoleService`**](https://docs.rs/yew/0.13.2/yew/services/console/struct.ConsoleService.html)**\`\`**
|
||||
|
||||
This service is included within yew and is available when the `"services"` feature is enabled:
|
||||
|
||||
```rust
|
||||
// usage
|
||||
ConsoleService::info(format!("Update: {:?}", msg).as_ref());
|
||||
```
|
||||
|
||||
## 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,39 @@
|
|||
---
|
||||
description: Yew 框架规划功能的路线图
|
||||
---
|
||||
|
||||
# 路线图
|
||||
|
||||
## `v1.0.0`
|
||||
|
||||
### 规划中的功能
|
||||
|
||||
- 标记 key 的列表项:[https://github.com/yewstack/yew/issues/479](https://github.com/yewstack/yew/issues/479)
|
||||
- 路由:[https://github.com/yewstack/yew_router](https://github.com/yewstack/yew_router)
|
||||
|
||||
### 生产环境准备
|
||||
|
||||
- 浏览器兼容性
|
||||
- 提高 Yew 框架的测试覆盖率
|
||||
- 增加性能基准测试:[https://github.com/yewstack/yew/issues/5](https://github.com/yewstack/yew/issues/5)
|
||||
|
||||
### 指南
|
||||
|
||||
- 最佳实践:[https://yew.rs/optimizations](https://yew.rs/optimizations)
|
||||
- 端到端教程
|
||||
- Futures / 并发
|
||||
- CSS / 样式
|
||||
- 测试
|
||||
- 状态管理
|
||||
|
||||
## 未来
|
||||
|
||||
### 潜在功能
|
||||
|
||||
- 服务端渲染:[https://github.com/yewstack/yew/issues/41](https://github.com/yewstack/yew/issues/41)
|
||||
- 组件库:[https://github.com/yewstrap/yewstrap](https://github.com/yewstrap/yewstrap)
|
||||
- 代码分割:[https://github.com/yewstack/yew/issues/599](https://github.com/yewstack/yew/issues/599)
|
||||
- 允许不同的虚拟 DOM 后端:[https://github.com/yewstack/yew/issues/482](https://github.com/yewstack/yew/issues/482)
|
||||
- 反思 Services:[https://github.com/yewstack/yew/issues/364](https://github.com/yewstack/yew/issues/364)
|
||||
- 成熟的工具包:[https://github.com/yewstack/yewtil](https://github.com/yewstack/yewtil)
|
||||
- HTML 模板备选方案:[https://github.com/yewstack/yew/issues/438](https://github.com/yewstack/yew/issues/438)
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
description: 测试你的应用程序
|
||||
---
|
||||
|
||||
# 测试
|
||||
|
||||
<TODO>
|
||||
|
||||
## Rust WebDriving
|
||||
|
||||
使用 Rust 以编程方式驱动 UI 集成测试,[fantoccini](https://crates.io/crates/fantoccini) 是一个推荐的选择。它允许你通过使用 CSS 选择器来查找特定的元素,然后对它们执行特定的操作,例如输入文本,点击按钮,或等待特定时间以使客户端代码执行(例如等待一个网络请求完成并导致 UI 改变),来测试你的网站。
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"version.label": {
|
||||
"message": "0.21",
|
||||
"description": "The label for version 0.21"
|
||||
},
|
||||
"sidebar.docs.category.Getting Started": {
|
||||
"message": "Getting Started",
|
||||
"description": "The label for category Getting Started in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts": {
|
||||
"message": "Concepts",
|
||||
"description": "The label for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts.link.generated-index.title": {
|
||||
"message": "Yew concepts",
|
||||
"description": "The generated-index page title for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Concepts.link.generated-index.description": {
|
||||
"message": "Learn about the important Yew concepts!",
|
||||
"description": "The generated-index page description for category Concepts in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew": {
|
||||
"message": "Using Basic Web Technologies In Yew",
|
||||
"description": "The label for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew.link.generated-index.title": {
|
||||
"message": "Yew's Take on Basic Web Technologies",
|
||||
"description": "The generated-index page title for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Using Basic Web Technologies In Yew.link.generated-index.description": {
|
||||
"message": "Yew centrally operates on the idea of keeping everything that a reusable piece of UI may needin one place - rust files, while also keeping the underlying technology accessible where necessary. Explore further to fully grasp what we mean by these statements:",
|
||||
"description": "The generated-index page description for category Using Basic Web Technologies In Yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Components": {
|
||||
"message": "Components",
|
||||
"description": "The label for category Components in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Hooks": {
|
||||
"message": "Hooks",
|
||||
"description": "The label for category Hooks in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.HTML": {
|
||||
"message": "HTML",
|
||||
"description": "The label for category HTML in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics": {
|
||||
"message": "Advanced topics",
|
||||
"description": "The label for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics.link.generated-index.title": {
|
||||
"message": "Advanced topics",
|
||||
"description": "The generated-index page title for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Advanced topics.link.generated-index.description": {
|
||||
"message": "Learn about the advanced topics and inner workings of Yew!",
|
||||
"description": "The generated-index page description for category Advanced topics in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Struct Components": {
|
||||
"message": "Struct Components",
|
||||
"description": "The label for category Struct Components in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.More": {
|
||||
"message": "More",
|
||||
"description": "The label for category More in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.More.link.generated-index.title": {
|
||||
"message": "Miscellaneous",
|
||||
"description": "The generated-index page title for category More in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.Migration guides": {
|
||||
"message": "Migration guides",
|
||||
"description": "The label for category Migration guides in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew": {
|
||||
"message": "yew",
|
||||
"description": "The label for category yew in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew-agent": {
|
||||
"message": "yew-agent",
|
||||
"description": "The label for category yew-agent in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.yew-router": {
|
||||
"message": "yew-router",
|
||||
"description": "The label for category yew-router in sidebar docs"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
description: 關於框架的底層細節
|
||||
---
|
||||
|
||||
# 內部底層的 library
|
||||
|
||||
元件生命周期的狀態機與 vdom diff 演算法
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
description: 加速你的專案
|
||||
---
|
||||
|
||||
# 優化與最佳實例
|
||||
|
||||
## neq_assign
|
||||
|
||||
當元件從父元件接收到屬性時, `change` 的方法就會被呼叫。除了讓你更新元件的狀態,也讓你回傳,決定元件是否要在屬性改變時,重新渲染自己的布林值 `ShouldRender` 。
|
||||
|
||||
重新渲染是很浪費效能的,儘可能避免這麼做。一般來說,只有在屬性真的改變時,才重新渲染。下面的程式碼是體現這個原則的例子,當屬性改變時,才回傳 `true`:
|
||||
|
||||
```rust
|
||||
use yew::ShouldRender;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct ExampleProps;
|
||||
|
||||
struct Example {
|
||||
props: ExampleProps,
|
||||
};
|
||||
|
||||
impl Example {
|
||||
fn change(&mut self, props: ExampleProps) -> ShouldRender {
|
||||
if self.props != props {
|
||||
self.props = props;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但我們可以走的更遠!這六行的模板,使用一個 trait 和一個 實作了 `PartialEq` 的 blanket implementation ,可以被縮短至一行。請參考[這裡](https://docs.rs/yewtil/*/yewtil/trait.NeqAssign.html), `yewtil` 的 crate 裡的 `NeqAssign` trait。
|
||||
|
||||
## RC
|
||||
|
||||
為了避免重新渲染時,複製大量的資料來建立屬性,我們可以使用智慧指針來讓程式只複製指針。如果你使用 `Rc<_>` 來封裝你的屬性,而不是未封裝的值,你可以使用 `Rc::make_mut`,去複製與存取你想要改變的資料的可變參考,這做到了延遲複製,直到你需要更動子元件的資料。透過避免複製直到有值改變,子元件可以在 `Component::change` 拒絕與他狀態中的屬性相同值的屬性,而且這樣不會有任何效能成本。另外,這個方式,資料必須在與子元件比較與被拒絕之前,被複製進父元件的屬性中。
|
||||
|
||||
這個優化最那些無法 `Copy` 的資料型別最有用。如果你可以輕易複製你的資料,那把資料放進智慧指針裡面似乎就沒有這麼值得。對於那些包含很多像是 `Vec` 、 `HashMap` 與 `String` 的結構,這個優化對他們會更值得。
|
||||
|
||||
如果子元件幾乎不會更新值,那這個優化效果會很好,甚至如果父元件也很少更新,那效果會更好。上面的情況,使在純元件中使用 `Rc<_>s` 是一個封裝屬性值很好的選擇。
|
||||
|
||||
## View 方法
|
||||
|
||||
出於程式碼的可讀性,通常會寫方法包裝複雜的 `html!`,這樣你可以避免巢狀的 HTML 造成過多的向右縮排。
|
||||
|
||||
## 純元件/函數式元件
|
||||
|
||||
純元件 是一種不會改變自己狀態的元件,他們只單純顯示內容或是向普通可變的元件傳送訊息。他們和 view 方法不同的地方在於們可以在 `html!` 巨集中使用,語法會像(`<SomePureComponent />`),而不是表達式語法(`{some_view_function()}`),而且根據他們的實作方式,他們可以被 memoized,這樣可以套用前面所述的 `neq_assign` 的邏輯避免重新渲染。
|
||||
|
||||
Yew 本身不支援純元件或是函數式元件,但是你可以透過 external crates 使用。
|
||||
|
||||
函數式元件還不存在,但是理論上純元件可以透過巨集與宣告方法產生。
|
||||
|
||||
## Keyed DOM nodes when they arrive
|
||||
|
||||
## 使用 Cargo Workspaces 加速編譯
|
||||
|
||||
Yew 最大的缺點就是花太多時間在編譯上了。編譯時間似乎和 `html!` 巨集中的程式碼質量相同。 對於小專案來說,這應該不是什麼大問題,但是對於有很多頁面的大型網頁應用程式來說,就必須要將程式碼封裝成很多 crates 以減少編譯所花的時間。
|
||||
|
||||
你應該將路由與頁面區塊封裝成一個 main crate,然後將共用的程式碼與元件封裝成另一個 crate,將每個頁面會用到的不同的元件,各自封裝到不同的 crate 中,或是只產生 `Html` 的大方法中。最好的狀況,你只需要重新編譯你 main crate 與修改的頁面的 crate 的程式碼;而最壞的情況,你編輯了共用的 crate,你就必須重新編譯所有依賴這個共用 crate 的程式碼。
|
||||
|
||||
如果你的 main crate 太過龐大,或是你希望快速迭代深層巢狀的頁面(一個頁面渲染另一個頁面的頂層),你可以使用範例的 crate ,在一個簡單的主頁面上編輯你未完成的元件。
|
||||
|
||||
## 編譯大小的優化 <a id="build-size-optimization"></a>
|
||||
|
||||
- 優化 Rust 的程式碼
|
||||
- `cargo.toml` (定義釋出的設定檔)
|
||||
- 使用 `wasm-opt` 優化 wasm 程式碼
|
||||
|
||||
更多關於程式碼大小的資訊,請參考: [rustwasm book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size)
|
||||
|
||||
### Cargo.toml <a id="cargo-toml"></a>
|
||||
|
||||
你可以設定你的發行版本更小的檔案大小,透過設定 `Cargo.toml` 的 `[profile.release]` 。
|
||||
|
||||
[Rust profiles documentation](https://doc.rust-lang.org/cargo/reference/profiles.html)
|
||||
|
||||
```rust
|
||||
[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 aggresive )
|
||||
opt-level = 'z'
|
||||
# optimization for size
|
||||
# opt-level = 's'
|
||||
# link time optimization using using whole-program analysis
|
||||
lto = true
|
||||
```
|
||||
|
||||
### wasm-opt <a id="wasm-opt"></a>
|
||||
|
||||
更多優化 `wasm` 程式碼大小的方法。
|
||||
|
||||
wasm-opt 資訊: [binaryen project](https://github.com/WebAssembly/binaryen)
|
||||
|
||||
Rust Wasm 中有一個關於減少 Wasm 二進位檔大小的章節:[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html)
|
||||
|
||||
- 使用`wasm-pack` 預設在發行版本編譯時優化 `wasm` 程式碼
|
||||
- 直接在 wasm 檔案上使用 `wasm-opt` 。
|
||||
|
||||
```rust
|
||||
wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm
|
||||
```
|
||||
|
||||
#### 編譯 yew/examples/ 中 最小的例子 <a id="build-size-of-minimal-example-in-yew-examples"></a>
|
||||
|
||||
注意: `wasm-pack` 包含對 Rust 與 wasm 程式碼的優化。而`wasm-bindgen` 只是一個單純的例子,沒有對 `Rust` 做任何優化。
|
||||
|
||||
| used tool | size |
|
||||
| :-------------------------- | :---- |
|
||||
| wasm-bindgen | 158KB |
|
||||
| wasm-binggen + wasm-opt -Os | 116KB |
|
||||
| wasm-pack | 99KB |
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
description: ComponentLink 與 Callbacks.
|
||||
---
|
||||
|
||||
# Callbacks
|
||||
|
||||
元件的「link」是一個讓元件註冊 callbacks 並自我更新的機制。
|
||||
|
||||
## ComponentLink API
|
||||
|
||||
### callback
|
||||
|
||||
註冊一個 callback 後,當這個 callback 被執行時,會發送一個訊息給元件的更新機制。在生命周期的勾子下,他會呼叫 `send_self` 並將被閉包回傳的訊息帶給他。
|
||||
|
||||
提供一個 `Fn(IN) -> Vec<COMP::Message>` 並回傳一個 `Callback<IN>` 。
|
||||
|
||||
### send_message
|
||||
|
||||
當現在的迴圈結束後,向元件發送訊息,並且開啟另一個迴圈。
|
||||
|
||||
### send_message_batch
|
||||
|
||||
註冊一個 callback,當這個 callback 被執行時,這個 callback 會一次送很多訊息。如果有任何一個訊息導致元件被重新渲染,元件會在所有批次送來的訊息都被處理完後,再重新渲染。
|
||||
|
||||
提供一個 `Fn(IN) -> COMP::Message` 並回傳一個 `Callback<IN>` 。
|
||||
|
||||
## Callbacks
|
||||
|
||||
_(他可能需要一個獨立的短頁來介紹)_
|
||||
|
||||
Callbacks 被用來當作 services 、 agents 與父元件跟 Yew 溝通的方式。他們只是一個被 `Rc` 包裹著的 `Fn`,好讓他們可以被複製。
|
||||
|
||||
他們有一個 `emit` 方法,這個方法拿他們的 `<IN>` 型別當作參數,並且轉換他作為目的地所期望的訊息。如果一個從父元件來的 callback 被提供作為子元件的屬性,子元件可以在他的 update 生命周期中,呼叫 callback 中的 emit 以傳遞訊息回給父元件。 在 `html!` 巨集中的閉包與方法如果被當作屬性傳遞,會被自動轉為 Callbacks。
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
description: 元件,以及生命周期鉤子
|
||||
---
|
||||
|
||||
# 元件
|
||||
|
||||
## 什麼是元件?
|
||||
|
||||
元件是 Yew 的基石。他們管理自己的狀態,可以渲染自己成為 DOM。元件可以透過實作,描述元件生命周期的 `Component` trait 來建立。
|
||||
|
||||
## 生命周期
|
||||
|
||||
:::note
|
||||
`歡迎來貢獻我們的文件:` [Add a diagram of the component lifecycle](https://github.com/yewstack/docs/issues/22)
|
||||
:::
|
||||
|
||||
## 生命周期的方法
|
||||
|
||||
### Create
|
||||
|
||||
當一個元件被建立,他會接收從父元件,也就是 `ComponentLink` ,傳下來的屬性。 這些屬性用來初始化元件的狀態,此外,「link」可以用來註冊回調函式或傳訊息給元件。
|
||||
|
||||
通常,你的元件 struct 會儲存 props 與 link,就像下面的例子:
|
||||
|
||||
```rust
|
||||
pub struct MyComponent {
|
||||
props: Props,
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Properties = Props;
|
||||
// ...
|
||||
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
MyComponent { props, link }
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
元件會在 `view()` 方法中宣告佈局。Yew 提供 `html!` 巨集來宣告 HTML 合 SVG 的結點,包含他們的監聽事件與子結點。這個巨集扮演像是 React 的 JSX 的角色,但是是使用 Rust 的表達式,而不是 JavaScript 的。
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let onclick = self.link.callback(|_| Msg::Click);
|
||||
html! {
|
||||
<button {onclick}>{ self.props.button_text }</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
更多使用細節,請參考 [`html!` 教學](concepts/html/introduction.mdx)。
|
||||
|
||||
### Rendered
|
||||
|
||||
`rendered()` 生命周期的方法會,在 `view()` 處理完並且 Yew 渲染完你的元件之後,與瀏覽器刷新頁面之前,被呼叫。一個元件可能希望實作這個方法,去執行只能在元件被渲染完元素才能做的事情。 你可以透過 `first_render` 變數來確認這個元件是不是第一次被渲染。
|
||||
|
||||
```rust
|
||||
use stdweb::web::html_element::InputElement;
|
||||
use stdweb::web::IHtmlElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct MyComponent {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<input ref={self.node_ref.clone()} type="text" />
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, first_render: bool) {
|
||||
if first_render {
|
||||
if let Some(input) = self.node_ref.cast::<InputElement>() {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
注意,這個生命周期方法,不是一定要被實作,預設的行為是不做任何事情。
|
||||
:::
|
||||
|
||||
### Update
|
||||
|
||||
元件是可動態更新且可以註冊接收非同步的訊息。 `update()` 生命周期方法會被每個訊息呼叫。他基於訊息是什麼,來允許元件更新自己,且會決定是否需要重新渲染。 訊息可以被 HTML 元素的監聽器觸發,或被子元件、Agents、Services 或 Futures 傳送。
|
||||
|
||||
`update()` 應用範例:
|
||||
|
||||
```rust
|
||||
pub enum Msg {
|
||||
SetInputEnabled(bool)
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
|
||||
// ...
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::SetInputEnabled(enabled) => {
|
||||
if self.input_enabled != enabled {
|
||||
self.input_enabled = enabled;
|
||||
true // 重新渲染
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change
|
||||
|
||||
元件可能會被他的父元件重新渲染。當他被父元件重新渲染時,他會收到新的屬性,然後決定要不要再渲染一次。 這設計是讓父元件透過便於跟子元件溝通。
|
||||
|
||||
一個簡單的實作方式像:
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
// ...
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
if self.props != props {
|
||||
self.props = props;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Destroy
|
||||
|
||||
當元件從 DOM 上被解除掛載,Yew 會呼叫 `destroy()` 生命周期方法以提供任何需要清理的操作。這個方法是不一定要被實作的,預設不會做設任何事。
|
||||
|
||||
## 相關的型別
|
||||
|
||||
`Component` trait 有兩個相關的型別:`Message` 與 `Properties`。
|
||||
|
||||
```rust
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`Message` 負責各式各樣的訊息,他可能被元件處理去觸發各種影響。舉例來說,你可能有一個 `Click` 的訊息,他會觸發 API 請求,或是切換 UI 元件的樣貌。下面是一個常見的實作,在你的元件模組中,創建一個叫作 `Msg` 的 enum,然後把他當作元件裡的 Message 型別。通常 message 會縮寫成 msg。
|
||||
|
||||
```rust
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
```
|
||||
|
||||
`Properties` 代表要從父員件傳遞到子元件的資訊。這個型別必須實作 `Properties` trait (通常會 deriving 他)並且可以決定某個屬性是必要的屬性,或是可選的屬性。這個型別會在創建元件的時候,或是更新元件的時候被使用到。常見的實作會在你的元件模組中,建立一個叫作 `Props` 的 struct,然後把他當作元件的`Properties` 型別。通常 properties 或縮寫成 props。因為屬性是從父原件被傳下來的,所以應用程式中的根元件的 `Properties` 原則上都是 `()`。如果你希望你的根元件有特定的屬性,可以使用 `App::mount_with_props` 的方法。
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
description: 父元件與子元件的溝通橋樑
|
||||
---
|
||||
|
||||
# Properties
|
||||
|
||||
屬性讓子元件與父元件可以互相溝通。
|
||||
|
||||
## Derive macro
|
||||
|
||||
不要嘗試自己實作 `Properties`,而是用`#[derive(Properties)]`derive 他。
|
||||
|
||||
### 必填的欄位
|
||||
|
||||
預設所有在 `Properties` struct 裡的欄位都是必填的。當必填的欄位沒有值,而元件在 `html!` 巨集中又被建立,編譯器就會報錯。如果希望欄位是可選的,可以使用 `#[prop_or_default]` 來讓該欄位有預設值。如果希望欄位預設特定值,可以使用 `#[prop_or_else(value)]` ,裡面的 value 就會是這個欄位的預設值。舉例來說,希望預設值是 `true`可以在欄位宣告上面這樣寫 `#[prop_or_else(true)]`. 通常可選的屬性,會用 `Option` ,且預設值為`None`。
|
||||
|
||||
### PartialEq
|
||||
|
||||
如果可以,最好在你的屬性上面 derive `PartialEq` 。他可以避免畫面多餘的渲染,更細節的內容請參考,**優化與最佳實例**的區塊。
|
||||
|
||||
## 屬性的記憶體與速度的開銷
|
||||
|
||||
在 `Component::view`,裡,你可以拿到元件狀態的參考,且用他來建立 `Html` 。但是屬性是有所有權的。這代表著為了建立屬性,並且將他們傳遞給子元件,我們必須取得被 `view` 方法拿走的所有權。 當將參考傳給元件時,可以透過隱式的複製來做到得到所有權。
|
||||
|
||||
這意味著,每個元件,都有從父元件傳遞下來的獨有的狀態複本,且每當你重新渲染一次元件,被重新渲染的元件的所有的子元件的屬性就會被重新複製一次。
|
||||
|
||||
代表如果你要在屬性中傳遞*大量*的資料(大於 10 KB 的字串之類的),你可能需要考慮將你的子元件變成一個回傳 `Html` 的方法,讓父元件呼叫,以避免資料被複製。
|
||||
|
||||
如果你不需要改變傳下去的資料,你可以用 `Rc` 將資料包裝起來,這樣就會只複製參考的指針,而不是資料本身。
|
||||
|
||||
## 範例
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use yew::Properties;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum LinkColor {
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
Black,
|
||||
Purple,
|
||||
}
|
||||
|
||||
impl Default for LinkColor {
|
||||
fn default() -> Self {
|
||||
// 除非有指定,否則預設是藍色
|
||||
LinkColor::Blue
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
/// 連結必須要有一個目標
|
||||
href: String,
|
||||
/// 如果連結文字很大,複製字串的參考可以減少記憶體的開銷
|
||||
/// 但除非效能已經成為嚴重的問題,否則通常不建議這麼做
|
||||
text: Rc<String>,
|
||||
/// 連結的顏色
|
||||
#[prop_or_default]
|
||||
color: LinkColor,
|
||||
/// 如果為 None,那 view 方法將不會指定 size
|
||||
#[prop_or_default]
|
||||
size: Option<u32>,
|
||||
/// 當沒有指定 active,預設為 true
|
||||
#[prop_or(true)]
|
||||
active: bool,
|
||||
}
|
||||
```
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
description: 外帶 DOM 的存取
|
||||
---
|
||||
|
||||
# Refs
|
||||
|
||||
## Refs
|
||||
|
||||
`ref` 關鍵字可以被使用在任何 HTML 的元素或是元件,用來得到那個物件附加的 DOM `Element`。這個可以在 view 生命周期方法之外,改變 DOM。
|
||||
|
||||
對於要存取 canvas 元素,或滾動到頁面不同的區塊,很有幫助。
|
||||
|
||||
語法可以這樣使用:
|
||||
|
||||
```rust
|
||||
// 建立
|
||||
self.node_ref = NodeRef::default();
|
||||
|
||||
// 在 view 裡
|
||||
html! {
|
||||
<div ref={self.node_ref.clone()}></div>
|
||||
}
|
||||
|
||||
// 更新
|
||||
let has_attributes = self.node_ref.cast::<Element>().unwrap().has_attributes();
|
||||
```
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
description: Yew 的 Actor 系統
|
||||
---
|
||||
|
||||
# Agents
|
||||
|
||||
Agents 類似於 Angular 的 [Services](https://angular.io/guide/architecture-services) (但沒有依賴注入)而且提供 Tew 一個 [Actor Model](https://en.wikipedia.org/wiki/Actor_model). Agents 可以用來作為兩個元件間的路由訊息,而且與他們在元件間的層級關係獨立出來,所以他也可以用來作為一個全域的狀態,甚至可以用來減輕用來渲染 UI 畫面的主執行緒的大量運算任務。 未來,我們還規劃要讓 agents 幫忙 Yew 專案可以跨頁籤溝通。
|
||||
|
||||
為了讓 agents 可以並行, Yew 使用了 [web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)。
|
||||
|
||||
## 生命周期
|
||||
|
||||

|
||||
|
||||
## Agents 的型別
|
||||
|
||||
#### 範圍
|
||||
|
||||
- Job - 在 UI 執行緒上,為每一個 bridge,新增一個 agent。這對於將「共享但獨立的行為」移出元件很有用。(待驗證)當工作結束,agent 會消失。
|
||||
- Context - Bridges 會建立並連接上 UI 執行緒上的 agent。這可以用來協調元件與其他 agent 之間的狀態。當沒有任何 bridge 連接上這個 agent,這個 agnet 就會消失。
|
||||
- Private - 與 Job 相同,但是是在自己的 web worker 上執行的。
|
||||
- Public - 與 Context 相同,但是是在自己的 web worker 上執行的。
|
||||
- Global (編寫中)
|
||||
|
||||
## 在 Agents 與元件之間溝通
|
||||
|
||||
### Bridges
|
||||
|
||||
bridge 允許 agent 與元件進行雙向的溝通。bridge 也允許 agents 之間互相溝通。
|
||||
|
||||
### Dispatchers
|
||||
|
||||
dispatcher 允許元件與 agnet 進行單向的溝通。dispatcher 也允許元件向 agnet 發送訊息。
|
||||
|
||||
## 開銷
|
||||
|
||||
Agents 透過使用 [bincode](https://github.com/servo/bincode) 序列化他們的訊息,來溝通。所以比起呼叫方法,他的效能花費比較高。除非計算的成本,或是跨元件計算的成本,比傳遞訊息的成本要高,否則 agnet 的方法儘量只有包含單純的邏輯運算。
|
||||
|
||||
## 延伸閱讀
|
||||
|
||||
- [web_worker_fib](https://github.com/yewstack/yew/tree/master/examples/web_worker_fib) 範例顯示了如何在 agnets 之間溝通。
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
description: 建立複雜元件層級與佈局
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
## 基本
|
||||
|
||||
任何實作 `Component` 的型別,都可以在 `html!` 的巨集中使用:
|
||||
|
||||
```rust
|
||||
html!{
|
||||
<>
|
||||
// 沒有屬性
|
||||
<MyComponent />
|
||||
|
||||
// 有屬性
|
||||
<MyComponent prop1="lorem" prop2="ipsum" />
|
||||
|
||||
// 一次提供很多屬性
|
||||
<MyComponent ..props />
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## 巢狀
|
||||
|
||||
只要元件的 `Properties` 中有 `children`,就可以傳遞子結點給元件。
|
||||
|
||||
{% code title="parent.rs" %}
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<Container>
|
||||
<h4>{ "Hi" }</h4>
|
||||
<div>{ "Hello" }</div>
|
||||
</Container>
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
{% code title="container.rs" %}
|
||||
|
||||
```rust
|
||||
pub struct Container(Props);
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
impl Component for Container {
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div id="container">
|
||||
{ self.0.children.clone() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
## 指定子結點的型別
|
||||
|
||||
如果指定了子結點的型別,就可以使用或改變巢狀元件的屬性。下面的範例就是, `List` 元件包裹 `ListItem` 元件。另一個真實的範例是 `yew-router` 的原始碼。還有一個更進階的範例,請參考 Yew GitHub repo 中的 `nested-list` 範例。
|
||||
|
||||
{% code title="parent.rs" %}
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<List>
|
||||
<ListItem value="a" />
|
||||
<ListItem value="b" />
|
||||
<ListItem value="c" />
|
||||
</List>
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
{% code title="list.rs" %}
|
||||
|
||||
```rust
|
||||
pub struct List(Props);
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
pub children: ChildrenWithProps<ListItem>,
|
||||
}
|
||||
|
||||
impl Component for List {
|
||||
type Properties = Props;
|
||||
|
||||
// ...
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html!{{
|
||||
for self.0.children.iter().map(|mut item| {
|
||||
item.props.value = format!("item-{}", item.props.value);
|
||||
item
|
||||
})
|
||||
}}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
|
@ -0,0 +1,264 @@
|
|||
---
|
||||
description: HTML 與 SVG 元件都支援
|
||||
---
|
||||
|
||||
# Elements
|
||||
|
||||
## 標籤結構
|
||||
|
||||
元件標籤都必須要是自封閉的標籤 `<... />` 或是跟開啟標籤對應的關閉標籤。
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Open - Close-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div id="my_div"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--INVALID-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div id="my_div"> // <- 缺少關閉標籤
|
||||
}
|
||||
```
|
||||
|
||||
<!--Self-Closing-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<input id="my_input" />
|
||||
}
|
||||
```
|
||||
|
||||
<!--INVALID-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<input id="my_input"> // <- 缺少自封閉標籤語法
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
:::note
|
||||
為了方便起見,通常需要關閉標籤的元件,也都可以用自封閉標籤表示。例如,寫 `html! { <div class="placeholder" /> }` 是合法的。
|
||||
:::
|
||||
|
||||
## 子結點
|
||||
|
||||
輕鬆寫出複雜巢狀的 HTML 與 SVG 架構:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--HTML-->
|
||||
|
||||
```rust
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
<!--SVG-->
|
||||
|
||||
```rust
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## Classes
|
||||
|
||||
你很多方便的選項可以寫元件裡的 class:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Literal-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class="container"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Multiple-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class="container center-align"></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Interpolated-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={format!("{}-container", size)}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Expression-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={self.classes()}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Tuple-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={("class-1", "class-2")}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Vector-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div class={vec!["class-1", "class-2"]}></div>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## 監聽
|
||||
|
||||
監聽器的屬性必須要傳入一個 `Callback` ,他封裝了閉包。callback 的內容取決於,當觸發監聽事件時,你希望應用程式有什麼反應:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Component Handler-->
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
Click,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
MyComponent { link }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Click => {
|
||||
// 處理點擊事件
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// 從一個元件連結中,建立一個 callback 並在元件中處理他
|
||||
let click_callback = self.link.callback(|_: ClickEvent| Msg::Click);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--Agent Handler-->
|
||||
|
||||
```rust
|
||||
struct MyComponent {
|
||||
worker: Dispatcher<MyWorker>,
|
||||
}
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
MyComponent {
|
||||
worker: MyWorker::dispatcher()
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// 從一個 worker 中建立一個 callback,並在其他的 context 中處理他
|
||||
let click_callback = self.worker.callback(|_: ClickEvent| WorkerMsg::Process);
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--Other Cases-->
|
||||
|
||||
```rust
|
||||
struct MyComponent;
|
||||
|
||||
impl Component for MyComponent {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
MyComponent
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
// 建立一個臨時的 callback
|
||||
let click_callback = Callback::from(|| {
|
||||
ConsoleService::log("clicked!");
|
||||
});
|
||||
|
||||
html! {
|
||||
<button onclick={click_callback}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
description: 用來產生 HTML 與 SVG 的巨集
|
||||
slug: /concepts/html
|
||||
---
|
||||
|
||||
# 使用 html!
|
||||
|
||||
`html!` 巨集可以讓你用 HTML 與 SVG 寫元件。如果你寫過 React 的 JSX(一種 JavaScript 的擴展,可以讓你在 JavaScript 中寫 HTML),應該會覺得這兩者十分相似。
|
||||
|
||||
**重要提示**
|
||||
|
||||
1. 在 `html!` 裡,只能有一個根結點(但你可以用 [Fragment 或是 Iterators](https://yew.rs/concepts/html/lists) 來繞過這個限制。)
|
||||
2. 空的 `html! {}` 是合法的,且他不會渲染任何東西在畫面上
|
||||
3. 字串必須被雙引號與大括號包裹住:`html! { "Hello, World" }`
|
|
@ -0,0 +1,57 @@
|
|||
# Lists
|
||||
|
||||
## Fragments
|
||||
|
||||
`html!` 巨集裡必須只有一個根結點。為了可以繞過這個限制,將兩個以上的結點,用空的標籤包裹起來,是合法的:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Valid-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<>
|
||||
<div></div>
|
||||
<p></p>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Invalid-->
|
||||
|
||||
```rust
|
||||
/* error: only one root html element allowed */
|
||||
|
||||
html! {
|
||||
<div></div>
|
||||
<p></p>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||
|
||||
## Iterators
|
||||
|
||||
Yew 支援兩種不同的方式,從 iterator 建構 html:
|
||||
|
||||
<!--DOCUSAURUS_CODE_TABS-->
|
||||
<!--Syntax Type 1-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ self.props.items.iter().map(renderItem).collect::<Html>() }
|
||||
</ul>
|
||||
}
|
||||
```
|
||||
|
||||
<!--Syntax Type 2-->
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<ul class="item-list">
|
||||
{ for self.props.items.iter().map(renderItem) }
|
||||
</ul>
|
||||
}
|
||||
```
|
||||
|
||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
@ -0,0 +1,57 @@
|
|||
# Literals & Expressions
|
||||
|
||||
## Literals
|
||||
|
||||
如果表達式中的型別有實作 `Display` ,他們會被轉換成字串,並在 DOM 中作為 [Text](https://developer.mozilla.org/en-US/docs/Web/API/Text) (文字)結點。
|
||||
|
||||
所有的文字都必須用 `{}` 括起來,因為文字是被當作表達式處理。這是 HTML 語法與 Yew 的語法中,最大的不同。
|
||||
|
||||
```rust
|
||||
let text = "lorem ipsum";
|
||||
html!{
|
||||
<>
|
||||
<div>{text}</div>
|
||||
<div>{"dolor sit"}</div>
|
||||
<span>{42}</span>
|
||||
</>
|
||||
}
|
||||
```
|
||||
|
||||
## Expressions
|
||||
|
||||
只要可以回傳 `Html`,你都可以在你的 HTML 中用 `{}` 插入表達式。
|
||||
|
||||
```rust
|
||||
html! {
|
||||
<div>
|
||||
{
|
||||
if show_link {
|
||||
html! {
|
||||
<a href="https://example.com">{"Link"}</a>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
通常把這些表達式與包裝成方法或閉包會比較好,可以提升可讀性:
|
||||
|
||||
```rust
|
||||
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,81 @@
|
|||
---
|
||||
description: Yew 的官方路由器
|
||||
---
|
||||
|
||||
# 路由器
|
||||
|
||||
[https://crates.io/crates/yew-router](https://crates.io/crates/yew-router)
|
||||
|
||||
單頁應用程式(SPA)中的路由器,會依據 URL 來顯示不同的畫面。當連結被點擊後,路由器沒有預設要請求遠端的資源, 而是將 URL 設定導向應用程式中的有效路由。路由器會偵測 URL 被更改,然後決定要渲染什麼畫面。
|
||||
|
||||
## 核心元素
|
||||
|
||||
### Route
|
||||
|
||||
包含一個字串,這個字串是網域名後的那串文字,並且可以選擇要不要將狀態存入 history api。
|
||||
|
||||
### RouteService
|
||||
|
||||
與瀏覽器溝通,存取路由。
|
||||
|
||||
### RouteAgent
|
||||
|
||||
擁有 RouteService 並且協調與更新,從應用程式邏輯造成的,或是從瀏覽器事件中造成的,路由的改變。
|
||||
|
||||
### Switch
|
||||
|
||||
`Switch` trait 用於讓 Route 在實作的 `trait` 之間來回轉換。
|
||||
|
||||
### Router
|
||||
|
||||
Router 元件會與 `RouteAgent` 溝通,並且自動解析從 agent 到 switch 的 Routes,Routes 會在 render 的 屬性中被揭露,這個屬性會決定 switch 的結果如何被轉換成 `Html`。
|
||||
|
||||
## 如何使用路由器
|
||||
|
||||
首先,你要建立一個代表你的應用程式所有狀態的型別。特別注意,這個型別可以是 enum、struct 都可以,而且你可以透過在裡面實作 `Switch` 來巢狀其他項目
|
||||
|
||||
然後你必須為你的型別 derive `Switch` 。對 enums 來說,每一個變數都必須宣告 `#[to = "/some/route"]`,或是如果你用 struct,那就要 struct 的外部宣告。
|
||||
|
||||
```rust
|
||||
#[derive(Switch)]
|
||||
enum AppRoute {
|
||||
#[to="/login"]
|
||||
Login,
|
||||
#[to="/register"]
|
||||
Register,
|
||||
#[to="/delete_account"]
|
||||
Delete,
|
||||
#[to="/posts/{id}"]
|
||||
ViewPost(i32),
|
||||
#[to="/posts/view"]
|
||||
ViewPosts,
|
||||
#[to="/"]
|
||||
Home
|
||||
}
|
||||
```
|
||||
|
||||
特別注意,這個巨集會試著依序配對每個變數,所以如果有任何路由可能配對到兩著不同的 `to` 宣告,那會配對到第一個,而第二個就永遠不會被配對到。舉例來說,如果你定義以下的 `Switch` ,那路由將永遠只會配對到 `AppRoute::Home`。
|
||||
|
||||
```rust
|
||||
#[derive(Switch)]
|
||||
enum AppRoute {
|
||||
#[to="/"]
|
||||
Home,
|
||||
#[to="/login"]
|
||||
Login,
|
||||
#[to="/register"]
|
||||
Register,
|
||||
#[to="/delete_account"]
|
||||
Delete,
|
||||
#[to="/posts/{id}"]
|
||||
ViewPost(i32),
|
||||
#[to="/posts/view"]
|
||||
ViewPosts,
|
||||
}
|
||||
```
|
||||
|
||||
你還可以拿到 url 中的參數,透過在`#[to = ""]` 中宣告 `{}`。`{}` 代表下一個分隔符號("/"、"?"、"&"、"\#" )之前, url 中的參數。`{*}` 表示取得直到後續字符匹配為止之間的變數,如果不存在任何字串,則它將匹配任何內容。 `{<number>}` 表示取得特定數量的的分隔符號之前的變數。(例如: `{2}` 會取得兩個分隔符號之前的變數。)
|
||||
|
||||
對於有命名欄位的 struct 與 enum,你必須給出變數的名字,像是: `{user_name}` 或是 `{*:age}`。
|
||||
|
||||
Switch trait 可以協助取得比起字串要更有結構的變數。你可以實作 `Switch`,這樣你就可以得到特定結構的變數,而他會是一個 `unsize`。但如果這個 URL 無法被轉換,就會被視為沒有匹配。
|
|
@ -0,0 +1,127 @@
|
|||
# 第一個簡單的 App
|
||||
|
||||
首先,先建立一個新的 binary 專案:
|
||||
|
||||
```bash
|
||||
cargo new --bin yew-app && cd yew-app
|
||||
```
|
||||
|
||||
在依賴庫裡加入 `yew` 與 `wasm-bindgen`(最新的版號,請參考[這裡](https://docs.rs/yew))
|
||||
|
||||
{% code title="Cargo.toml" %}
|
||||
|
||||
```text
|
||||
[package]
|
||||
name = "yew-app"
|
||||
version = "0.1.0"
|
||||
authors = ["Yew App Developer <name@example.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
yew = "0.16"
|
||||
wasm-bindgen = "0.2"
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
將下面的模板複製進你的 `src/lib.rs` 檔案:
|
||||
|
||||
{% code title="src/lib.rs" %}
|
||||
|
||||
```rust
|
||||
use wasm_bindgen::prelude::*;
|
||||
use yew::prelude::*;
|
||||
|
||||
struct Model {
|
||||
link: ComponentLink<Self>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
AddOne,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Self {
|
||||
link,
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::AddOne => self.value += 1
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
||||
// 如果有新的不同屬性,應該只能回傳 true
|
||||
// 若是這個元件沒有任何屬性,那就可以只回傳 false
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<button onclick={self.link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
|
||||
<p>{ self.value }</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run_app() {
|
||||
App::<Model>::new().mount_to_body();
|
||||
}
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
模板會建置名叫 `Model` 的根元件 `Component`,Model 會顯示一個按鈕,當你按下按鈕時, `Model` 會更新自己的狀態。需要特別注意的是,在 `main()` 裡的 `App::<Model>::new().mount_to_body()`,他會啟動你的 app 並且掛載 `Model` 裡的 HTML 到 `<body>` 標籤中。如果你想要在啟動應用程式時,帶入動態的屬性,你可以改用 `App::<Model>::new().mount_to_body_with_props(..)`。
|
||||
|
||||
最後,在你的專案,新增 `static` 資料夾,並新增 `index.html` 檔案到 static 裡。
|
||||
|
||||
```bash
|
||||
mkdir static
|
||||
```
|
||||
|
||||
{% code title="index.html" %}
|
||||
|
||||
```bash
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Yew Sample App</title>
|
||||
<script type="module">
|
||||
import init from "./wasm.js"
|
||||
init()
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
```
|
||||
|
||||
{% endcode %}
|
||||
|
||||
## 執行你的 App!
|
||||
|
||||
使用 [`wasm-pack`](https://rustwasm.github.io/docs/wasm-pack/) 來執行專案是比較好的選擇。如果你還沒有做任何準備,先用`cargo install wasm-pack`安裝 `wasm-pack` ,然後用下面的指令,建置與開啟開發用伺服器:
|
||||
|
||||
```bash
|
||||
wasm-pack build --target web --out-name wasm --out-dir ./static
|
||||
```
|
||||
|
||||
`wasm-pack` 會在 `./static` 裡產生一個 bundle,裡面包含專案編成的 WebAssembly,以及 JavaScript 的包裹器,這些東西都會在你的專案執行時被載入。
|
||||
|
||||
最後,用你最喜歡的網頁伺服器,去啟動在`./static` 底下的檔案。範例:
|
||||
|
||||
```bash
|
||||
cargo +nightly install miniserve
|
||||
miniserve ./static --index index.html
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
# 透過範例學習
|
||||
|
||||
我們有各種範例(都持續在維護中),建議你可以仔細閱讀他們,以了解如何使用各種不同的框架功能。當遇到問題或需要幫忙時,我們也很歡迎大家 pull-requests 或開 issues ♥️
|
||||
|
||||
- [**Todo App \(stdweb\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/std_web/todomvc)
|
||||
- [**Todo App \(web_sys\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/web_sys/todomvc)
|
||||
- [**Custom Components**](https://github.com/yewstack/yew/tree/v0.14.0/examples/custom_components)
|
||||
- [**Multi-threading \(Agents\) \(stdweb\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/std_web/multi_thread)
|
||||
- [**Multi-threading \(Agents\) \(web_sys\)**](https://github.com/yewstack/yew/tree/v0.14.0/examples/web_sys/multi_thread)
|
||||
- [**Timer Service**](https://github.com/yewstack/yew/tree/v0.14.0/examples/timer)
|
||||
- [**Nested Components**](https://github.com/yewstack/yew/tree/v0.14.0/examples/nested_list)
|
|
@ -0,0 +1,5 @@
|
|||
# CSS
|
||||
|
||||
<TODO>
|
||||
|
||||
有關 CSS 的支援與建議可以在這裡找到: [https://github.com/yewstack/yew/issues/533](https://github.com/yewstack/yew/issues/533)
|
|
@ -0,0 +1,50 @@
|
|||
# 除錯
|
||||
|
||||
## Panics
|
||||
|
||||
請使用 [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook) crate ,他會用 Rust symbols 來做 stacktraces。注意,他跟 `cargo-web` 不相容。
|
||||
|
||||
## Console Logging
|
||||
|
||||
通常,Wasm 的網頁應用程式可以跟瀏覽器的 API 互操作,所以 `console.log` 這個 api 也不例外,你可以使用以下幾種方法:
|
||||
|
||||
### [`wasm-logger`](https://crates.io/crates/wasm-logger)
|
||||
|
||||
這個 crate 整合了令人熟悉的 Rust `log` crate:
|
||||
|
||||
```rust
|
||||
// 設定
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
}
|
||||
|
||||
// 使用
|
||||
log::info!("Update: {:?}", msg);
|
||||
```
|
||||
|
||||
### **\`\`**[**`ConsoleService`**](https://docs.rs/yew/0.13.2/yew/services/console/struct.ConsoleService.html)**\`\`**
|
||||
|
||||
Yew 包含了這個 service,而且如果 `"services"` 這個 feaure 有被打開的話,你可以直接使用他:
|
||||
|
||||
```rust
|
||||
// 使用
|
||||
ConsoleService::info(format!("Update: {:?}", msg).as_ref());
|
||||
```
|
||||
|
||||
## Source Maps
|
||||
|
||||
目前 Rust/Wasm 網頁應用程式,不對 source maps 第一線支援。當然,這件事在未來可能會改變,如果這裡寫的資訊不正確,或是事情有所變化,請建議我們修改這篇文件!
|
||||
|
||||
### 最新資訊
|
||||
|
||||
\[2019 12 月\] [Chrome DevTools update](https://developers.google.com/web/updates/2019/12/webassembly#the_future)
|
||||
|
||||
> 但還是有大量的工作要做。舉例還說,在工具方面,Emscripten \(Binaryen\) 與 wasm-pack \(wasm-bindgen\),還不支援更新轉換他們的行為的 DWARF 資訊。
|
||||
|
||||
\[2020\] [Rust Wasm 除錯指南](https://rustwasm.github.io/book/reference/debugging.html#using-a-debugger)
|
||||
|
||||
> 不幸地,WebAssembly 的除錯還不夠完善。在大部分的 Unix 系統中,[DWARF](http://dwarfstd.org/) 被用來編碼除錯器需要提供的程式碼等級的資訊。還有一種在 Windows 上的編碼資訊。但現在還沒有跟 WebAssembly 等價。
|
||||
|
||||
\[2019\] [Rust Wasm roadmap](https://rustwasm.github.io/rfcs/007-2019-roadmap.html#debugging)
|
||||
|
||||
> 除錯是一件棘手的事情,因為大部分的事情都不是掌握在這個工作群組中,而是依賴 WebAssembly 的標準,與瀏覽器的開發者工具如何實作。
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
description: Yew 的版本紀錄
|
||||
---
|
||||
|
||||
# Roadmap
|
||||
|
||||
### 優先順序
|
||||
|
||||
即將推出的新功能和重點開發方向的優先順序將由社群決定。在 2020 的春季,我們會發出一個開發者調查,收集專案方向的回饋。你可以在 [Yew Wiki](https://github.com/yewstack/yew/wiki/Dev-Survey-%5BSpring-2020%5D) 中找到結果。
|
||||
|
||||
:::note
|
||||
你可以在 Yew GitHub 追蹤我們主要的開發方向 [Project board](https://github.com/yewstack/yew/projects)
|
||||
:::
|
||||
|
||||
### 重點 <a id="focuses"></a>
|
||||
|
||||
1. 需求最多的功能
|
||||
2. 產品準備
|
||||
3. 文件
|
||||
4. 痛點
|
||||
|
||||
#### 需求最多的功能 <a id="top-requested-features"></a>
|
||||
|
||||
1. [函數式元件](https://github.com/yewstack/yew/projects/3)
|
||||
2. [元件函式庫](https://github.com/yewstack/yew/projects/4)
|
||||
3. 更好的狀態管理器
|
||||
4. [Server side rendering](https://github.com/yewstack/yew/projects/5)
|
||||
|
||||
#### 產品準備
|
||||
|
||||
- 提升 Yew 的測試覆蓋率
|
||||
- 減少二進位檔的大小
|
||||
- [Benchmark performance](https://github.com/yewstack/yew/issues/5)
|
||||
|
||||
#### 文件
|
||||
|
||||
- 建立教學文件
|
||||
- 簡化專案設定
|
||||
|
||||
#### 痛點
|
||||
|
||||
- [元件模板](https://github.com/yewstack/yew/issues/830)
|
||||
- Fetch API
|
||||
- Agents
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
description: 測試你的專案
|
||||
---
|
||||
|
||||
# 測試
|
||||
|
||||
<TODO>
|
||||
|
||||
## wasm_bindgen_test <a id="wasm_bindgen_test"></a>
|
||||
|
||||
Rust Wasm 工作群組有維護一個 crate 叫作 [`wasm_bindgen_test`](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html) ,他讓你可以在瀏覽器裡跑類似於用內建的巨集`#[test]`測試流程。 更多資訊可以參考 [Rust Wasm working group's documentation](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/index.html)。
|
|
@ -8,12 +8,13 @@
|
|||
"name": "yew-docs",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "~2.4.0",
|
||||
"@docusaurus/plugin-client-redirects": "~2.4.0",
|
||||
"@docusaurus/plugin-content-docs": "~2.4.0",
|
||||
"@docusaurus/plugin-content-pages": "~2.4.0",
|
||||
"@docusaurus/plugin-google-analytics": "~2.4.1",
|
||||
"@docusaurus/theme-classic": "~2.4.1",
|
||||
"@docusaurus/core": "^2.4.3",
|
||||
"@docusaurus/plugin-client-redirects": "^2.4.3",
|
||||
"@docusaurus/plugin-content-docs": "^2.4.3",
|
||||
"@docusaurus/plugin-content-pages": "^2.4.3",
|
||||
"@docusaurus/plugin-google-analytics": "^2.4.3",
|
||||
"@docusaurus/plugin-google-gtag": "^2.4.3",
|
||||
"@docusaurus/theme-classic": "^2.4.3",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.32.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
|
@ -26,7 +27,7 @@
|
|||
"url-loader": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "~2.4.0",
|
||||
"@docusaurus/module-type-aliases": "^2.4.3",
|
||||
"@tsconfig/docusaurus": "^2.0.0",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
|
@ -1851,9 +1852,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/core": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.4.1.tgz",
|
||||
"integrity": "sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.4.3.tgz",
|
||||
"integrity": "sha512-dWH5P7cgeNSIg9ufReX6gaCl/TmrGKD38Orbwuz05WPhAQtFXHd5B8Qym1TiXfvUNvwoYKkAJOJuGe8ou0Z7PA==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/generator": "^7.18.7",
|
||||
|
@ -1865,13 +1866,13 @@
|
|||
"@babel/runtime": "^7.18.6",
|
||||
"@babel/runtime-corejs3": "^7.18.6",
|
||||
"@babel/traverse": "^7.18.8",
|
||||
"@docusaurus/cssnano-preset": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/mdx-loader": "2.4.1",
|
||||
"@docusaurus/cssnano-preset": "2.4.3",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@docusaurus/mdx-loader": "2.4.3",
|
||||
"@docusaurus/react-loadable": "5.5.2",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-common": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-common": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"@slorber/static-site-generator-webpack-plugin": "^4.0.7",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"autoprefixer": "^10.4.7",
|
||||
|
@ -2167,9 +2168,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/cssnano-preset": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.1.tgz",
|
||||
"integrity": "sha512-ka+vqXwtcW1NbXxWsh6yA1Ckii1klY9E53cJ4O9J09nkMBgrNX3iEFED1fWdv8wf4mJjvGi5RLZ2p9hJNjsLyQ==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.3.tgz",
|
||||
"integrity": "sha512-ZvGSRCi7z9wLnZrXNPG6DmVPHdKGd8dIn9pYbEOFiYihfv4uDR3UtxogmKf+rT8ZlKFf5Lqne8E8nt08zNM8CA==",
|
||||
"dependencies": {
|
||||
"cssnano-preset-advanced": "^5.3.8",
|
||||
"postcss": "^8.4.14",
|
||||
|
@ -2181,9 +2182,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/logger": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.4.1.tgz",
|
||||
"integrity": "sha512-5h5ysIIWYIDHyTVd8BjheZmQZmEgWDR54aQ1BX9pjFfpyzFo5puKXKYrYJXbjEHGyVhEzmB9UXwbxGfaZhOjcg==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.4.3.tgz",
|
||||
"integrity": "sha512-Zxws7r3yLufk9xM1zq9ged0YHs65mlRmtsobnFkdZTxWXdTYlWWLWdKyNKAsVC+D7zg+pv2fGbyabdOnyZOM3w==",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"tslib": "^2.4.0"
|
||||
|
@ -2193,14 +2194,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/mdx-loader": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.4.1.tgz",
|
||||
"integrity": "sha512-4KhUhEavteIAmbBj7LVFnrVYDiU51H5YWW1zY6SmBSte/YLhDutztLTBE0PQl1Grux1jzUJeaSvAzHpTn6JJDQ==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.4.3.tgz",
|
||||
"integrity": "sha512-b1+fDnWtl3GiqkL0BRjYtc94FZrcDDBV1j8446+4tptB9BAOlePwG2p/pK6vGvfL53lkOsszXMghr2g67M0vCw==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.18.8",
|
||||
"@babel/traverse": "^7.18.8",
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@mdx-js/mdx": "^1.6.22",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
|
@ -2224,12 +2225,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/module-type-aliases": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.1.tgz",
|
||||
"integrity": "sha512-gLBuIFM8Dp2XOCWffUDSjtxY7jQgKvYujt7Mx5s4FCTfoL5dN1EVbnrn+O2Wvh8b0a77D57qoIDY7ghgmatR1A==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.3.tgz",
|
||||
"integrity": "sha512-cwkBkt1UCiduuvEAo7XZY01dJfRn7UR/75mBgOdb1hKknhrabJZ8YH+7savd/y9kLExPyrhe0QwdS9GuzsRRIA==",
|
||||
"dependencies": {
|
||||
"@docusaurus/react-loadable": "5.5.2",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
|
@ -2243,15 +2244,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-client-redirects": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.1.tgz",
|
||||
"integrity": "sha512-tp0j16gaLIJ4p+IR0P6KDOFsTOGGMY54MNPnmM61Vaqqt5omLqsuKUO8UlCGU1oW/4EIQOhXYy99XYY5MjE+7A==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.3.tgz",
|
||||
"integrity": "sha512-iCwc/zH8X6eNtLYdyUJFY6+GbsbRgMgvAC/TmSmCYTmwnoN5Y1Bc5OwUkdtoch0XKizotJMRAmGIAhP8sAetdQ==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-common": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-common": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"eta": "^2.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -2266,17 +2267,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-blog": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.1.tgz",
|
||||
"integrity": "sha512-E2i7Knz5YIbE1XELI6RlTnZnGgS52cUO4BlCiCUCvQHbR+s1xeIWz4C6BtaVnlug0Ccz7nFSksfwDpVlkujg5Q==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.3.tgz",
|
||||
"integrity": "sha512-PVhypqaA0t98zVDpOeTqWUTvRqCEjJubtfFUQ7zJNYdbYTbS/E/ytq6zbLVsN/dImvemtO/5JQgjLxsh8XLo8Q==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/mdx-loader": "2.4.1",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-common": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@docusaurus/mdx-loader": "2.4.3",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-common": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"feed": "^4.2.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
|
@ -2296,17 +2297,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-docs": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.1.tgz",
|
||||
"integrity": "sha512-Lo7lSIcpswa2Kv4HEeUcGYqaasMUQNpjTXpV0N8G6jXgZaQurqp7E8NGYeGbDXnb48czmHWbzDL4S3+BbK0VzA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.3.tgz",
|
||||
"integrity": "sha512-N7Po2LSH6UejQhzTCsvuX5NOzlC+HiXOVvofnEPj0WhMu1etpLEXE6a4aTxrtg95lQ5kf0xUIdjX9sh3d3G76A==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/mdx-loader": "2.4.1",
|
||||
"@docusaurus/module-type-aliases": "2.4.1",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@docusaurus/mdx-loader": "2.4.3",
|
||||
"@docusaurus/module-type-aliases": "2.4.3",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"@types/react-router-config": "^5.0.6",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
|
@ -2326,15 +2327,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-pages": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.1.tgz",
|
||||
"integrity": "sha512-/UjuH/76KLaUlL+o1OvyORynv6FURzjurSjvn2lbWTFc4tpYY2qLYTlKpTCBVPhlLUQsfyFnshEJDLmPneq2oA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.3.tgz",
|
||||
"integrity": "sha512-txtDVz7y3zGk67q0HjG0gRttVPodkHqE0bpJ+7dOaTH40CQFLSh7+aBeGnPOTl+oCPG+hxkim4SndqPqXjQ8Bg==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/mdx-loader": "2.4.1",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/mdx-loader": "2.4.3",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"tslib": "^2.4.0",
|
||||
"webpack": "^5.73.0"
|
||||
|
@ -2348,13 +2349,31 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-google-analytics": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.1.tgz",
|
||||
"integrity": "sha512-dyZJdJiCoL+rcfnm0RPkLt/o732HvLiEwmtoNzOoz9MSZz117UH2J6U2vUDtzUzwtFLIf32KkeyzisbwUCgcaQ==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.3.tgz",
|
||||
"integrity": "sha512-KzBV3k8lDkWOhg/oYGxlK5o9bOwX7KpPc/FTWoB+SfKhlHfhq7qcQdMi1elAaVEIop8tgK6gD1E58Q+XC6otSQ==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-google-gtag": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.3.tgz",
|
||||
"integrity": "sha512-5FMg0rT7sDy4i9AGsvJC71MQrqQZwgLNdDetLEGDHLfSHLvJhQbTCUGbGXknUgWXQJckcV/AILYeJy+HhxeIFA==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -2378,22 +2397,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-classic": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.4.1.tgz",
|
||||
"integrity": "sha512-Rz0wKUa+LTW1PLXmwnf8mn85EBzaGSt6qamqtmnh9Hflkc+EqiYMhtUJeLdV+wsgYq4aG0ANc+bpUDpsUhdnwg==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.4.3.tgz",
|
||||
"integrity": "sha512-QKRAJPSGPfDY2yCiPMIVyr+MqwZCIV2lxNzqbyUW0YkrlmdzzP3WuQJPMGLCjWgQp/5c9kpWMvMxjhpZx1R32Q==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/mdx-loader": "2.4.1",
|
||||
"@docusaurus/module-type-aliases": "2.4.1",
|
||||
"@docusaurus/plugin-content-blog": "2.4.1",
|
||||
"@docusaurus/plugin-content-docs": "2.4.1",
|
||||
"@docusaurus/plugin-content-pages": "2.4.1",
|
||||
"@docusaurus/theme-common": "2.4.1",
|
||||
"@docusaurus/theme-translations": "2.4.1",
|
||||
"@docusaurus/types": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-common": "2.4.1",
|
||||
"@docusaurus/utils-validation": "2.4.1",
|
||||
"@docusaurus/core": "2.4.3",
|
||||
"@docusaurus/mdx-loader": "2.4.3",
|
||||
"@docusaurus/module-type-aliases": "2.4.3",
|
||||
"@docusaurus/plugin-content-blog": "2.4.3",
|
||||
"@docusaurus/plugin-content-docs": "2.4.3",
|
||||
"@docusaurus/plugin-content-pages": "2.4.3",
|
||||
"@docusaurus/theme-common": "2.4.3",
|
||||
"@docusaurus/theme-translations": "2.4.3",
|
||||
"@docusaurus/types": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-common": "2.4.3",
|
||||
"@docusaurus/utils-validation": "2.4.3",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"copy-text-to-clipboard": "^3.0.1",
|
||||
|
@ -2417,17 +2436,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-common": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.1.tgz",
|
||||
"integrity": "sha512-G7Zau1W5rQTaFFB3x3soQoZpkgMbl/SYNG8PfMFIjKa3M3q8n0m/GRf5/H/e5BqOvt8c+ZWIXGCiz+kUCSHovA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.3.tgz",
|
||||
"integrity": "sha512-7KaDJBXKBVGXw5WOVt84FtN8czGWhM0lbyWEZXGp8AFfL6sZQfRTluFp4QriR97qwzSyOfQb+nzcDZZU4tezUw==",
|
||||
"dependencies": {
|
||||
"@docusaurus/mdx-loader": "2.4.1",
|
||||
"@docusaurus/module-type-aliases": "2.4.1",
|
||||
"@docusaurus/plugin-content-blog": "2.4.1",
|
||||
"@docusaurus/plugin-content-docs": "2.4.1",
|
||||
"@docusaurus/plugin-content-pages": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/utils-common": "2.4.1",
|
||||
"@docusaurus/mdx-loader": "2.4.3",
|
||||
"@docusaurus/module-type-aliases": "2.4.3",
|
||||
"@docusaurus/plugin-content-blog": "2.4.3",
|
||||
"@docusaurus/plugin-content-docs": "2.4.3",
|
||||
"@docusaurus/plugin-content-pages": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"@docusaurus/utils-common": "2.4.3",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
|
@ -2447,9 +2466,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-translations": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.1.tgz",
|
||||
"integrity": "sha512-T1RAGP+f86CA1kfE8ejZ3T3pUU3XcyvrGMfC/zxCtc2BsnoexuNI9Vk2CmuKCb+Tacvhxjv5unhxXce0+NKyvA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz",
|
||||
"integrity": "sha512-H4D+lbZbjbKNS/Zw1Lel64PioUAIT3cLYYJLUf3KkuO/oc9e0QCVhIYVtUI2SfBCF2NNdlyhBDQEEMygsCedIg==",
|
||||
"dependencies": {
|
||||
"fs-extra": "^10.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
|
@ -2459,9 +2478,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/types": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.4.1.tgz",
|
||||
"integrity": "sha512-0R+cbhpMkhbRXX138UOc/2XZFF8hiZa6ooZAEEJFp5scytzCw4tC1gChMFXrpa3d2tYE6AX8IrOEpSonLmfQuQ==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.4.3.tgz",
|
||||
"integrity": "sha512-W6zNLGQqfrp/EoPD0bhb9n7OobP+RHpmvVzpA+Z/IuU3Q63njJM24hmT0GYboovWcDtFmnIJC9wcyx4RVPQscw==",
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
|
@ -2478,11 +2497,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/utils": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.4.1.tgz",
|
||||
"integrity": "sha512-1lvEZdAQhKNht9aPXPoh69eeKnV0/62ROhQeFKKxmzd0zkcuE/Oc5Gpnt00y/f5bIsmOsYMY7Pqfm/5rteT5GA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.4.3.tgz",
|
||||
"integrity": "sha512-fKcXsjrD86Smxv8Pt0TBFqYieZZCPh4cbf9oszUq/AMhZn3ujwpKaVYZACPX8mmjtYx0JOgNx52CREBfiGQB4A==",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
|
@ -2512,9 +2531,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/utils-common": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.4.1.tgz",
|
||||
"integrity": "sha512-bCVGdZU+z/qVcIiEQdyx0K13OC5mYwxhSuDUR95oFbKVuXYRrTVrwZIqQljuo1fyJvFTKHiL9L9skQOPokuFNQ==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.4.3.tgz",
|
||||
"integrity": "sha512-/jascp4GbLQCPVmcGkPzEQjNaAk3ADVfMtudk49Ggb+131B1WDD6HqlSmDf8MxGdy7Dja2gc+StHf01kiWoTDQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
|
@ -2531,12 +2550,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@docusaurus/utils-validation": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.4.1.tgz",
|
||||
"integrity": "sha512-unII3hlJlDwZ3w8U+pMO3Lx3RhI4YEbY3YNsQj4yzrkZzlpqZOLuAiZK2JyULnD+TKbceKU0WyWkQXtYbLNDFA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.4.3.tgz",
|
||||
"integrity": "sha512-G2+Vt3WR5E/9drAobP+hhZQMaswRwDlp6qOMi7o7ZypB+VO7N//DZWhZEwhcRGepMDJGQEwtPv7UxtYwPL9PBw==",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "2.4.1",
|
||||
"@docusaurus/utils": "2.4.1",
|
||||
"@docusaurus/logger": "2.4.3",
|
||||
"@docusaurus/utils": "2.4.3",
|
||||
"joi": "^17.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
|
@ -4681,9 +4700,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.14",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
|
||||
"integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==",
|
||||
"version": "10.4.16",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
|
||||
"integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -4692,12 +4711,16 @@
|
|||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"browserslist": "^4.21.5",
|
||||
"caniuse-lite": "^1.0.30001464",
|
||||
"fraction.js": "^4.2.0",
|
||||
"browserslist": "^4.21.10",
|
||||
"caniuse-lite": "^1.0.30001538",
|
||||
"fraction.js": "^4.3.6",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
|
@ -4965,9 +4988,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"version": "4.21.11",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz",
|
||||
"integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -4976,13 +4999,17 @@
|
|||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
"caniuse-lite": "^1.0.30001538",
|
||||
"electron-to-chromium": "^1.4.526",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
|
@ -5111,9 +5138,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001473",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
|
||||
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
|
||||
"version": "1.0.30001538",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz",
|
||||
"integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -6384,9 +6411,9 @@
|
|||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.348",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
|
||||
"integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ=="
|
||||
"version": "1.4.528",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz",
|
||||
"integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
|
@ -7246,15 +7273,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
|
||||
"integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/infusion"
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
|
@ -9152,9 +9179,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
|
@ -12583,9 +12610,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -12594,6 +12621,10 @@
|
|||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
|
@ -12601,7 +12632,7 @@
|
|||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist-lint": "cli.js"
|
||||
"update-browserslist-db": "cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"browserslist": ">= 4.21.0"
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
"fmt:write": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "~2.4.0",
|
||||
"@docusaurus/plugin-client-redirects": "~2.4.0",
|
||||
"@docusaurus/plugin-content-docs": "~2.4.0",
|
||||
"@docusaurus/plugin-content-pages": "~2.4.0",
|
||||
"@docusaurus/plugin-google-analytics": "~2.4.1",
|
||||
"@docusaurus/theme-classic": "~2.4.1",
|
||||
"@docusaurus/core": "^2.4.3",
|
||||
"@docusaurus/plugin-client-redirects": "^2.4.3",
|
||||
"@docusaurus/plugin-content-docs": "^2.4.3",
|
||||
"@docusaurus/plugin-content-pages": "^2.4.3",
|
||||
"@docusaurus/plugin-google-gtag": "^2.4.3",
|
||||
"@docusaurus/theme-classic": "^2.4.3",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.32.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
|
@ -47,7 +47,7 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "~2.4.0",
|
||||
"@docusaurus/module-type-aliases": "^2.4.3",
|
||||
"@tsconfig/docusaurus": "^2.0.0",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
|
|
|
@ -154,7 +154,7 @@ module.exports = {
|
|||
type: 'category',
|
||||
label: 'yew',
|
||||
items: [
|
||||
'migration-guides/yew/from-0_20_0-to-next',
|
||||
'migration-guides/yew/from-0_20_0-to-0_21_0',
|
||||
'migration-guides/yew/from-0_19_0-to-0_20_0',
|
||||
'migration-guides/yew/from-0_18_0-to-0_19_0',
|
||||
],
|
||||
|
|
|
@ -0,0 +1,318 @@
|
|||
---
|
||||
title: 'Children'
|
||||
---
|
||||
|
||||
:::caution
|
||||
|
||||
Inspecting and manipulating `Children` can often result in surprising and hard-to-explain behaviours in your application.
|
||||
This can lead to edge cases and often does not yield expected result.
|
||||
You should consider other approaches if you are trying to manipulate `Children`.
|
||||
|
||||
Yew supports using `Html` as the type of the children prop.
|
||||
You should use `Html` as children if you do not need `Children` or `ChildrenRenderer`.
|
||||
It doesn't have the drawbacks of `Children` and has a lower performance overhead.
|
||||
|
||||
:::
|
||||
|
||||
## 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, Component, Context, Html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ListProps {
|
||||
#[prop_or_default]
|
||||
pub children: Html,
|
||||
}
|
||||
|
||||
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">
|
||||
{ctx.props().children.clone()}
|
||||
</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>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Nested Children with Props
|
||||
|
||||
Nested component properties can be accessed and mutated if the containing component types its children.
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct ListItemProps {
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn ListItem(props: &ListItemProps) -> Html {
|
||||
let ListItemProps { value } = props.clone();
|
||||
html! {
|
||||
<span>
|
||||
{value}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub children: ChildrenWithProps<ListItem>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn List(props: &Props) -> Html {
|
||||
let modified_children = props.children.iter().map(|mut item| {
|
||||
let mut props = Rc::make_mut(&mut item.props);
|
||||
props.value = format!("item-{}", props.value);
|
||||
item
|
||||
});
|
||||
html! { for modified_children }
|
||||
}
|
||||
|
||||
html! {
|
||||
<List>
|
||||
<ListItem value="a" />
|
||||
<ListItem value="b" />
|
||||
<ListItem value="c" />
|
||||
</List>
|
||||
};
|
||||
```
|
||||
|
||||
### 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 />
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
- 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](https://github.com/yewstack/yew/tree/master/examples/nested_list) in the main yew repository.
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
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 for the page. This becomes increasingly useful as your
|
||||
application gets more interactive and your codebase gets larger. Rather than manually writing
|
||||
all of the code to manipulate the DOM yourself, the macro will handle it for you.
|
||||
|
||||
Using the `html!` macro can feel pretty magical, but it has nothing to hide. If you are curious about
|
||||
how it works, try expanding the `html!` macro calls in your program. There is a useful command called
|
||||
`cargo expand` which allows you to see the expansion of Rust macros. `cargo expand` does not ship with
|
||||
`cargo` by default so you will need to install it with `cargo install cargo-expand` if you have not
|
||||
already. [Rust-Analyzer](https://rust-analyzer.github.io/) also provides a mechanism for
|
||||
[obtaining macro output from within an IDE](https://rust-analyzer.github.io/manual.html#expand-macro-recursively).
|
||||
|
||||
Output from the `html!` macro is often pretty terse! This is a feature: machine-generated code can
|
||||
sometimes clash with other code in an application. 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 are
|
||||
using members from the correct 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 helpful for libraries that 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 it 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,24 @@
|
|||
---
|
||||
title: 'Immutable Types'
|
||||
description: 'Immutable data structures for Yew'
|
||||
---
|
||||
|
||||
## What are immutable types?
|
||||
|
||||
These are types that you can instantiate but never mutate the values. In order
|
||||
to update a value, you must instantiate a new value.
|
||||
|
||||
## Why using immutable types?
|
||||
|
||||
Properties, like in React, are propagated from ancestors to
|
||||
children. This means that the properties must live when each component is
|
||||
updated. This is why properties should —ideally— be cheap to clone. To
|
||||
achieve this we usually wrap things in `Rc`.
|
||||
|
||||
Immutable types are a great fit for holding property's values because they can
|
||||
be cheaply cloned when passed from component to component.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable)
|
||||
- [Crate `implicit-clone`](https://docs.rs/implicit-clone/)
|
|
@ -0,0 +1,163 @@
|
|||
---
|
||||
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).**
|
||||
|
||||
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 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, but it also adds the 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 that is shared between the crates containing different parts of
|
||||
the application could be stored in a separate crate which the project depends on.
|
||||
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 additionally render the component you are working on.
|
||||
|
||||
## Reducing binary sizes
|
||||
|
||||
- optimize Rust code
|
||||
- `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).**
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### Nightly Cargo configuration
|
||||
|
||||
You can also gain additional benefits from experimental nightly features of rust and
|
||||
cargo. To use the nightly toolchain with `trunk`, set the `RUSTUP_TOOLCHAIN="nightly"` environment
|
||||
variable. Then, you can configure unstable rustc features in your `.cargo/config.toml`.
|
||||
Refer to the doc of [unstable features], specifically the section about [`build-std`] and
|
||||
[`build-std-features`], to understand the configuration.
|
||||
|
||||
```toml, title=".cargo/config.toml"
|
||||
[unstable]
|
||||
# Requires the rust-src component. `rustup +nightly component add rust-src`
|
||||
build-std = ["std", "panic_abort"]
|
||||
build-std-features = ["panic_immediate_abort"]
|
||||
```
|
||||
|
||||
[unstable features]: https://doc.rust-lang.org/cargo/reference/unstable.html
|
||||
[`build-std`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std
|
||||
[`build-std-features`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std-features
|
||||
|
||||
:::caution
|
||||
The nightly rust compiler can contain bugs, such as [this one](https://github.com/yewstack/yew/issues/2696),
|
||||
that require occasional attention and tweaking. Use these experimental options with care.
|
||||
:::
|
||||
|
||||
### wasm-opt
|
||||
|
||||
Further, it is possible to optimize the 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,64 @@
|
|||
---
|
||||
title: 'Portals'
|
||||
description: 'Rendering into out-of-tree DOM nodes'
|
||||
---
|
||||
|
||||
## What is a portal?
|
||||
|
||||
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 an `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 low-level building block. Libraries should use it to implement
|
||||
higher-level APIs which can then be consumed by applications. For 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::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ModalProps {
|
||||
#[prop_or_default]
|
||||
pub children: Html,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn Modal(props: &ModalProps) -> Html {
|
||||
let modal_host = gloo::utils::document()
|
||||
.get_element_by_id("modal_host")
|
||||
.expect("Expected to find a #modal_host element");
|
||||
|
||||
create_portal(
|
||||
props.children.clone(),
|
||||
modal_host.into(),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Event handling
|
||||
|
||||
Events emitted on elements inside portals follow the virtual DOM when bubbling up. That is,
|
||||
if a portal is rendered as the child of an element, then an event listener on that element
|
||||
will catch events dispatched from inside the portal, even if the portal renders its contents
|
||||
in an unrelated location in the actual DOM.
|
||||
|
||||
This allows developers to be oblivious of whether a component they consume, is implemented with
|
||||
or without portals. Events fired on its children will bubble up regardless.
|
||||
|
||||
A known issue is that events from portals into **closed** shadow roots will be dispatched twice,
|
||||
once targeting the element inside the shadow root and once targeting the host element itself. Keep
|
||||
in mind that **open** shadow roots work fine. If this impacts you, feel free to open a bug report
|
||||
about it.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Portals example](https://github.com/yewstack/yew/tree/master/examples/portals)
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
title: 'Server-side Rendering'
|
||||
description: 'Render Yew on the server-side.'
|
||||
---
|
||||
|
||||
# Server-side Rendering
|
||||
|
||||
By default, Yew components render on the client side. When a viewer
|
||||
visits a website, the server sends a skeleton HTML file without any actual
|
||||
content and a WebAssembly bundle to the browser.
|
||||
Everything is rendered on the client side by the WebAssembly
|
||||
bundle. This is known as client-side rendering.
|
||||
|
||||
This approach works fine for most websites, with some caveats:
|
||||
|
||||
1. Users will not be able to see anything until the entire WebAssembly
|
||||
bundle is downloaded and the initial render has been completed.
|
||||
This can result in a poor experience for users on a slow network.
|
||||
2. Some search engines do not support dynamically rendered web content and
|
||||
those who do usually rank dynamic websites lower in the search results.
|
||||
|
||||
To solve these problems, we can render our website on the server side.
|
||||
|
||||
## How it Works
|
||||
|
||||
Yew provides a `ServerRenderer` to render pages on the
|
||||
server side.
|
||||
|
||||
To render Yew components on the server side, you can create a renderer
|
||||
with `ServerRenderer::<App>::new()` and call `renderer.render().await`
|
||||
to render `<App />` into a `String`.
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
use yew::ServerRenderer;
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! {<div>{"Hello, World!"}</div>}
|
||||
}
|
||||
|
||||
// we use `flavor = "current_thread"` so this snippet can be tested in CI,
|
||||
// where tests are run in a WASM environment. You likely want to use
|
||||
// the (default) `multi_thread` favor as:
|
||||
// #[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn no_main() {
|
||||
let renderer = ServerRenderer::<App>::new();
|
||||
|
||||
let rendered = renderer.render().await;
|
||||
|
||||
// Prints: <div>Hello, World!</div>
|
||||
println!("{}", rendered);
|
||||
}
|
||||
```
|
||||
|
||||
## Component Lifecycle
|
||||
|
||||
The recommended way of working with server-side rendering is
|
||||
function components.
|
||||
|
||||
All hooks other than `use_effect` (and `use_effect_with`)
|
||||
will function normally until a component successfully renders into `Html`
|
||||
for the first time.
|
||||
|
||||
:::caution Web APIs are not available!
|
||||
|
||||
Web APIs such as `web_sys` are not available when your component is
|
||||
rendering on the server side.
|
||||
Your application will panic if you try to use them.
|
||||
You should isolate logics that need Web APIs in `use_effect` or
|
||||
`use_effect_with` as effects are not executed during server-side rendering.
|
||||
|
||||
:::
|
||||
|
||||
:::danger Struct Components
|
||||
|
||||
While it is possible to use Struct Components with server-side rendering,
|
||||
there are no clear boundaries between client-side safe logic like the
|
||||
`use_effect` hook for function components and lifecycle events are invoked
|
||||
in a different order than the client side.
|
||||
|
||||
In addition, Struct Components will continue to accept messages until all of its
|
||||
children are rendered and `destroy` method is called. Developers need to
|
||||
make sure no messages possibly passed to components would link to logic
|
||||
that makes use of Web APIs.
|
||||
|
||||
When designing an application with server-side rendering support,
|
||||
prefer function components unless you have a good reason not to.
|
||||
|
||||
:::
|
||||
|
||||
## Data Fetching during Server-side Rendering
|
||||
|
||||
Data fetching is one of the difficult points with server-side rendering and hydration.
|
||||
|
||||
Traditionally, when a component renders, it is instantly available
|
||||
(outputs a virtual DOM to be rendered). This works fine when the
|
||||
component does not want to fetch any data. But what happens if the component
|
||||
wants to fetch some data during rendering?
|
||||
|
||||
In the past, there was no mechanism for Yew to detect whether a component is still
|
||||
fetching data. The data-fetching client is responsible to implement
|
||||
a solution to detect what is being requested during the initial render and triggers
|
||||
a second render after requests are fulfilled. The server repeats this process until
|
||||
no more pending requests are added during a render before returning a response.
|
||||
|
||||
This not only wastes CPU resources by repeatedly rendering components,
|
||||
but the data client also needs to provide a way to make the data fetched on the
|
||||
server side available during the hydration process to make sure that the
|
||||
virtual DOM returned by the initial render is consistent with the
|
||||
server-side rendered DOM tree which can be hard to implement.
|
||||
|
||||
Yew takes a different approach by trying to solve this issue with `<Suspense />`.
|
||||
|
||||
Suspense is a special component that when used on the client side, provides a
|
||||
way to show a fallback UI while the component is fetching
|
||||
data (suspended) and resumes to normal UI when the data fetching completes.
|
||||
|
||||
When the application is rendered on the server side, Yew waits until a
|
||||
component is no longer suspended before serializing it into the string
|
||||
buffer.
|
||||
|
||||
During the hydration process, elements within a `<Suspense />` component
|
||||
remains dehydrated until all of its child components are no longer
|
||||
suspended.
|
||||
|
||||
With this approach, developers can build a client-agnostic, SSR-ready
|
||||
application with data fetching with very little effort.
|
||||
|
||||
## SSR Hydration
|
||||
|
||||
Hydration is the process that connects a Yew application to the
|
||||
server-side generated HTML file. By default, `ServerRender` prints
|
||||
hydratable HTML string which includes additional information to facilitate hydration.
|
||||
When the `Renderer::hydrate` method is called, instead of starting rendering from
|
||||
scratch, Yew will reconcile the Virtual DOM generated by the application
|
||||
with the HTML string generated by the server renderer.
|
||||
|
||||
:::caution
|
||||
|
||||
To successfully hydrate an HTML representation created by the
|
||||
`ServerRenderer`, the client must produce a Virtual DOM layout that
|
||||
exactly matches the one used for SSR including components that do not
|
||||
contain any elements. If you have any component that is only useful in
|
||||
one implementation, you may want to use a `PhantomComponent` to fill the
|
||||
position of the extra component.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
|
||||
The hydration can only succeed if the real DOM matches the expected DOM
|
||||
after initial render of the SSR output (static HTML) by browser. If your HTML is
|
||||
not spec-compliant, the hydration _may_ fail. Browsers may change the DOM structure
|
||||
of the incorrect HTML, causing the actual DOM to be different from the expected DOM.
|
||||
For example, [if you have a `<table>` without a `<tbody>`, the browser may add a `<tbody>` to the DOM](https://github.com/yewstack/yew/issues/2684)
|
||||
:::
|
||||
|
||||
## Component Lifecycle during hydration
|
||||
|
||||
During Hydration, components schedule 2 consecutive renders after it is
|
||||
created. Any effects are called after the second render completes.
|
||||
It is important to make sure that the render function of your
|
||||
component is free of side effects. It should not mutate any states or trigger
|
||||
additional renders. If your component currently mutates states or triggers
|
||||
additional renders, move them into a `use_effect` hook.
|
||||
|
||||
It is possible to use Struct Components with server-side rendering in
|
||||
hydration, the view function will be called
|
||||
multiple times before the rendered function will be called.
|
||||
The DOM is considered as not connected until the rendered function is called,
|
||||
you should prevent any access to rendered nodes
|
||||
until `rendered()` method is called.
|
||||
|
||||
## Example
|
||||
|
||||
```rust ,ignore
|
||||
use yew::prelude::*;
|
||||
use yew::Renderer;
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! {<div>{"Hello, World!"}</div>}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let renderer = Renderer::<App>::new();
|
||||
|
||||
// hydrates everything under body element, removes trailing
|
||||
// elements (if any).
|
||||
renderer.hydrate();
|
||||
}
|
||||
```
|
||||
|
||||
Example: [simple_ssr](https://github.com/yewstack/yew/tree/master/examples/simple_ssr)
|
||||
Example: [ssr_router](https://github.com/yewstack/yew/tree/master/examples/ssr_router)
|
||||
|
||||
:::caution
|
||||
|
||||
Server-side rendering is currently experimental. If you find a bug, please file
|
||||
an issue on [GitHub](https://github.com/yewstack/yew/issues/new?assignees=&labels=bug&template=bug_report.md&title=).
|
||||
|
||||
:::
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
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 that 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,82 @@
|
|||
---
|
||||
title: 'Higher Order Components'
|
||||
---
|
||||
|
||||
There are several cases where Struct components do not directly support a feature (ex. Suspense) or require a lot of boilerplate code to use the features (ex. Context).
|
||||
|
||||
In those cases, it is recommended to create function components that are higher-order components.
|
||||
|
||||
## Higher Order Components Definition
|
||||
|
||||
Higher Order Components are components that do not add any new HTML and only wrap some other components to provide extra functionality.
|
||||
|
||||
### Example
|
||||
|
||||
Hook into Context and pass it down to a struct component
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn App() -> Html {
|
||||
let ctx = use_state(|| Theme {
|
||||
foreground: "#000000".to_owned(),
|
||||
background: "#eeeeee".to_owned(),
|
||||
});
|
||||
|
||||
html! {
|
||||
<ContextProvider<Theme> context={(*ctx).clone()}>
|
||||
<ThemedButtonHOC />
|
||||
</ContextProvider<Theme>>
|
||||
}
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
#[function_component]
|
||||
pub fn ThemedButtonHOC() -> Html {
|
||||
let theme = use_context::<Theme>().expect("no ctx found");
|
||||
|
||||
html! {<ThemedButtonStructComponent {theme} />}
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
struct ThemedButtonStructComponent;
|
||||
|
||||
impl Component for ThemedButtonStructComponent {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let theme = &ctx.props().theme;
|
||||
html! {
|
||||
<button style={format!(
|
||||
"background: {}; color: {};",
|
||||
theme.background,
|
||||
theme.foreground
|
||||
)}
|
||||
>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: 'Introduction'
|
||||
description: 'Components in Yew'
|
||||
---
|
||||
|
||||
## What are Components?
|
||||
|
||||
Components are the building blocks of Yew. They manage an internal state and can render elements to the DOM.
|
||||
Components are created by implementing the `Component` trait for a type.
|
||||
|
||||
## Writing Component's markup
|
||||
|
||||
Yew uses Virtual DOM to render elements to the DOM. The Virtual DOM tree can be constructed by using the
|
||||
`html!` macro. `html!` uses a syntax which is similar to HTML but is not the same. The rules are also
|
||||
much stricter. It also provides superpowers like conditional rendering and rendering of lists using iterators.
|
||||
|
||||
:::info
|
||||
[Learn more about the `html!` macro, how it is used and its syntax](concepts/html/introduction.mdx)
|
||||
:::
|
||||
|
||||
## Passing data to a component
|
||||
|
||||
Yew components use _props_ to communicate between parents and children. A parent component may pass any data as props to
|
||||
its children. Props are similar to HTML attributes but any Rust type can be passed as props.
|
||||
|
||||
:::info
|
||||
[Learn more about the props](advanced-topics/struct-components/properties.mdx)
|
||||
:::
|
||||
|
||||
:::info
|
||||
For other than parent/child communication, use [contexts](../../concepts/contexts.mdx)
|
||||
:::
|
|
@ -0,0 +1,305 @@
|
|||
---
|
||||
title: 'Lifecycle'
|
||||
description: 'Components and their lifecycle hooks'
|
||||
---
|
||||
|
||||
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>` that is 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](concepts/html/introduction.mdx).
|
||||
|
||||
### 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 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 is 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 that 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 is 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.
|
||||
|
||||
:::info
|
||||
[Learn more about properties](./properties)
|
||||
:::
|
||||
|
||||
## Lifecycle Context
|
||||
|
||||
All component lifecycle methods take a context object. This object provides a reference to the component's scope, which
|
||||
allows sending messages to a component and the props passed to the component.
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
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 is 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 are set to another value.
|
||||
|
||||
:::tip
|
||||
Attributes aren't visible in Rustdoc generated documentation.
|
||||
The doc strings 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.
|
||||
|
||||
:::tip
|
||||
Make use of `AttrValue` which is our custom type for attribute values instead of defining them as String or another similar type.
|
||||
:::
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use yew::Properties;
|
||||
/// Importing the AttrValue from virtual_dom
|
||||
use yew::virtual_dom::AttrValue;
|
||||
|
||||
#[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: AttrValue,
|
||||
/// Also notice that we are using AttrValue instead of String
|
||||
text: AttrValue,
|
||||
/// 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 does not 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 cannot 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 yew::{props, Properties, virtual_dom::AttrValue};
|
||||
|
||||
#[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: AttrValue,
|
||||
/// Also notice that we're using AttrValue instead of String
|
||||
text: AttrValue,
|
||||
/// 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 {
|
||||
/// Notice that this function receives href and text as String
|
||||
/// We can use `AttrValue::from` to convert it to a `AttrValue`
|
||||
pub fn new_link_with_size(href: String, text: String, size: u32) -> Self {
|
||||
// highlight-start
|
||||
props! {LinkProps {
|
||||
href: AttrValue::from(href),
|
||||
text: AttrValue::from(text),
|
||||
size,
|
||||
}}
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
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,81 @@
|
|||
---
|
||||
title: 'Scope'
|
||||
description: "Component's Scope"
|
||||
---
|
||||
|
||||
## Component's `Scope<_>` API
|
||||
|
||||
The component "`Scope`" is the mechanism through which components can 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 does nothing.
|
||||
|
||||
### `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.
|
||||
|
||||
```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`.
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: 'Agents'
|
||||
description: "Yew's Actor System"
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl'
|
||||
import ThemedImage from '@theme/ThemedImage'
|
||||
|
||||
Agents are a way to offload tasks to web workers.
|
||||
|
||||
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
|
||||
|
||||
<!--
|
||||
The diagram is produced with nomnoml (nomnoml.com),
|
||||
The code can be found in the <desc> tag of the svgs.
|
||||
-->
|
||||
|
||||
<ThemedImage
|
||||
alt="agent lifecycle diagram"
|
||||
sources={{
|
||||
light: useBaseUrl('/img/agent-lifecycle-light.svg'),
|
||||
dark: useBaseUrl('/img/agent-lifecycle-dark.svg'),
|
||||
}}
|
||||
/>
|
||||
|
||||
## Types of Agents
|
||||
|
||||
### Reaches
|
||||
|
||||
- Public - There will exist at most one instance of a Public Agent at any given time. Bridges will
|
||||
spawn or connect to an already spawned agent in a web worker.
|
||||
When no bridges are connected to this agent, the agent will disappear.
|
||||
|
||||
- Private - Spawn a new agent in a web worker for every new bridge. This is good for moving shared but
|
||||
independent behavior that communicates with the browser out of components. When
|
||||
the connected bridge is dropped, the agent will disappear.
|
||||
|
||||
- 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 use web workers \(i.e. Private and Public\). They incur a serialization overhead on the
|
||||
messages they send and receive. Agents use [bincode](https://github.com/servo/bincode) to communicate
|
||||
with other threads, so the cost is substantially higher than just calling a function.
|
||||
|
||||
## Further reading
|
||||
|
||||
- The [web_worker_fib](https://github.com/yewstack/yew/tree/master/examples/web_worker_fib) example shows how
|
||||
components can send messages to and receive messages from agents.
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: 'CSS with classes!'
|
||||
description: 'A handy macro to handle classes'
|
||||
comment: 'Keep this file as short and simple as possible. Its purpose is to ease the reader into components in Yew instead of providing proper API docs'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
Yew does not natively provide a CSS-in-Rust solution but helps with styling by providing
|
||||
programmatic ways to interact with the HTML `class` attribute.
|
||||
|
||||
## Classes
|
||||
|
||||
The `classes!` macro and associated `Classes` struct simplify the use of HTML 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};
|
||||
|
||||
html! {
|
||||
<div class={classes!(String::from("class-1 class-2"))}></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="Slice" label="Slice">
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
html! {
|
||||
<div class={classes!(["class-1", "class-2"].as_ref())}></div>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
We will expand upon this concept in [more CSS](../../more/css).
|
||||
|
||||
## Inline Styles
|
||||
|
||||
Currently Yew does not provide any special help with inline styles specified via the `styles` attribute,
|
||||
but you can use it like any other HTML attribute:
|
||||
|
||||
```rust
|
||||
use yew::{classes, html};
|
||||
|
||||
html! {
|
||||
<div style="color: red;"></div>
|
||||
};
|
||||
```
|
||||
|
||||
We will expand upon this concept in [more CSS](../../more/css).
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: 'HTML with html!'
|
||||
description: 'Its HTML but not quite!'
|
||||
comment: 'Keep this file as short and simple as possible. Its purpose is to ease the reader into components in Yew instead of providing proper API docs'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
You can write expressions resembling HTML with the `html!` macro. Behind the scenes, Yew turns
|
||||
it into rust code representing the DOM to generate.
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
|
||||
let my_header: Html = html! {
|
||||
<img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600" />
|
||||
};
|
||||
```
|
||||
|
||||
Similar to format expressions, there is an easy way to embed values from the surrounding
|
||||
context into the HTML by applying curly brackets:
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
|
||||
let header_text = "Hello world".to_string();
|
||||
let header_html: Html = html! {
|
||||
<h1>{header_text}</h1>
|
||||
};
|
||||
|
||||
let count: usize = 5;
|
||||
let counter_html: Html = html! {
|
||||
<p>{"My age is: "}{count}</p>
|
||||
};
|
||||
|
||||
let combined_html: Html = html! {
|
||||
<div>{header_html}{counter_html}</div>
|
||||
};
|
||||
```
|
||||
|
||||
One major rule comes with the use of `html!` - you can only return 1 wrapping node.
|
||||
To render a list of multiple elements, `html!` allows fragments. Fragments are tags
|
||||
without a name, that produce no HTML element by themselves.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Invalid" label="Invalid">
|
||||
|
||||
```rust , compile_fail
|
||||
use yew::html;
|
||||
|
||||
// error: only one root HTML element allowed
|
||||
html! {
|
||||
|
||||
<div></div>
|
||||
<p></p>
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Valid" label="Valid">
|
||||
|
||||
```rust
|
||||
use yew::html;
|
||||
|
||||
// fixed: using HTML fragments
|
||||
html! {
|
||||
<>
|
||||
<div></div>
|
||||
<p></p>
|
||||
</>
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
We will introduce Yew and HTML further in depth in [more HTML](concepts/html/introduction.mdx).
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: 'JS with RS'
|
||||
description: 'JavaScript with Rust'
|
||||
comment: 'Keep this file as short and simple as possible. Its purpose is to ease the reader into components in Yew instead of providing proper API docs'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
> Yew centrally operates on the idea of keeping everything that a reusable piece of
|
||||
> UI may need in one place - rust files, while also keeping the underlying technology
|
||||
> accessible where necessary.
|
||||
|
||||
As of today, WebAssembly is not feature-complete for DOM interactions. This means even in Yew we
|
||||
sometimes rely on calling JavaScript. What follows is an overview of the involved libraries.
|
||||
|
||||
## wasm-bindgen
|
||||
|
||||
[`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) is a library and tool that bridges calls between JavaScript and Rust functions.
|
||||
|
||||
We highly recommend you take a look at their [documentation](https://rustwasm.github.io/docs/wasm-bindgen/) and our [quick guide](./wasm-bindgen.mdx).
|
||||
|
||||
## web-sys
|
||||
|
||||
The [`web-sys` crate](https://crates.io/crates/web-sys) provides bindings for Web APIs and allows us to write JavaScript code in a rustyfied and safe way.
|
||||
|
||||
Example:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="JS" label="JS">
|
||||
|
||||
```js
|
||||
let document = window.document
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="RS" label="RS">
|
||||
|
||||
```rust ,no_run
|
||||
use wasm_bindgen::UnwrapThrowExt;
|
||||
use web_sys::window;
|
||||
|
||||
let document = window()
|
||||
.expect_throw("window is undefined")
|
||||
.document()
|
||||
.expect_throw("document is undefined");
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Once again we highly recommend you take a look at their [documentation](https://rustwasm.github.io/docs/wasm-bindgen/) and our [quick guide](./web-sys.mdx).
|
|
@ -0,0 +1,244 @@
|
|||
---
|
||||
title: 'wasm-bindgen'
|
||||
sidebar_label: 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 uses `wasm-bindgen` to interact with the browser through a number of 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 at a high level, to make it easier to understand
|
||||
and use `wasm-bindgen` APIs with Yew. For a more in-depth guide to `wasm-bindgen` and its 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 provides an interface between Rust and JavaScript, providing a system
|
||||
for translating between the two. Using this macro is more advanced, and you should not need to reach
|
||||
for it unless you are trying to use an external JavaScript library. The `js-sys` and `web-sys`
|
||||
crates expose `wasm-bindgen` definitions for built-in JavaScript types and browser APIs.
|
||||
|
||||
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) function.
|
||||
|
||||
```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 core feature of the Javascript language and the DOM
|
||||
(Document Object Model) is designed around it. When types are imported using `wasm-bindgen` you can
|
||||
also add attributes that describe their inheritance.
|
||||
|
||||
In Rust, this inheritance is represented 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`.
|
||||
|
||||
It is 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 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) {#jsvalue}
|
||||
|
||||
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 does not have
|
||||
a strong type system so any function that accepts a variable `x` does not define its type so `x` can be
|
||||
a valid JavaScript value; hence `JsValue`. If 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 accept certain types and this
|
||||
can lead to panics - so when using raw `wasm-bindgen` APIs check the documentation of the JavaScript
|
||||
being imported as to whether an exception (panic) will be raised 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) {#JsCast}
|
||||
|
||||
Rust has a strong type system and JavaScript...doesn't 😞. For Rust to maintain these
|
||||
strong types but still be convenient, the WebAssembly 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 another, then you can use the functions of `JsCast`
|
||||
to jump from one type to the other. It is a nice trait to get to know when working with `web-sys`,
|
||||
`wasm_bindgen`, `js-sys` - you will 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.mdx) 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).
|
||||
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>`. If the casting fails, the original `Self` value is returned in `Err`. 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 passed 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.mdx#using-closure-verbose)
|
||||
on the [Events](../html/events.mdx) 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`](./web-sys.mdx) 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`](https://doc.rust-lang.org/stable/std/future/trait.Future.html), and 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 `Future` will resolve to `Ok` if
|
||||
the `Promise` is resolved and `Err` if the `Promise` is rejected, containing the resolved or rejected
|
||||
value from the `Promise` respectively.
|
||||
|
||||
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,245 @@
|
|||
---
|
||||
title: 'web-sys'
|
||||
description: 'The web-sys crate provides bindings for Web APIs.'
|
||||
---
|
||||
|
||||
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 names are so long and why some types are vague.
|
||||
|
||||
## Features in `web-sys`
|
||||
|
||||
The `web-sys` crate with all of its features enabled can add lots of bloat to a Wasm application.
|
||||
To get around this issue most types are feature gated so that you only include the types
|
||||
you require for your application. Yew enables several features from `web-sys` and
|
||||
exposes some types in its 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.mdx#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 its inheritance.
|
||||
|
||||
This section is going to look at a specific element and list out its 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.mdx#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 have 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 have 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`](concepts/function-components/node-refs.mdx) to provide a way for keeping a reference to
|
||||
a `Node` made by the [`html!`](concepts/html/introduction.mdx) 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 its specific methods. This casting
|
||||
can be done using [`JsCast`](./wasm-bindgen.mdx#JsCast) on the `Node` value, if present, but Yew
|
||||
provides the `NodeRef::cast` method to perform this casting for convenience and so that you do not
|
||||
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 demonstrates examples of how JavaScript code which interact with the
|
||||
Web APIs can be rewritten with `web-sys` in Rust.
|
||||
|
||||
### 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, or otherwise will
|
||||
cause a panic in Rust. Another part of the verbosity is the calls to `JsCast` to cast into
|
||||
different types so that you can call its specific methods.
|
||||
|
||||
### Yew example
|
||||
|
||||
In Yew you will mostly be creating [`Callback`](concepts/function-components/callbacks.mdx)s to use in the
|
||||
[`html!`](concepts/html/introduction.mdx) 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 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
|
||||
provide abstractions over `web-sys` to provide more idiomatic Rust APIs.
|
||||
|
||||
_[External libraries page](/community/external-libs)_
|
|
@ -0,0 +1,196 @@
|
|||
---
|
||||
title: 'Contexts'
|
||||
sidebar_label: Contexts
|
||||
description: 'Using contexts to pass deeply nested data'
|
||||
---
|
||||
|
||||
Usually, data is passed from a parent component to a child component via props.
|
||||
But passing props can become verbose and annoying if you have to pass them through many components in the middle,
|
||||
or if many components in your app need the same information. Context solves this problem by allowing a
|
||||
parent component to make data available to _any_ component in the tree below it, no matter how deep,
|
||||
without having to pass it down with props.
|
||||
|
||||
## The problem with props: "Prop Drilling"
|
||||
|
||||
Passing [props](./function-components/properties.mdx) is a great way to pass data directly from a parent to a child.
|
||||
They become cumbersome to pass down through deeply nested component trees or when multiple components share the same data.
|
||||
A common solution to data sharing is lifting the data to a common ancestor and making the children take it as props.
|
||||
However, this can lead to cases where the prop has to go through multiple components to reach the component that needs it.
|
||||
This situation is called "Prop Drilling".
|
||||
|
||||
Consider the following example which passes down the theme using props:
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html, Properties, function_component};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct NavbarProps {
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn Navbar(props: &NavbarProps) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<Title theme={props.theme.clone()}>
|
||||
{ "App title" }
|
||||
</Title>
|
||||
<NavButton theme={props.theme.clone()}>
|
||||
{ "Somewhere" }
|
||||
</NavButton>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct ThemeProps {
|
||||
theme: Theme,
|
||||
children: Html,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn Title(_props: &ThemeProps) -> Html {
|
||||
html! {
|
||||
// impl
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn NavButton(_props: &ThemeProps) -> Html {
|
||||
html! {
|
||||
// impl
|
||||
}
|
||||
}
|
||||
|
||||
/// App root
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
let theme = Theme {
|
||||
foreground: "yellow".to_owned(),
|
||||
background: "pink".to_owned(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<Navbar {theme} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We "drill" the theme prop through `Navbar` so that it can reach `Title` and `NavButton`.
|
||||
It would be nice if `Title` and `NavButton`, the components that need access to the theme, can just access the theme
|
||||
without having to pass it to them as a prop. Contexts solve this problem by allowing a parent to pass data, theme in this case,
|
||||
to its children.
|
||||
|
||||
## Using Contexts
|
||||
|
||||
### Step 1: Providing the context
|
||||
|
||||
A context provider is required to consume the context. `ContextProvider<T>`, where `T` is the context struct 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. A struct is used to define what data is to be passed. The `ContextProvider` can be used as:
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
|
||||
|
||||
/// App theme
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Theme {
|
||||
foreground: String,
|
||||
background: String,
|
||||
}
|
||||
|
||||
/// Main component
|
||||
#[function_component]
|
||||
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]
|
||||
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]
|
||||
pub fn ThemedButton() -> Html {
|
||||
let theme = use_context::<Theme>().expect("no ctx found");
|
||||
|
||||
html! {
|
||||
<button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
|
||||
{ "Click me!" }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Consuming context
|
||||
|
||||
#### Function components
|
||||
|
||||
`use_context` hook is used to consume contexts in function components.
|
||||
See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use_context.html) to learn more.
|
||||
|
||||
#### Struct components
|
||||
|
||||
We have 2 options to consume contexts in struct components:
|
||||
|
||||
- [Higher Order Components](../advanced-topics/struct-components/hoc.mdx): A higher-order function component will consume the context and pass the data to the struct component which requires it.
|
||||
- Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs)
|
||||
|
||||
## Use cases
|
||||
|
||||
Generally, if some data is needed by distant components in different parts of the tree, context will likely help you.
|
||||
Here are some examples of such cases:
|
||||
|
||||
- **Theming**: You can put a context at the top of the app that holds your app theme and use it to adjust the visual appearance, as shown in the above example.
|
||||
- **Current user account**: In many cases, components need to know the currently logged-in user. You can use a context to provide the current user object to the components.
|
||||
|
||||
### Considerations to make before using contexts
|
||||
|
||||
Contexts are very easy to use. That makes them very easy to misuse/overuse.
|
||||
Just because you can use a context to share props to components multiple levels deep, does not mean that you should.
|
||||
|
||||
For example, you may be able to extract a component and pass that component as a child to another component. For example,
|
||||
you may have a `Layout` component that takes `articles` as a prop and passes it down to `ArticleList` component.
|
||||
You should refactor the `Layout` component to take children as props and display `<Layout> <ArticleList {articles} /> </Layout>`.
|
||||
|
||||
## Mutating the context value of a child
|
||||
|
||||
Because of Rust's ownership rules, a context cannot have a method that takes `&mut self` that can be called by children.
|
||||
To mutate a context's value, we must combine it with a reducer. This is done by using the
|
||||
[`use_reducer`](https://yew-rs-api.web.app/next/yew/functional/fn.use_reducer.html) hook.
|
||||
|
||||
The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) demonstrates mutable contexts
|
||||
with the help of contexts
|
||||
|
||||
## Further reading
|
||||
|
||||
- The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts)
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
title: 'Callbacks'
|
||||
---
|
||||
|
||||
Callbacks are used to asynchronously communicate upwards the components tree and with other things like agents or the DOM during event handling.
|
||||
Internally their type is just an `Fn` wrapped in `Rc` to allow them to be cheaply cloned.
|
||||
|
||||
They have an `emit` function if you want to call them manually.
|
||||
|
||||
```rust
|
||||
use yew::{html, Component, Context, Html, Callback};
|
||||
|
||||
let cb: Callback<String, String> = Callback::from(move |name: String| {
|
||||
format!("Bye {}", name)
|
||||
});
|
||||
|
||||
let result = cb.emit(String::from("Bob")); // call the callback
|
||||
// web_sys::console::log_1(&result.into()); // if uncommented will print "Bye Bob"
|
||||
```
|
||||
|
||||
## Passing callbacks as props
|
||||
|
||||
A common pattern in yew is to create a callback and pass it down as a prop.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties, Callback};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub on_name_entry: Callback<String>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
|
||||
props.on_name_entry.emit(String::from("Bob"));
|
||||
|
||||
html! { "Hello" }
|
||||
}
|
||||
|
||||
// Then supply the prop
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
let on_name_entry: Callback<String> = Callback::from(move |name: String| {
|
||||
let greeting = format!("Hey, {}!", name);
|
||||
// web_sys::console::log_1(&greeting.into()); // if uncommented will print
|
||||
});
|
||||
|
||||
html! { <HelloWorld {on_name_entry} /> }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## DOM Events and Callbacks
|
||||
|
||||
Callbacks are also used to hook into DOM events.
|
||||
|
||||
For example, here we define a callback that will be called when the user clicks the button:
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties, Callback};
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
let onclick = Callback::from(move |_| {
|
||||
let greeting = String::from("Hi there");
|
||||
// web_sys::console::log_1(&greeting.into()); // if uncommented will print
|
||||
});
|
||||
|
||||
html! {
|
||||
<button {onclick}>{ "Click" }</button>
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: 'Children'
|
||||
---
|
||||
|
||||
`Children` is a special prop type that allows you to receive nested `Html` that is provided like html child elements.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties};
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! {
|
||||
// highlight-start
|
||||
<HelloWorld>
|
||||
<span>{"Hey what is up ;)"}</span>
|
||||
<h1>{"THE SKY"}</h1>
|
||||
</HelloWorld>
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
// highlight-next-line
|
||||
pub children: Html, // the field name `children` is important!
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
html! {
|
||||
<div class="very-stylized-container">
|
||||
// highlight-next-line
|
||||
{ props.children.clone() } // you can forward children like this
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: 'Communication between components'
|
||||
---
|
||||
|
||||
## Parent to child messaging
|
||||
|
||||
Pass data as [props](./properties) that cause a re-render, this is the way to pass messages to children.
|
||||
|
||||
## Child to parent messaging
|
||||
|
||||
Pass down a callback via props, that the child on an event can call. [Example](callbacks#passing-callbacks-as-props)
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
title: 'Generic Components'
|
||||
description: 'The #[function_component] attribute'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
The `#[function_component]` attribute also works with generic functions for creating generic components.
|
||||
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
use yew::{function_component, html, Properties, Html, ToHtml};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props<T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
data: T,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn MyGenericComponent<T>(props: &Props<T>) -> Html
|
||||
where
|
||||
T: PartialEq + ToHtml,
|
||||
{
|
||||
html! {
|
||||
<p>
|
||||
{ &props.data }
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
// then can be used like this
|
||||
html! {
|
||||
<MyGenericComponent<i32> data=123 />
|
||||
};
|
||||
|
||||
// or
|
||||
html! {
|
||||
<MyGenericComponent<String> data={"foo".to_string()} />
|
||||
};
|
||||
```
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
title: 'Custom Hooks'
|
||||
---
|
||||
|
||||
## Defining custom Hooks
|
||||
|
||||
The stateful logic of a component can be extracted into reusable functions by creating custom Hooks.
|
||||
|
||||
Consider that we wish to create an event listener that listens to an event on the `window`
|
||||
object.
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
use gloo::events::EventListener;
|
||||
use gloo::utils::window;
|
||||
use std::mem::drop;
|
||||
|
||||
|
||||
#[function_component(ShowStorageChanged)]
|
||||
pub fn show_storage_changed() -> Html {
|
||||
let state_storage_changed = use_state(|| false);
|
||||
|
||||
{
|
||||
let state_storage_changed = state_storage_changed.clone();
|
||||
use_effect(|| {
|
||||
let listener = EventListener::new(&window(), "storage", move |_| state_storage_changed.set(true));
|
||||
|
||||
move || { drop(listener); }
|
||||
});
|
||||
}
|
||||
|
||||
html! { <div>{"Storage Event Fired: "}{*state_storage_changed}</div> }
|
||||
}
|
||||
```
|
||||
|
||||
There's one problem with this code: the logic can't be reused by another component.
|
||||
If we build another component that listens to a different event,
|
||||
instead of copying the code, we can move the logic into a custom hook.
|
||||
|
||||
We'll start by creating a new function called `use_event`.
|
||||
The `use_` prefix denotes that a function is a hook.
|
||||
This function will take an event target, an event type, and a callback.
|
||||
All hooks must be marked by `#[hook]` on their function definition.
|
||||
|
||||
```rust
|
||||
use web_sys::{Event, EventTarget};
|
||||
use std::borrow::Cow;
|
||||
use gloo::events::EventListener;
|
||||
use yew::prelude::*;
|
||||
|
||||
#[hook]
|
||||
pub fn use_event<E, F>(target: &EventTarget, event_type: E, callback: F)
|
||||
where
|
||||
E: Into<Cow<'static, str>>,
|
||||
F: Fn(&Event) + 'static,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
This simple hook can be created by composing built-in hooks. For this example, we'll use the
|
||||
`use_effect_with` hook, so an event listener can be recreated when the hook arguments change.
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
use web_sys::{Event, EventTarget};
|
||||
use std::borrow::Cow;
|
||||
use std::rc::Rc;
|
||||
use gloo::events::EventListener;
|
||||
|
||||
#[hook]
|
||||
pub fn use_event<E, F>(target: &EventTarget, event_type: E, callback: F)
|
||||
where
|
||||
E: Into<Cow<'static, str>>,
|
||||
F: Fn(Event) + 'static,
|
||||
{
|
||||
#[derive(PartialEq, Clone)]
|
||||
struct EventDependents {
|
||||
target: EventTarget,
|
||||
event_type: Cow<'static, str>,
|
||||
callback: Callback<Event>,
|
||||
}
|
||||
|
||||
let deps = EventDependents {
|
||||
target: target.clone(),
|
||||
event_type: event_type.into(),
|
||||
callback: Callback::from(callback),
|
||||
};
|
||||
|
||||
use_effect_with(
|
||||
deps,
|
||||
|deps| {
|
||||
let EventDependents {
|
||||
target,
|
||||
event_type,
|
||||
callback,
|
||||
} = deps.clone();
|
||||
|
||||
let listener = EventListener::new(&target, event_type, move |e| {
|
||||
callback.emit(e.clone());
|
||||
});
|
||||
|
||||
move || {
|
||||
drop(listener);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
View the docs on [docs.rs](https://docs.rs/yew) for documentation and `hooks` directory to see implementations of pre-defined hooks.
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
title: 'Hooks'
|
||||
slug: /concepts/function-components/hooks
|
||||
---
|
||||
|
||||
## Hooks
|
||||
|
||||
Hooks are functions that let you store state and perform side effects.
|
||||
|
||||
Yew comes with a few pre-defined hooks. You can also create your own or discover many [community-made hooks](/community/awesome#hooks).
|
||||
|
||||
## Rules of hooks
|
||||
|
||||
1. A hook function name always has to start with `use_`
|
||||
2. Hooks can only be used in the following locations:
|
||||
- Top-level of a function/hook.
|
||||
- Blocks inside a function/hook, given it is not already branched.
|
||||
- In the condition of a top-level `if` expression inside a function/hook.
|
||||
- In the scrutinee of a top-level `match` expression inside a function/hook.
|
||||
3. Hooks must be called in the same order for every render. Returning early is only allowed when using [Suspense](../../suspense.mdx)
|
||||
|
||||
These rules are enforced by either compile-time or run-time errors.
|
||||
|
||||
### Pre-defined Hooks
|
||||
|
||||
Yew comes with the following predefined Hooks:
|
||||
|
||||
- `use_state`
|
||||
- `use_state_eq`
|
||||
- `use_memo`
|
||||
- `use_callback`
|
||||
- `use_mut_ref`
|
||||
- `use_node_ref`
|
||||
- `use_reducer`
|
||||
- `use_reducer_eq`
|
||||
- `use_effect`
|
||||
- `use_effect_with`
|
||||
- `use_context`
|
||||
- `use_force_update`
|
||||
|
||||
The documentation for these hooks can be found in the [Yew API docs](https://yew-rs-api.web.app/next/yew/functional/)
|
||||
|
||||
### Custom Hooks
|
||||
|
||||
There are cases where you want to define your own Hooks to encapsulate potentially stateful logic from a component into reusable functions.
|
||||
See the [Defining custom hooks](concepts/function-components/hooks/custom-hooks.mdx#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 the same as Yew's hooks, but the underlying concept is similar.
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
title: 'Function Components'
|
||||
slug: /concepts/function-components
|
||||
---
|
||||
|
||||
Let's revisit this previous statement:
|
||||
|
||||
> Yew centrally operates on the idea of keeping everything that a reusable piece of
|
||||
> UI may need in one place - rust files.
|
||||
|
||||
We will refine this statement, by introducing the concept that will define the logic and
|
||||
presentation behavior of an application: "components".
|
||||
|
||||
## What are Components?
|
||||
|
||||
Components are the building blocks of Yew.
|
||||
|
||||
They:
|
||||
|
||||
- Take arguments in form of [Props](./properties.mdx)
|
||||
- Can have their own state
|
||||
- Compute pieces of HTML visible to the user (DOM)
|
||||
|
||||
## Two flavors of Yew Components
|
||||
|
||||
You are currently reading about function components - the recommended way to write components
|
||||
when starting with Yew and when writing simple presentation logic.
|
||||
|
||||
There is a more advanced, but less accessible, way to write components - [Struct components](advanced-topics/struct-components/introduction.mdx).
|
||||
They allow very detailed control, though you will not need that level of detail most of the time.
|
||||
|
||||
## Creating function components
|
||||
|
||||
To create a function component add the `#[function_component]` attribute to a function.
|
||||
By convention, the function is named in PascalCase, like all components, to contrast its
|
||||
use to normal html elements inside the `html!` macro.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html};
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld() -> Html {
|
||||
html! { "Hello world" }
|
||||
}
|
||||
|
||||
// Then somewhere else you can use the component inside `html!`
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! { <HelloWorld /> }
|
||||
}
|
||||
```
|
||||
|
||||
## What happens to components
|
||||
|
||||
When rendering, Yew will build a virtual tree of these components.
|
||||
It will call the view function of each (function) component to compute a virtual version (VDOM) of the DOM
|
||||
that you as the library user see as the `Html` type.
|
||||
For the previous example, this would look like this:
|
||||
|
||||
```xhtml
|
||||
<App>
|
||||
<HelloWorld>
|
||||
<p>"Hello world"</p>
|
||||
</HelloWord>
|
||||
</App>
|
||||
```
|
||||
|
||||
When an update is necessary, Yew will again call the view function and reconcile the new virtual DOM with its
|
||||
previous version and only propagate the new/changed/necessary parts to the actual DOM.
|
||||
This is what we call **rendering**.
|
||||
|
||||
:::note
|
||||
|
||||
Behind the scenes, `Html` is just an alias for `VNode` - a virtual node.
|
||||
|
||||
:::
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: 'Node Refs'
|
||||
description: 'Out-of-band DOM access'
|
||||
---
|
||||
|
||||
The `ref` attribute can be used to attach the `NodeRef` to an HTML element. In callbacks,
|
||||
you can then get the DOM `Element` that the ref is attached to. This can be used to make
|
||||
changes to the DOM outside of the `view` lifecycle method, retrieve the value of an `<input>`
|
||||
and other direct interactions with the DOM via the javascript API.
|
||||
|
||||
This is useful for getting ahold of canvas elements, or scrolling to different sections of a page.
|
||||
|
||||
:::caution
|
||||
Do not manually modify the DOM tree that is rendered by Yew. Treat the `NodeRef` as a read-only
|
||||
access, if you are unsure.
|
||||
:::
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [use_node_ref hook](https://yew-rs-api.web.app/next/yew/functional/fn.use_node_ref.html)
|
||||
- [`node_refs` example](https://github.com/yewstack/yew/tree/master/examples/node_refs)
|
|
@ -0,0 +1,304 @@
|
|||
---
|
||||
title: 'Properties'
|
||||
description: 'Parent to child communication'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
Properties are often shortened as "Props".
|
||||
|
||||
:::
|
||||
|
||||
Properties are essentially component arguments that Yew can keep watch on.
|
||||
|
||||
A type has to implement the `Properties` trait before it can be used as the properties of a component.
|
||||
|
||||
## Reactivity
|
||||
|
||||
Yew checks if props have changed when reconciling the Virtual DOM during re-rendering, to know if nested components need to be re-rendered.
|
||||
This way Yew can be considered a very reactive framework, as changes from the parent will always be propagated downward,
|
||||
and the view will never be out of sync with the data coming from props/state.
|
||||
|
||||
:::tip
|
||||
|
||||
If you have not yet completed the [tutorial](../../tutorial), try it out and test this reactivity yourself!
|
||||
|
||||
:::
|
||||
|
||||
## Derive macro
|
||||
|
||||
Yew provides a derive macro to easily implement the `Properties` trait on structs.
|
||||
|
||||
Types for which you derive `Properties` must also implement `PartialEq` so Yew can do data comparison.
|
||||
|
||||
```rust
|
||||
use yew::Properties;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub is_loading: bool,
|
||||
}
|
||||
```
|
||||
|
||||
## Use in function components
|
||||
|
||||
The attribute `#[function_component]` allows to optionally receive Props in the function arguments. To supply them,
|
||||
they are assigned via attributes in the `html!` macro.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="with-props" label="With Props">
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub is_loading: bool,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
html! { <>{"Am I loading? - "}{props.is_loading.clone()}</> }
|
||||
}
|
||||
|
||||
// Then supply the prop
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! {<HelloWorld is_loading={true} />}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="no-props" label="No Props">
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld() -> Html {
|
||||
html! { "Hello world" }
|
||||
}
|
||||
|
||||
// No props to supply
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! {<HelloWorld />}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Derive macro field attributes
|
||||
|
||||
When deriving `Properties` all fields are required by default.
|
||||
The following attributes allow you to give your props default values which will be used when the parent has not set them.
|
||||
|
||||
:::tip
|
||||
Attributes aren't visible in Rustdoc generated documentation.
|
||||
The doc strings of your properties should mention whether a prop is optional and if it has a special default value.
|
||||
:::
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="prop_or_default" label="#[prop_or_default]">
|
||||
|
||||
Initialize the prop value with the default value of the field's type using the `Default` trait.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
// highlight-start
|
||||
#[prop_or_default]
|
||||
// highlight-end
|
||||
pub is_loading: bool,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
if props.is_loading.clone() {
|
||||
html! { "Loading" }
|
||||
} else {
|
||||
html! { "Hello world" }
|
||||
}
|
||||
}
|
||||
|
||||
// Then use like this with default
|
||||
#[function_component]
|
||||
fn Case1() -> Html {
|
||||
html! {<HelloWorld />}
|
||||
}
|
||||
// Or no override the default
|
||||
#[function_component]
|
||||
fn Case2() -> Html {
|
||||
html! {<HelloWorld is_loading={true} />}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="prop_or_value" label="#[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)]`. The expression
|
||||
is evaluated when the properties are constructed and no explicit value has been given.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
// highlight-start
|
||||
#[prop_or("Bob".to_string())]
|
||||
// highlight-end
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
html! {<>{"Hello world"}{props.name.clone()}</>}
|
||||
}
|
||||
|
||||
// Then use like this with default
|
||||
#[function_component]
|
||||
fn Case1() -> Html {
|
||||
html! {<HelloWorld />}
|
||||
}
|
||||
// Or no override the default
|
||||
#[function_component]
|
||||
fn Case2() -> Html {
|
||||
html! {<HelloWorld name={"Sam".to_string()} />}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="prop_or_else_function" label="#[prop_or_else(function)]">
|
||||
|
||||
Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type.
|
||||
The function is called when no explicit value has been given for that attribute.
|
||||
|
||||
```rust
|
||||
use yew::{function_component, html, Html, Properties};
|
||||
|
||||
fn create_default_name() -> String {
|
||||
"Bob".to_string()
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
// highlight-start
|
||||
#[prop_or_else(create_default_name)]
|
||||
// highlight-end
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
html! {<>{"Hello world"}{props.name.clone()}</>}
|
||||
}
|
||||
|
||||
// Then use like this with default
|
||||
#[function_component]
|
||||
fn Case1() -> Html {
|
||||
html! {<HelloWorld />}
|
||||
}
|
||||
// Or no override the default
|
||||
#[function_component]
|
||||
fn Case2() -> Html {
|
||||
html! {<HelloWorld name={"Sam".to_string()} />}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Memory/speed overhead of using Properties
|
||||
|
||||
Internally properties are reference counted. This means that only a shared 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.
|
||||
|
||||
:::tip
|
||||
Make use of `AttrValue` which is our custom type for attribute values instead of defining them as String or another similar type.
|
||||
:::
|
||||
|
||||
## 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 yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
#[prop_or(AttrValue::from("Bob"))]
|
||||
pub name: AttrValue,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
html! {<>{"Hello world"}{props.name.clone()}</>}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
// highlight-start
|
||||
let pre_made_props = props! {
|
||||
Props {} // Notice we did not need to specify name prop
|
||||
};
|
||||
// highlight-end
|
||||
html! {<HelloWorld ..pre_made_props />}
|
||||
}
|
||||
```
|
||||
|
||||
## Evaluation Order
|
||||
|
||||
Props are evaluated in the order they're specified, as shown by the following example:
|
||||
|
||||
```rust
|
||||
#[derive(yew::Properties, PartialEq)]
|
||||
struct Props { first: usize, second: usize, last: usize }
|
||||
|
||||
fn main() {
|
||||
let mut g = 1..=3;
|
||||
let props = yew::props!(Props { first: g.next().unwrap(), second: g.next().unwrap(), last: g.next().unwrap() });
|
||||
|
||||
assert_eq!(props.first, 1);
|
||||
assert_eq!(props.second, 2);
|
||||
assert_eq!(props.last, 3);
|
||||
}
|
||||
```
|
||||
|
||||
## Anti Patterns
|
||||
|
||||
While almost any Rust type can be passed as properties, there are some anti-patterns that should be avoided.
|
||||
These include, but are not limited to:
|
||||
|
||||
1. Using `String` type instead of `AttrValue`. <br />
|
||||
**Why is this bad?** `String` can be expensive to clone.
|
||||
Cloning is often needed when the prop value is used with hooks and callbacks. `AttrValue` is either
|
||||
a reference-counted string (`Rc<str>`) or a `&'static str`, thus very cheap to clone.<br />
|
||||
**Note**: `AttrValue` internally is `IString` from [implicit-clone](https://crates.io/crates/implicit-clone)
|
||||
See that crate to learn more.
|
||||
2. Using interior mutability. <br />
|
||||
**Why is this bad?** Interior mutability (such as with `RefCell`, `Mutex`, etc.) should
|
||||
_generally_ be avoided. It can cause problems with re-renders (Yew doesn't know when the state has changed)
|
||||
so you may have to manually force a render. Like all things, it has its place. Use it with caution.
|
||||
3. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue
|
||||
or PR a fix to this documentation.
|
||||
|
||||
## yew-autoprops
|
||||
|
||||
[yew-autoprops](https://crates.io/crates/yew-autoprops) is an experimental package that allows one to create the Props struct on the fly out of the arguments of your function. Might be useful, if the properties struct is never reused.
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: 'Pure Components'
|
||||
---
|
||||
|
||||
A function component is considered [pure] when the returned `Html` is deterministically derived
|
||||
from its props when its view function does not mutate its state or has other side effects.
|
||||
|
||||
[pure]: https://en.wikipedia.org/wiki/Pure_function
|
||||
|
||||
The example below is a pure component. For a given prop `is_loading` it will always result in the same `Html` without any side effects.
|
||||
|
||||
```rust
|
||||
use yew::{Properties, function_component, Html, html};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub is_loading: bool,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn HelloWorld(props: &Props) -> Html {
|
||||
if props.is_loading {
|
||||
html! { "Loading" }
|
||||
} else {
|
||||
html! { "Hello world" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
If you have an internal pure component that makes no use of hooks and other component machinery, you can often write it instead
|
||||
as a normal function returning `Html` and avoid a bit of overhead for Yew, related to running the component lifecycle. Use
|
||||
[expression syntax](concepts/html/literals-and-expressions.mdx#expressions) to render them in `html!`.
|
||||
:::
|
||||
|
||||
## Impure components
|
||||
|
||||
You might wonder if a component can be impure if it does not use any globals, since it is just a function that is called every render.
|
||||
This is where the next topic comes in - [hooks](./hooks)
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: 'State'
|
||||
---
|
||||
|
||||
## General view of how to store state
|
||||
|
||||
This table can be used as a guide when deciding what state-storing type fits best for your use case:
|
||||
|
||||
| Hook | Type | Rerender when? | Scope |
|
||||
| ------------------------ | -------------------------- | ---------------------------- | ------------------- |
|
||||
| [use_state] | `T` | got set | component instance |
|
||||
| [use_state_eq] | `T: PartialEq` | got set with diff. value | component instance |
|
||||
| [use_reducer] | `T: Reducible` | got reduced | component instance |
|
||||
| [use_reducer_eq] | `T: Reducible + PartialEq` | got reduced with diff. value | component instance |
|
||||
| [use_memo] | `Deps -> T` | dependencies changed | component instance |
|
||||
| [use_callback] | `Deps -> Callback<E>` | dependencies changed | component instance |
|
||||
| [use_mut_ref] | `T` | - | component instance |
|
||||
| a static global variable | `T` | - | global, used by all |
|
||||
|
||||
[use_state]: https://yew-rs-api.web.app/next/yew/functional/fn.use_state.html
|
||||
[use_state_eq]: https://yew-rs-api.web.app/next/yew/functional/fn.use_state_eq.html
|
||||
[use_reducer]: https://yew-rs-api.web.app/next/yew/functional/fn.use_reducer.html
|
||||
[use_reducer_eq]: https://yew-rs-api.web.app/next/yew/functional/fn.use_reducer_eq.html
|
||||
[use_memo]: https://yew-rs-api.web.app/next/yew/functional/fn.use_memo.html
|
||||
[use_callback]: https://yew-rs-api.web.app/next/yew/functional/fn.use_callback.html
|
||||
[use_mut_ref]: https://yew-rs-api.web.app/next/yew/functional/fn.use_mut_ref.html
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue