2

请参阅此笔以获取问题的演示(基于教程中的幻灯片)。当单击“下一步”和“上一个”箭头时,您会注意到imgIndexmustache 正确更新,但表达式 mustache 例如<p>{{ curImageCaption() }}</p>不识别它们的值何时发生变化。

也就是说,对象发生了变异,因此如果重新评估表达式,胡子值会发生变化,但 ractive 似乎没有意识到这一点。有没有办法让它工作,除非写适配器?我是否误解了魔术模式的工作原理?有趣的是,即使我ractive.update()在事件处理程序内部显式调用,ractive 仍然没有响应。

更新新信息

经过更多的摆弄,我想出了这个让它工作的技巧。hack 是要改变,例如,<p>{{ curImageCaption() }}</p><p>{{ curImageCaption(imgIndex) }}</p>mustache 表达式添加一个简单的原语,以便 ractive 了解如何正确观看。

我想我知道现在发生了什么,但是必须显式地向包含更改原语的 mustache 表达式添加参数,这违背了拥有单独域对象的大部分目的——也就是说,现在你在编码你的域对象时考虑到了 ractive,使用更改原语是一种基本的发布/订阅机制,用于通知 ractive 更改。

必须在我的自定义对象上创建一个真正的 pub/sub 机制,然后 ractive 然后显式订阅,这很好。问题是,正如我在 OP 中指出的那样,即使通过 ractive 收到更改通知ractive.update(),它仍然不知道它应该重新计算胡须,除非我使用假参数 hack。因此,尚不清楚应该注册什么回调 ractive 以使一切正常。

我不太了解 ractive 的内部工作来做到这一点,但我怀疑需要的是直接使用这些_deps东西的能力,并手动触发表达式的重新计算。如果这听起来正确,将不胜感激提供如何实现它的示例。

更新 2——一个不错的解决方案

这是一个不太老套的解决方法的概念证明

这个想法是使用 ECMA5 属性来装饰您的自定义域对象,提供委托给您想要使用但在 ractive 模板中不起作用的现有方法的属性。otoh的属性工作得很好。

因此,我们不再<p>{{ curImageCaption() }}</p>简单地编写<p>{{ imageCaption }}</p>,然后像这样装饰我们的自定义域对象:

Object.defineProperty(mySlideshow, "imageCaption", {
  configurable: true,
  get: function() { return this.curImageCaption() },
  set: function() { }
});

这种装饰,在我的演示中有点冗长,可以通过创建一个辅助方法来轻松精简.

注意:此方法的一个缺点是您必须ractive.update()在事件处理程序中手动调用。我想知道是否有办法解决这个问题。如果没有,这会对性能造成多大的影响?它是否违背了 ractive 手术更新的全部目的?

更新 3——更好的解决方案?

这支笔采用了另一种方法,其中通过通用调度程序对象(实现的对象notify())将我们的自定义域模型与 ractive 链接起来。我认为这是迄今为止我最喜欢的方法....

它类似于官方的ractive 适配器,但我们使用 DI 将非官方的 ractive 适配器传递给我们的域对象,而不是包装我们的对象。乍一看,我们似乎在“为 ractive 编码”,但实际上这只是部分正确。即使我们使用另一个框架,我们也需要使用一些通知机制来广播对我们的视图模型的更改,以便视图可以对其做出反应。这种 DI 方法似乎比官方的 ractive 适配器需要更少的样板文件,尽管我对它们的理解不够好,无法确定这一点。它也不像官方适配器那样完全通用。

来自笔的代码为后代

HTML

<div id='output'></div>

<script id='template' type='text/ractive'>
  <div class='slideshow'>
    <div class='main'>
      <a class='prev' on-tap='prev'><span>&laquo;</span></a>
      <div class='main-image' style='background-image: url({{ curImageSrc() }});'></div>
      <a class='next' on-tap='next'><span>&raquo;</span></a>
    </div>

    <div class='caption'>
      <p>{{ curImageCaption() }}</p>
      <p>Image index: {{ imgIndex }} </p>
    </div>
  </div>
</script>

JS

// Fix JS modular arithmetic to always return positive numbers
function mod(m, n) { return ((m%n)+n)%n; }

function SlideshowViewModel(imageData) {
  var self = this;
  self.imgIndex = 0;
  self.next = function() { self.setLegalIndex(self.imgIndex+1); }
  self.prev = function() { self.setLegalIndex(self.imgIndex-1); }
  self.curImage = function() { return imageData[self.imgIndex]; }
  self.curImageSrc = function() { return self.curImage().src; }
  self.curImageCaption = function() { return self.curImage().caption; }
  self.setLegalIndex = function(newIndex) { self.imgIndex = mod(newIndex, imageData.length); } 
}

var mySlideshow = new SlideshowViewModel(
  [
    { src: imgPath('problem.gif'), caption: 'Trying to work out a problem after the 5th hour' },
    { src: imgPath('css.gif'), caption: 'Trying to fix someone else\'s CSS' },
    { src: imgPath('ie.gif'), caption: 'Testing interface on Internet Explorer' },
    { src: imgPath('w3c.gif'), caption: 'Trying to code to W3C standards' },
    { src: imgPath('build.gif'), caption: 'Visiting the guy that wrote the build scripts' },
    { src: imgPath('test.gif'), caption: 'I don\'t need to test that. What can possibly go wrong?' }
  ]
);

