How Frender works
Frender is based on react's functional components (React.FC
).
React.FC
are just js functions with special restrictions (
such as must return null | React.Element
,
must obey hooks rules).
What frender do is just to convert a rust function to a js function with wasm_bindgen::clousre::Closure
.
However, the problem is that js doesn't know about
the ownership and lifetimes of data in rust.
Luckily, React components have life cycles.
In functional components,
the best way to do tasks (side effects)
in life cycles is using
React.useEffect
like the following.
function MyComponent() {
React.useEffect(() => {
// do side effects on mounted
doSomething();
return () => {
// do side effects on unmounted
doSomeCleanup();
};
}, []);
}
Frender bridges rust ownership within the component life cycle, by persisting some data when like the following:
function MyComponent() {
const refInitialized = React.useRef(false);
if (!refInitialized.current) {
// persist rust data with
// std::mem::ManuallyDrop
persistRustData();
refInitialized.current = true;
}
React.useEffect(() => {
return () => {
// manually drop the persisted data
// on unmounted
dropPersistedRustData();
};
}, []);
}
With the above idea,
frender::react::use_ref
can be implemented by guarding React.useRef
with the following pseudo code.
fn use_ref<T>(initial_value: T) {
let js_ref_object = React.useRef();
if js_ref_object.is_undefined() {
// forget the initial value and use a number to represent it
let key: usize = forget_and_get_key(initial_value);
// numbers are safe and easy to be converted to a js number
js_ref_object.current = key;
}
React.useEffect(|| {
// return a cleanup function so that
// the forgotten data will be dropped after component unmounted
return || {
// manually drop the forgotten data
manually_drop_by_key(key);
}
}, []);
let rust_ref_object = FrenderUseRefObject {
// to get the current data,
// just get the current key,
// and then get the data
current: || get_by_key(js_ref_object.current),
set_current: |new_value| {
let old_key = js_ref_object.current;
manually_drop_by_key(old_key);
let new_key = forget_and_get_key(new_value);
js_ref_object.current = new_key;
},
};
rust_ref_object
}
Frender guards other hooks and the component function in the same way to keep the app memory safe.
Next, you can read about How rsx
macro works.