Evan You 11 лет назад
Родитель
Сommit
6a562c4951
1 измененных файлов с 95 добавлено и 48 удалено
  1. 95 48
      src/directives/repeat.js

+ 95 - 48
src/directives/repeat.js

@@ -9,6 +9,12 @@ var transclude = require('../compiler/transclude')
 var mergeOptions = require('../util/merge-option')
 var uid = 0
 
+// async component resolution states
+var UNRESOLVED = 0
+var PENDING = 1
+var RESOLVED = 2
+var ABORTED = 3
+
 module.exports = {
 
   /**
@@ -85,6 +91,7 @@ module.exports = {
    */
 
   checkComponent: function () {
+    this.componentState = UNRESOLVED
     var id = _.attr(this.el, 'component')
     var options = this.vm.$options
     if (!id) {
@@ -106,37 +113,105 @@ module.exports = {
         this.inlineTempalte = _.extractContent(this.el, true)
       }
       var tokens = textParser.parse(id)
-      if (!tokens) { // static component
-        var Ctor = this.Ctor = options.components[id]
-        _.assertAsset(Ctor, 'component', id)
-        var merged = mergeOptions(Ctor.options, {}, {
-          $parent: this.vm
-        })
-        merged.template = this.inlineTempalte || merged.template
-        merged._asComponent = true
-        merged._parent = this.vm
-        this.template = transclude(this.template, merged)
-        // Important: mark the template as a root node so that
-        // custom element components don't get compiled twice.
-        // fixes #822
-        this.template.__vue__ = true
-        this._linkFn = compile(this.template, merged)
-      } else {
-        // to be resolved later
+      if (tokens) {
+        // dynamic component to be resolved later
         var ctorExp = textParser.tokensToExp(tokens)
         this.ctorGetter = expParser.parse(ctorExp).get
+      } else {
+        // static
+        this.componentId = id
+        this.pendingData = null
       }
     }
   },
 
+  resolveComponent: function () {
+    this.componentState = PENDING
+    this.vm._resolveComponent(this.componentId, _.bind(function (Ctor) {
+      if (this.componentState === ABORTED) {
+        return
+      }
+      this.Ctor = Ctor
+      var merged = mergeOptions(Ctor.options, {}, {
+        $parent: this.vm
+      })
+      merged.template = this.inlineTempalte || merged.template
+      merged._asComponent = true
+      merged._parent = this.vm
+      this.template = transclude(this.template, merged)
+      // Important: mark the template as a root node so that
+      // custom element components don't get compiled twice.
+      // fixes #822
+      this.template.__vue__ = true
+      this._linkFn = compile(this.template, merged)
+      this.componentState = RESOLVED
+      this.realUpdate(this.pendingData)
+      this.pendingData = null
+    }, this))
+  },
+
+    /**
+   * Resolve a dynamic component to use for an instance.
+   * The tricky part here is that there could be dynamic
+   * components depending on instance data.
+   *
+   * @param {Object} data
+   * @param {Object} meta
+   * @return {Function}
+   */
+
+  resolveDynamicComponent: function (data, meta) {
+    // create a temporary context object and copy data
+    // and meta properties onto it.
+    // use _.define to avoid accidentally overwriting scope
+    // properties.
+    var context = Object.create(this.vm)
+    var key
+    for (key in data) {
+      _.define(context, key, data[key])
+    }
+    for (key in meta) {
+      _.define(context, key, meta[key])
+    }
+    var id = this.ctorGetter.call(context, context)
+    var Ctor = this.vm.$options.components[id]
+    _.assertAsset(Ctor, 'component', id)
+    return Ctor
+  },
+
   /**
    * Update.
-   * This is called whenever the Array mutates.
+   * This is called whenever the Array mutates. If we have
+   * a component, we might need to wait for it to resolve
+   * asynchronously.
    *
    * @param {Array|Number|String} data
    */
 
   update: function (data) {
+    if (this.componentId) {
+      var state = this.componentState
+      if (state === UNRESOLVED) {
+        this.pendingData = data
+        // once resolved, it will call realUpdate
+        this.resolveComponent()
+      } else if (state === PENDING) {
+        this.pendingData = data
+      } else if (state === RESOLVED) {
+        this.realUpdate(data)
+      }
+    } else {
+      this.realUpdate(data)
+    }
+  },
+
+  /**
+   * The real update that actually modifies the DOM.
+   *
+   * @param {Array|Number|String} data
+   */
+
+  realUpdate: function (data) {
     data = data || []
     var type = typeof data
     if (type === 'number') {
@@ -292,7 +367,7 @@ module.exports = {
       data = raw
     }
     // resolve constructor
-    var Ctor = this.Ctor || this.resolveCtor(data, meta)
+    var Ctor = this.Ctor || this.resolveDynamicComponent(data, meta)
     var vm = this.vm.$addChild({
       el: templateParser.clone(this.template),
       _asComponent: this.asComponent,
@@ -323,40 +398,12 @@ module.exports = {
     return vm
   },
 
-  /**
-   * Resolve a contructor to use for an instance.
-   * The tricky part here is that there could be dynamic
-   * components depending on instance data.
-   *
-   * @param {Object} data
-   * @param {Object} meta
-   * @return {Function}
-   */
-
-  resolveCtor: function (data, meta) {
-    // create a temporary context object and copy data
-    // and meta properties onto it.
-    // use _.define to avoid accidentally overwriting scope
-    // properties.
-    var context = Object.create(this.vm)
-    var key
-    for (key in data) {
-      _.define(context, key, data[key])
-    }
-    for (key in meta) {
-      _.define(context, key, meta[key])
-    }
-    var id = this.ctorGetter.call(context, context)
-    var Ctor = this.vm.$options.components[id]
-    _.assertAsset(Ctor, 'component', id)
-    return Ctor
-  },
-
   /**
    * Unbind, teardown everything
    */
 
   unbind: function () {
+    this.componentState = ABORTED
     if (this.refID) {
       this.vm.$[this.refID] = null
     }