var ractive = new Ractive({
  el: '#output',
  template: '#template',
  data: mySlideshow,
  magic: true
});

ractive.on( 'next', function(event) {
  ractive.data.next(); 
});
ractive.on( 'prev', function(event) {
  ractive.data.prev(); 
});


function imgPath(name) { return 'http://learn.ractivejs.org/files/gifs/' + name; }
4

2 回答 2

3

在提出可能的解决方案之前,我将尝试解释幕后发生的事情:

以魔法模式包裹物体

在魔术模式中,当 Ractive 遇到对象的未包装数据描述符时,它会通过将其替换为访问器描述符-函数来包装它。有关 MDN 的更多信息,供感兴趣的人参考。)因此,当您这样做时,您实际上是在触发该函数,该函数知道如何通知该属性的所有依赖项。get()/set()self.imgIndex = 1set()imgIndex

这里的关键词是“相遇”。Ractive 知道它需要包装的唯一方法imgIndex是我们是否这样做ractive.get('imgIndex')。这发生在内部,因为我们有{{imgIndex}}胡子。

这就是索引属性更新的原因。

使用计算值进行依赖跟踪

在普通模板中,您可以使用以下方法获得基本上相当于计算值的内容get()

<p>{{ curImageCaption() }}</p>
ractive = new Ractive({
  el: 'body',
  template: template,
  data: {
    images: images,
    imgIndex: 0,
    curImageCaption: function () {
      var imgIndex = this.get( 'imgIndex' );
      return this.get( 'images' )[ imgIndex ].caption;
    }
  }
});

在这里,因为我们在函数ractive.get()内部调用curImageCaption,所以 Ractive 知道它需要在每次更改时重新运行images函数imgIndex

您实际上要问的是一个合理的问题:为什么self.imgIndex在魔术模式下检索的值与做的工作不同ractive.get('imgIndex')

答案分为两部分:首先,我没想过要添加该功能,其次,事实证明它不起作用!或者更确切地说,它非常脆弱。我更改了魔术模式,以便get()访问器以相同的方式捕获依赖项ractive.get()- 但如果 R​​active 已经遇到它,它self.imgIndex只是一个访问器描述符(而不是数据描述符)。所以当我们在模板顶部时它可以工作,但在底部时就不行了!<p>Image index: {{ imgIndex }} </p>

通常情况下,处方会相当简单:使用ractive.get()明确的self.imgIndex内部curImageSrc()curImageCaption(). 但是因为您使用的是自定义视图模型对象,所以这并不理想,因为它实际上意味着硬编码键路径。

解决方案 - 创建自定义适配器

这是我的建议 - 制作一个适用于自定义视图模型对象的适配器:

Ractive.adaptors.slides = {
  filter: function ( object ) {
    return object instanceof SlideshowViewModel;
  },
  wrap: function ( ractive, slides, keypath, prefix ) {
    var originalNext, originalPrev;

    // intercept next() and prev()
    originalNext = slides.next;
    slides.next = function () {
      originalNext.call( slides );
      ractive.update( keypath );
    };

    originalPrev = slides.prev;
    slides.prev = function () {
      originalPrev.call( slides );
      ractive.update( keypath );
    };

    return {
      get: function () {
        return {
          current: slides.curImage(),
          index: slides.imgIndex
        };
      },
      teardown: function () {
        slides.next = originalNext;
        slides.prev = originalPrev;
      }
    };
  }
};

var ractive = new Ractive({
  el: '#output',
  template: '#template',
  data: mySlideshow,
  adaptors: [ 'slides' ]
});

这是一个非常简单的适配器,它可能会被改进,但你明白了 - 我们正在拦截对next()and的调用prev(),并让 Ractive 知道(通过ractive.update())它需要进行一些脏检查。请注意,我们正在呈现一个外观(通过get()包装器的方法),因此模板看起来略有不同 -请参见这支笔

希望这可以帮助。

于 2013-12-20T06:28:29.177 回答
0

也许这是一个学术练习,我是 Ractive 的新手,但问题似乎在于模板没有当前图像的上下文。

已编辑:使用当前图像作为上下文块,而不是循环通过集合。

  <div class='slideshow'>
    {{#curImage}}
    <div class='main'>
      <a class='prev' on-tap='prev'><span>&laquo;</span></a>
      <div class='main-image' style='background-image: url({{ src }});'></div>
      <a class='next' on-tap='next'><span>&raquo;</span></a>
    </div>

    <div class='caption'>
      <p>{{ caption }}</p>
      <p>Image index: {{ imgIndex }} </p>
    </div>
  </div>

...

    function SlideshowViewModel(imageData) {
      ...
      self.curImage = imageData[self.imgIndex]
      ...
      self.setLegalIndex = function(newIndex) { 
        self.imgIndex = mod(newIndex,imageData.length); 
        self.curImage = imageData[self.imgIndex]
        } 
    }

这是使用您的原始笔,仅进行关键修改。这是新钢笔

我仍然会将按钮移动到模板的外部,这样中间的显示可以变成局部的:

<div class='main'>
  <a class='prev' on-tap='prev'><span>&laquo;</span></a>
  {{#current}}
    {{>partial}}
  {{/}}
  {{/current}}
  <a class='next' on-tap='next'><span>&raquo;</span></a>
</div>

并封装在 Ractive.extend 中,但如果 ViewModel 适合您...

于 2013-12-20T10:07:49.907 回答