5 releases
1.0.0-alpha.8 | Mar 2, 2022 |
---|---|
1.0.0-alpha.7 | Mar 1, 2022 |
1.0.0-alpha.6 | Feb 28, 2022 |
1.0.0-alpha.5 | Feb 27, 2022 |
1.0.0-alpha.2 | Jan 28, 2022 |
#1752 in Web programming
15KB
156 lines
frender
Functional Rendering: React
in Rust
frender is still in alpha and it's api might change.
For now it is recommended to specify the exact version in Cargo.toml
.
Before updating, please see the full changelog in case there are breaking changes.
There are some example apps in
examples
folder. You can preview them at this site.
Quick Start
-
Create a new cargo project
cargo new my-frender-app cd my-frender-app
-
Add
frender
to dependencies inCargo.toml
.[dependencies] frender = "= 1.0.0-alpha.8"
-
Create
index.html
in the project root directory.<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>My frender App</title> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <link data-trunk rel="rust" href="Cargo.toml" /> </head> <body> <div id="frender-root"></div> </body> </html>
-
Modify
src/main.rs
use frender::prelude::*; #[component(main(mount_element_id = "frender-root"))] fn Main() { rsx!( <div> "Hello, frender!" </div> ) }
-
Run with
trunk
Install trunk and then execute:
trunk serve
Then you can navigate to
http://localhost:8080
to see your frender app.
rsx
syntax
rsx
element
use frender::prelude::*;
rsx! (
<MyComp id="my-component">
// Child node can be any literal strings or numbers
"some string"
1
// Child node can be any rust expressions wrapped in braces
{ 1 + 6 }
{ value }
// Child node can be an element
<MyChild key="k" prop={any_expr} />
// Prop without value means `true`, just like React
<MyDialog show />
// Fragment
<>1 2 3</>
// Fragment with key
<# key="key">1 2 3</#>
// you can also use `</_>` to enclose any element
<path::to::Component></_>
// the above is equivalent to:
<path::to::Component></path::to::Component>
</MyComp>
)
Any component name starting with lower case letter [a-z]
will be interpreted as an intrinsic component.
For example, rsx!( <div id="my-div" /> )
will be resolved to:
use frender::prelude::*;
use self::intrinsic_components::div::prelude::*;
rsx! (
<self::intrinsic_components::div::prelude::Component id="my-div" />
)
rsx
prop
In order to make rsx less verbose, frender provides
IntoPropValue
trait. The value
in
<MyComponent prop={value} />
will be mapped to
IntoPropValue::into_prop_value(value)
.
With this, assuming the prop accepts Option<i32>
,
you can simplify prop={Some(1)}
to prop={1}
,
because T
implements IntoPropValue<Option<T>>
.
If you want to pass the value as is, you can
use :=
to set prop. prop:={value}
Write a component
Component with no props
use frender::prelude::*;
#[component]
fn MyComponent() {
// ^
// the return type defaults to react::Element
rsx!( <div /> )
}
// Or you can specify the return type explicitly
#[component]
fn MyAnotherComponent() -> Option<react::Element> {
if check_something() {
Some(rsx!( <MyComponent /> ))
} else {
None
}
}
Component with props
First, define MyProps
use frender::prelude::*;
def_props! {
pub struct MyProps {
// Required prop
name: String,
// Optional prop which defaults to `Default::default()`
// The following property `age` is optional, and defaults to `None`
age?: Option<u8>,
// The following property `tags` is optional, and defaults to `Vec::default()`
tags?: Vec<String>,
// If the prop type is not specified,
// then frender will infer the type by prop name.
// For example, `class_name` default has type `Option<String>`
// The following property `class_name` is optional, has type Option<String>
class_name?,
// The following property `id` is required, has type Option<String>
id,
// Prop can also have type generics.
// For example, the following is
// the default definition for prop `children`,
// which means it accepts any `Option<TNode>` where TNode implements react::Node,
// and then map the value into `Option<react::Children>` and store it into MyProps.
children<TNode: react::Node>(value: Option<TNode>) -> Option<react::Children> {
value.and_then(react::Node::into_children)
},
}
}
Then write the component with the above props:
use frender::prelude::*;
#[component]
pub fn MyComponent(props: &MyProps) {
rsx!(<div>{&props.children}</div>)
}
Due to the generics, in some very rare cases, you may meet errors like
type annotations needed
cannot infer type for type parameter
.
You can solve it by specifying the type
with the turbofish syntax ::<>
.
For example:
rsx! (
// ERROR: type annotations needed
<a children={None} />
)
rsx! (
// it works!
<a children={None::<()>} />
)
Hooks
React hooks are also available in frender
.
You checkout the examples for the usage.
Future Development Plans
- Documentation
- Intrinsic svg components
- Export
frender
components to js - Server Side Rendering
- Type checking for
CssProperties
- Css-in-rust (For example, integrate with
emotion/react
)
Contributing
frender
is open sourced at GitHub.
Pull requests and issues are welcomed.
You can also sponsor me and I would be very grateful ❤️
Dependencies
~14MB
~264K SLoC