2

問題のデモについては、このペンを参照してください(チュートリアルのスライドショーに基づく)。「次へ」と「前へ」の矢印をクリックすると、imgIndex口ひげは正しく更新されますが、 などの式の口ひげ<p>{{ curImageCaption() }}</p>は値がいつ変更されるかを認識しません。

つまり、オブジェクトは、式が再評価されると口ひげの値が変わるように変更されますが、ractive はそれを認識していないようです。書き込みアダプターを除いて、これを機能させる方法はありますか? マジックモードの仕組みを誤解していますか? 興味深いことに、イベント ハンドラー内で明示的に呼び出してもractive.update()、ractive は応答しません。

新しい情報で更新

さらにいじった後、私はそれを機能させるこのハックを思いつきました。ハックは、変更することです。たとえば、単純なプリミティブを口ひげの表現<p>{{ curImageCaption() }}</p><p>{{ curImageCaption(imgIndex) }}</p>追加して、ractive が正しく監視する方法を理解するようにします。

今何が起こっているかはわかりますが、変化するプリミティブを含む口ひげ式に引数を明示的に追加する必要があると、別のドメイン オブジェクトを持つ目的の多くが無効になります。つまり、ractive を念頭に置いてドメイン オブジェクトをコーディングしています。プリミティブの変更を使用して、変更を通知するための一種の基本的な pub/sub メカニズムを使用します。

カスタム オブジェクトに実際の pub/sub メカニズムを作成する必要があり、それが ractive で明示的にサブスクライブされるのは問題ありません。問題は、OPで指摘したように、 ractive が を介して変更を通知された場合ractive.update()でも、偽の引数ハックを使用しない限り、口ひげを再計算する必要があることをまだ認識していないことです。そのため、すべてを機能させるためにどのコールバック ractive を登録する必要があるかは明確ではありません。

これを行うのに十分なほどractiveの内部動作を理解していませんが、必要なのは、それらを直接_deps操作し、式の再計算を手動でトリガーする機能であると思います。これが正しいと思われる場合は、それを達成する方法の例をいただければ幸いです。

UPDATE 2 -- まともな解決策

これは、それほどハックではない回避策の概念実証です

アイデアは、ECMA5 プロパティを使用してカスタム ドメイン オブジェクトを装飾し、使用したいが、ractive テンプレート内では機能しない既存のメソッドに委任するプロパティを提供することです。プロパティ、おと、うまくいきます。

<p>{{ curImageCaption() }}</p>そのため、単純に を記述する代わりに、次の<p>{{ imageCaption }}</p>ようにカスタム ドメイン オブジェクトを装飾します。

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

この装飾は、私のデモでは少し冗長ですが、オブジェクトの新しい ractive フレンドリなプロパティ名をオブジェクトの既存のメソッドの名前にマッピングし、上記のボイラープレートを処理するオブジェクトを受け入れるヘルパー メソッドを作成することで、簡単にスリム化できます。 .

注: この方法の欠点の 1 つはractive.update()、イベント ハンドラーで手動で呼び出す必要があることです。それを回避する方法があれば知りたいです。また、そうでない場合、これによってどの程度のパフォーマンス ヒットが発生するのでしょうか? それは、ractive の外科的アップデートの目的全体を無効にしますか?

Update 3 -- より適切な解決策はありますか?

このペンはさらに別のアプローチを採用しており、汎用ディスパッチャ オブジェクト ( を実装するオブジェクトnotify()) を介してカスタム ドメイン モデルを ractive にリンクします。これまでのアプローチの中でこれが私のお気に入りだと思います....

これは公式のractive アダプターに似ていますが、オブジェクトをラップするのではなく、非公式の ractive アダプターをドメイン オブジェクトに渡すために DI を使用しています。一見、「非アクティブにコーディング」しているように見えるかもしれませんが、実際にはこれは部分的にしか当てはまりません。別のフレームワークを使用していたとしても、何らかの通知メカニズムを使用して変更をビュー モデルにブロードキャストし、ビューがそれに反応できるようにする必要があります。この 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}}口ひげがあるため、これは内部で発生します。

そのため、index プロパティが更新されます。

計算値による依存関係の追跡

通常のテンプレート内では、次の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')ですか?

答えは 2 つあります。1 つは、その機能を追加することを考えていなかったことです。もう 1 つは、機能しないことが判明したことです。というか、非常に脆い。get()アクセサーが同じ方法で依存関係をキャプチャするようにマジック モードを変更しましたが、Ractive が既に遭遇した場合は (データ記述子ではなく) アクセサー記述子にすぎractive.get()ません。したがって、テンプレートの上部にある場合は機能しましたが、下部にある場合は機能しません!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()prev()( を介して) Ractiveractive.update()にダーティ チェックを行う必要があることを知らせます。(ラッパーのメソッドを介して) ファサードを提示しているget()ため、テンプレートがわずかに異なることに注意してください -このペン を参照してください

お役に立てれば。

于 2013-12-20T06:28:29.177 に答える
0

おそらくこれは学術的な演習であり、私は Ractive を初めて使用しますが、問題はテンプレートに現在の画像のコンテキストがないことにあるようです。

EDITED : コレクションをループする代わりに、現在の Image をコンテキスト ブロックとして使用します。

  <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 に答える