#reactive #react #ui #cell #vue

reax

为 Rust 提供一个函数之间依赖推断的反应性系统

2 个不稳定版本

0.2.0 2020 年 7 月 5 日
0.1.0 2020 年 1 月 23 日

#7 in #vue

每月 46 次下载

MIT 许可证

82KB
1.5K SLoC

Reax

Reax 是一个为 Rust 提供的函数之间依赖推断的反应性系统。

Reax 管理的每个 Variable 都是一个依赖图中的一个节点。变量的更改和使用由全局的 ThreadRuntime 跟踪,该全局变量会更新图以反映实际的变量访问。存在两种内置变量

  • Var 可以显式地被修改。
  • ComputedVar 使用函数懒加载地计算其值。

用户可以使用 .watch 监听和响应变量的更改。

关键的是,一个 ComputedVar 只会在需要时重新计算。如果在计算其值时,一个 ComputedVar 在任何地方(直接或间接地)使用了任何其他变量,这些上游变量的任何更改都会自动将计算变量标记为脏,并在下次使用时重新计算变量的值。

示例

Reax 会构建一个模型来描述程序运行时变量之间的交互。

use reax::prelude::*;

// Create input variables.
let number = Var::new(1).with_label("number");
let name = Var::new("Sam").with_label("name");

// Create computed variables.
let formatted = (&number)
   .map(|x| format!("{}", x))
   .with_label("formatted");

let printout = computed! {
    output! text = String::new(); // Reuse a buffer

    text.clear();
    *text += *name.get();
    *text += " sees ";
    *text += formatted.get().as_str();
}.with_label("printout");

// The computed variables haven't been used yet. Nothing is hooked-up.
assert_eq!(printout.node().depends_on(formatted.node()), false);

// Use the variables!
assert_eq!(printout.get().as_str(), "Sam sees 1");
number.set(42);
name.set("Reax");
assert_eq!(printout.get().as_str(), "Reax sees 42");

// Reax now knows how data moves through the variables!
assert_eq!(printout.node().depends_on(formatted.node()), true);

// Print a .dot visualization.
reax::ThreadRuntime::write_graphviz(std::io::stdout().lock()).unwrap();

我们可以通过 Reax 的视角来查看这个示例

Dependency Graph


Reax 只会在需要时更新计算变量。

use reax::prelude::*;

let number = Var::new(0);
let bigger_number = (&number).map(|x| *x + 10);
let even_bigger_number = (&bigger_number).map(|x| *x + 100);
let times_called = Var::new(0);

// Set up a watcher to track how often bigger_number changes.
let mut eval = EagerCompute::new(());
eval.watch(&bigger_number, |_| {
   *times_called.mutate() += 1;
});

// The watcher is called once on creation.
assert_eq!(*times_called.get(), 1);

// Run the watcher. This is effectively a no-op since nothing has changed.
for _ in 0..100 { eval.tick(); }

// Update a variable.
number.set(1);

// Dependent variables are instantly dirty.
assert_eq!(bigger_number.node().is_dirty(), true);
assert_eq!(even_bigger_number.node().is_dirty(), true);

// Run the watcher again. This time it fires.
eval.tick();
assert_eq!(*times_called.get(), 2);

// even_bigger_number is still dirty since no one has used it yet.
assert_eq!(even_bigger_number.node().is_dirty(), true);

在这里,你可以看到当 number 改变时,它下游的所有变量都会立即被标记。但它们不会在未被使用之前重新计算。

Dependency Graph


Reax 没有内置对集合的理解,因此你可以使用嵌套的 Var 来更好地控制更改的“深度”。

use reax::prelude::*;

// Create a list of variables.
let list = Var::new(Vec::new());
for x in 1..=3 {
    list.mutate().push(Var::new(x));
}

// Some computed properties:
let length = computed! { list.get().len() };
let sum = computed! {
    list.get().iter().map(|elem| *elem.get()).sum::<i32>()
};

// Make length and sum outdated by pushing an extra element.
list.mutate().push(Var::new(4));

// Update the length.
length.check(&mut ());

// Now only make sum outdated, and leave it that way.
list.get()[0].set(100);

在示例的最后,可视化运行时,你可以看到只有总和是脏的。列表元素本身没有任何依赖,因此任何对它们的更改都不会影响从未读取过它们的变量。Reax 也没有看到额外元素将被用于总和。它将在下一次计算总和时发现这一点。

Dependency Graph

依赖关系