如何透明地将原生 JavaScript 变量转换为反应式 Meteor 变量?

Kyl*_*yll 4 javascript meteor

当本机变量更改时,不会重新运行跟踪器计算:

var foo = 'foo';
Tracker.autorun(function logFoo() { console.log('foo is:', foo); });
Run Code Online (Sandbox Code Playgroud)

这段代码只会执行一次:

foo 是:
'foo'

计算没有依赖性,没有_onInvalidateCallback。它几乎死了。

然而,在很多情况下,我确实需要一个原生 JavaScript 变量或一个对象字段以某种方式在 Tracker 计算中反应性地运行(原生 API 未完全移植到 Meteor,...)

当然我不能简单地写:

foo = new ReactiveVar(foo);
Run Code Online (Sandbox Code Playgroud)

由于我将打破当前帧的参考,其他函数可能会使用另一个参考 for foo,从而导致不同步、痛苦和头痛。
以类似的方式...

obj.foo = new ReactiveVar(obj.foo);
Run Code Online (Sandbox Code Playgroud)

这也会中断,因为obj.foo现在完全不同,依赖于obj.foo简单、非反应性值的代码将立即中断。
它也对模块模式(对 的孤立引用obj.foo)毫无用处,并且会导致更多的不同步和更多的痛苦,甚至更多的头痛。

如何在不破坏遗留代码的情况下将原生 Javascript 变量或对象字段正确更改为 Reactive-Var?

Kyl*_*yll 5

本地变量和对象字段这两种情况需要分开处理,它们将需要不同的方法。第一个将使用一个简单但肮脏的技巧,第二个将使用更高级的技巧。
让我们从原生变量案例开始。


对象字段

如果变量是一个可写的对象字段,那么我们可以更改引用,使自定义的 get/set 对链接到闭包中的反应变量。
就这么简单:

function reactivise(obj, field) {
  var rvar = new ReactiveVar(obj[field]);

  Object.defineProperty(obj, field, {
    get : function() {
      return rvar.get();
    },
    set : function(value) {
      rvar.set(value);
      return value;
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

它只是有效。使用本机代码obj.foo不会注意到更改(除非他们检查属性描述符,但这是一件很奇怪的事情)。然而,响应式计算将因更改此字段而无效。
但是,它对模块模式很弱(引用隔离以防止损坏)。这是此类模块的示例:

(function logFoo(foo) {
  console.log(foo);
}(obj.foo);
Run Code Online (Sandbox Code Playgroud)

这段代码不会在意您更改了 getter 或 setter,它已经拥有引用。

可能有办法解决这个问题......但在撰写本文时,它几乎是炼金术。一个可以帮助的 ES7 特性:Object.observe. 今天太年轻了,我不会从中举出一个例子。


变量

如果您要观察的不是非模块对象字段(上面的示例),那么我知道的唯一解决方案是轮询。
基本上,定期检查值是否改变,并为此设置一个新的反应变量(我们失去了透明度)。

此类轮询的示例:

function reactivePoll(getter) {
  var rPoll = new ReactiveVar(getter());

  Meteor.setInterval(function pollVariable() {
    var newValue = getter();
    if(!_.isEqual(rPoll.curValue, newValue)) {
      rPoll.set(newValue);
    }
  }, 100);

  return rPoll;
}
Run Code Online (Sandbox Code Playgroud)

我们需要让它工作的不是变量引用本身( foo),而是这个变量的getter。这是因为如果foo稍后在代码中更改了引用,我们的函数将不会意识到它(更加痛苦的不同步头痛)。
另外,我们每次都必须检查深度相等,以确保在我们导致无效之前该值确实发生了变化,因为如果 Tracker 看到一个非原始值,它会自动失效。

使用示例:

var reactiveFoo = reactivePoll(function getFoo() { return foo; });
Run Code Online (Sandbox Code Playgroud)

它当然也适用于对象字段。
请注意,示例代码没有任何类型的停止机制。它将永远运行,可能会引发内存泄漏、崩溃、减慢您的应用程序并导致剧烈的头痛。不要在生产应用程序中使用它,调整它以更好地控制间隔。


最安全的选择是基本的脏轮询,即使这意味着更多的负载和防止内存泄漏所需的完全控制。