Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a macro for building properties outside of html! #1599

Merged
merged 46 commits into from
Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
43f0d57
remove renamed imports from yew-macro
siku2 Sep 24, 2020
273d7a0
just a prototype
siku2 Sep 24, 2020
9ce788f
cleanup
siku2 Oct 1, 2020
eefa6ee
add prop type resolver
siku2 Oct 2, 2020
7914e15
use new props for tags
siku2 Oct 3, 2020
f3b8717
silence clippy
siku2 Oct 6, 2020
a587c41
simplify tag parsing
siku2 Oct 6, 2020
61a4bf8
clean up
siku2 Oct 6, 2020
1487c36
improve names
siku2 Oct 6, 2020
16b60e7
fix list span
siku2 Oct 6, 2020
9401195
new component props parsing
siku2 Oct 6, 2020
b716fe3
fix rogue lint
siku2 Oct 7, 2020
86f2e87
update tag attribute parsing
siku2 Oct 8, 2020
1c4ebe5
unify prop handling
siku2 Oct 8, 2020
7b635a3
add new tests
siku2 Oct 8, 2020
1ffd629
integrate prop validation
siku2 Oct 8, 2020
128ea81
improve error span regression
siku2 Oct 8, 2020
0e7f25e
add docstring
siku2 Oct 8, 2020
d3c5993
update tests
siku2 Oct 8, 2020
f3a3763
add test for specifying `children` twice
siku2 Oct 8, 2020
abb702c
move properties derive macro
siku2 Oct 9, 2020
1915414
component transformer documentation
siku2 Oct 9, 2020
fc31295
update properties documentation
siku2 Oct 9, 2020
bf1d93a
document special properties
siku2 Oct 9, 2020
114cb28
let's try to fix the spellcheck
siku2 Oct 9, 2020
008681e
let's just use a newer image then
siku2 Oct 9, 2020
13c231e
document `with props` children
siku2 Oct 9, 2020
efb1799
clean up a tad
siku2 Oct 9, 2020
c603630
is boolean the missing word?
siku2 Oct 9, 2020
5113508
update to see if the preview deploys work
siku2 Oct 10, 2020
a7182ba
update again
siku2 Oct 10, 2020
3e9d1b2
update for the last time, plz :(
siku2 Oct 10, 2020
0f47c49
give me a deploy finally
siku2 Oct 10, 2020
d300ea7
you bet I'm testing the website changes here again
siku2 Oct 14, 2020
cf4a5ed
time for the obligatory update
siku2 Oct 15, 2020
c58fb1c
add the note for the recursion limit back in
siku2 Oct 15, 2020
cdcd552
code review
siku2 Oct 16, 2020
d122556
improve error for duplicate children
siku2 Oct 16, 2020
6b68c67
clippyfying
siku2 Oct 16, 2020
18053a9
revert Task: Drop
siku2 Oct 18, 2020
21d001b
HtmlTag -> HtmlElement
siku2 Oct 18, 2020
267f785
link the issue for prop_or_else
siku2 Oct 18, 2020
2372ebe
PropList -> SortedPropList
siku2 Oct 18, 2020
93c60f8
use struct syntax
siku2 Oct 19, 2020
effced3
update to latest commit
siku2 Oct 20, 2020
620e503
use html! in transformer demonstration
siku2 Oct 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ jobs:

doc_tests:
name: Documentation Tests
runs-on: ubuntu-latest
# Using 20.04 because 18.04 (latest) only has aspell 0.60.7 and we need 0.60.8
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
Expand All @@ -103,7 +104,9 @@ jobs:
cargo-${{ runner.os }}-

- name: Check spelling
run: bash ci/spellcheck.sh list
run: |
sudo apt-get install aspell
ci/spellcheck.sh list

- name: Run doctest
uses: actions-rs/cargo@v1
Expand Down
60 changes: 31 additions & 29 deletions ci/dictionary.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
personal_ws-1.1 en 88 utf-8
Angular's
Config
Deref
Github
Json
Lifecycle
React's
Todo
VecDeque
Webpack
personal_ws-1.1 en 0 utf-8
alloc
ALLOC
allocator
Angular's
asmjs
backends
barebones
binaryen
Binaryen
bindgen
bool
boolean
Expand All @@ -23,47 +14,58 @@ charset
codebase
codegen
composable
Config
declaratively
doctype
defs
DerefMut
DOCTYPE
DOCUSAURUS
emscripten
Emscripten
enum
enums
Github
href
html
Html
IHtmlElement
impl
init
INIT
interop
interoperability
interoperable
lang
libs
lifecycle
Lifecycle
linecap
linejoin
memoized
metaprogramming
minimize
miniserve
mkdir
natively
onclick
proc
React's
README
rlib
roadmap
Roadmap
rollup
rustc
rustfmt
rustwasm
stacktraces
rustup
stdweb
struct
structs
tbody
textarea
thead
TODO
toml
Unselected
usize
vdom
vtag
VecDeque
Vuetify
wasm
Wasm
webpack
Webpack
WeeAlloc
workspaces
vuetify
xmlns
yewtil
Yewtil
8 changes: 4 additions & 4 deletions ci/spellcheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ if [[ ! -f "$dict_filename" ]]; then
echo "Scanning files to generate dictionary file '$dict_filename'."
echo "Please check that it doesn't contain any misspellings."

echo "personal_ws-1.1 en 0 utf-8" > "$dict_filename"
cat "${markdown_sources[@]}" | aspell --ignore 3 list | sort -u >> "$dict_filename"
echo "personal_ws-1.1 en 0 utf-8" >"$dict_filename"
cat "${markdown_sources[@]}" | aspell --ignore 3 --camel-case list | sort -u >>"$dict_filename"
elif [[ "$mode" == "list" ]]; then
# List (default) mode: scan all files, report errors.
declare -i retval=0
Expand All @@ -74,7 +74,7 @@ elif [[ "$mode" == "list" ]]; then
fi

for fname in "${markdown_sources[@]}"; do
command=$(aspell --ignore 3 --camel-case --personal="$dict_path" "$mode" < "$fname")
command=$(aspell --ignore 3 --camel-case --personal="$dict_path" "$mode" <"$fname")
if [[ -n "$command" ]]; then
for error in $command; do
# FIXME: find more correct way to get line number
Expand All @@ -96,6 +96,6 @@ elif [[ "$mode" == "check" ]]; then
fi

for fname in "${markdown_sources[@]}"; do
aspell --ignore 3 --dont-backup --personal="$dict_path" "$mode" "$fname"
aspell --ignore 3 --camel-case --dont-backup --personal="$dict_path" "$mode" "$fname"
done
fi
37 changes: 19 additions & 18 deletions docs/concepts/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Introduction
description: Components and their lifecycle hooks
---

## What are Components?

Components are the building blocks of Yew. They manage their own state and can render themselves to the DOM. Components are created by implementing the `Component` trait for a type. The `Component`
Expand All @@ -20,7 +21,7 @@ in the lifecycle of a component.

When a component is created, it receives properties from its parent component as well as a `ComponentLink`. 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.

It is common to store the props (data which can be passed from parent to child components) and the
It is common to store the props (data which can be passed from parent to child components) and the
`ComponentLink` in your component struct, like so:

```rust
Expand All @@ -43,10 +44,10 @@ impl Component for MyComponent {

### 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
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).

```rust
Expand All @@ -66,9 +67,9 @@ For usage details, check out [the `html!` guide](html.md).

### Rendered

The `rendered` component lifecycle method is called once `view` has been called and Yew has 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
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.

Expand Down Expand Up @@ -107,8 +108,8 @@ Note that this lifecycle method does not require an implementation and will do n
### 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
`update` lifecycle method. This allows the component to update itself
based on what the message was, and determine if it needs to re-render itself. Messages can be sent
by event listeners, child components, Agents, Services, or Futures.

Here's an example of what an implementation of `update` could look like:
Expand Down Expand Up @@ -140,8 +141,8 @@ impl Component for MyComponent {

### Change

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
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.

A typical implementation would look something like:
Expand All @@ -163,7 +164,7 @@ impl Component for MyComponent {

### Destroy

After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is
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.

Expand All @@ -180,15 +181,15 @@ impl Component for MyComponent {
}
```

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
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 organising 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
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
Expand Down
74 changes: 59 additions & 15 deletions docs/concepts/components/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,50 @@
title: Properties
description: Parent to child communication
---

Properties enable child and parent components to communicate with each other.
Every component has an associated properties type which describes what is passed down from the parent.
In theory this can be any type that implements the `Properties` trait, but in practice there's no
reason for it to be anything but a struct where each field represents a property.

## Derive macro

Don't try to implement `Properties` yourself, derive it by using `#[derive(Properties)]` instead.
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 `Clone`.

### Field attributes

:::note
Types for which you derive `Properties` must also implement `Clone`. This can be done by either using `#[derive(Properties, Clone)` or manually implementing `Clone` for your type.
When deriving `Properties`, all fields are required by default.
The following attributes allow you to give your props initial values which will be used unless they're set to another value.

:::tip
Attributes aren't visible in Rustdoc generated documentation.
The docstrings of your properties should mention whether a prop is optional and if it has a special default value.
:::

### Required attributes
#### `#[prop_or_default]`

Initialize the prop value with the default value of the field's type using the `Default` trait.

The fields within a struct that derives `Properties` are required by default. When a field is missing and the component is created in the `html!` macro, a compiler error is returned. For fields with optional properties, use the `#[prop_or_default]` attribute to use the default value for that type when the prop is not specified. To specify a value, use the `#[prop_or(value)]` attribute where value is the default value for the property or alternatively use `#[prop_or_else(function)]` where `function` returns the default value. For example, to default a boolean value as `true`, use the attribute `#[prop_or(true)]`. It is common for optional properties to use the `Option` enum which has the default value `None`.
#### `#[prop_or(value)]`

### PartialEq
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.

:::warning
The function is currently called even if the prop is explicitly set. If your function is performance intensive, consider using `Option` where `None` values are initialized in the `create` method.
See [#1623](/~https://github.com/yewstack/yew/issues/1623)
:::

It is likely to make sense to derive `PartialEq` on your props if you can do this. Using `PartialEq` makes it much easier to avoid unnecessary rerendering \(this is explained in the **Optimizations & Best Practices** section\).
## PartialEq

It makes sense to derive `PartialEq` on your props if you can do so.
Using `PartialEq` makes it much easier to avoid unnecessary rendering \(this is explained in the **Optimizations & Best Practices** section\).

## Memory/speed overhead of using Properties

Expand All @@ -45,11 +72,8 @@ pub enum LinkColor {
Purple,
}

impl Default for LinkColor {
fn default() -> Self {
// The link color will be blue unless otherwise specified.
LinkColor::Blue
}
fn create_default_link_color() -> LinkColor {
LinkColor::Blue
}

#[derive(Properties, Clone, PartialEq)]
Expand All @@ -58,9 +82,9 @@ pub struct LinkProps {
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]
text: Rc<str>,
/// 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]
Expand All @@ -71,3 +95,23 @@ pub struct LinkProps {
}
```

## 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
let props = yew::props!(LinkProps {
href: "/",
text: Rc::from("imagine this text being really long"),
size: 64,
});

// build the associated properties of a component
let props = yew::props!(Model::Properties {
href: "/book",
text: Rc::from("my bestselling novel"),
});
```
Loading