use std::cell::RefCell;
use crate::functional::{hook, Effect, Hook, HookContext};
pub trait TearDown: Sized + 'static {
fn tear_down(self);
}
impl TearDown for () {
fn tear_down(self) {}
}
impl<F: FnOnce() + 'static> TearDown for F {
fn tear_down(self) {
self()
}
}
struct UseEffectBase<T, F, D>
where
F: FnOnce(&T) -> D + 'static,
T: 'static,
D: TearDown,
{
runner_with_deps: Option<(T, F)>,
destructor: Option<D>,
deps: Option<T>,
effect_changed_fn: fn(Option<&T>, Option<&T>) -> bool,
}
impl<T, F, D> Effect for RefCell<UseEffectBase<T, F, D>>
where
F: FnOnce(&T) -> D + 'static,
T: 'static,
D: TearDown,
{
fn rendered(&self) {
let mut this = self.borrow_mut();
if let Some((deps, runner)) = this.runner_with_deps.take() {
if !(this.effect_changed_fn)(Some(&deps), this.deps.as_ref()) {
return;
}
if let Some(de) = this.destructor.take() {
de.tear_down();
}
let new_destructor = runner(&deps);
this.deps = Some(deps);
this.destructor = Some(new_destructor);
}
}
}
impl<T, F, D> Drop for UseEffectBase<T, F, D>
where
F: FnOnce(&T) -> D + 'static,
T: 'static,
D: TearDown,
{
fn drop(&mut self) {
if let Some(destructor) = self.destructor.take() {
destructor.tear_down()
}
}
}
fn use_effect_base<T, D>(
runner: impl FnOnce(&T) -> D + 'static,
deps: T,
effect_changed_fn: fn(Option<&T>, Option<&T>) -> bool,
) -> impl Hook<Output = ()>
where
T: 'static,
D: TearDown,
{
struct HookProvider<T, F, D>
where
F: FnOnce(&T) -> D + 'static,
T: 'static,
D: TearDown,
{
runner: F,
deps: T,
effect_changed_fn: fn(Option<&T>, Option<&T>) -> bool,
}
impl<T, F, D> Hook for HookProvider<T, F, D>
where
F: FnOnce(&T) -> D + 'static,
T: 'static,
D: TearDown,
{
type Output = ();
fn run(self, ctx: &mut HookContext) -> Self::Output {
let Self {
runner,
deps,
effect_changed_fn,
} = self;
let state = ctx.next_effect(|_| -> RefCell<UseEffectBase<T, F, D>> {
RefCell::new(UseEffectBase {
runner_with_deps: None,
destructor: None,
deps: None,
effect_changed_fn,
})
});
state.borrow_mut().runner_with_deps = Some((deps, runner));
}
}
HookProvider {
runner,
deps,
effect_changed_fn,
}
}
#[hook]
pub fn use_effect<F, D>(f: F)
where
F: FnOnce() -> D + 'static,
D: TearDown,
{
use_effect_base(|_| f(), (), |_, _| true);
}
pub fn use_effect_with<T, F, D>(deps: T, f: F) -> impl Hook<Output = ()>
where
T: PartialEq + 'static,
F: FnOnce(&T) -> D + 'static,
D: TearDown,
{
use_effect_base(f, deps, |lhs, rhs| lhs != rhs)
}