Просмотр исходного кода

keep-alive option for v-component

Evan You 11 лет назад
Родитель
Сommit
d3846f6685
1 измененных файлов с 110 добавлено и 24 удалено
  1. 110 24
      src/directives/component.js

+ 110 - 24
src/directives/component.js

@@ -1,27 +1,26 @@
 var _ = require('../util')
 var Watcher = require('../watcher')
 
-/**
- * Possible permutations:
- *
- * - literal:
- *   v-component="comp"
- *
- * - dynamic:
- *   v-component="{{currentView}}"
- *
- * - conditional:
- *   v-component="comp" v-if="abc"
- *
- * - dynamic + conditional:
- *   v-component="{{currentView}}" v-if="abc"
- *
- */
-
 module.exports = {
 
   isLiteral: true,
 
+  /**
+   * Setup. Need to check a few possible permutations:
+   *
+   * - literal:
+   *   v-component="comp"
+   *
+   * - dynamic:
+   *   v-component="{{currentView}}"
+   *
+   * - conditional:
+   *   v-component="comp" v-if="abc"
+   *
+   * - dynamic + conditional:
+   *   v-component="{{currentView}}" v-if="abc"
+   */
+
   bind: function () {
     if (!this.el.__vue__) {
       // create a ref anchor
@@ -30,6 +29,8 @@ module.exports = {
       _.remove(this.el)
       // check v-if conditionals
       this.checkIf()
+      // check keep-alive options
+      this.checkKeepAlive()
       // if static, build right now.
       if (!this._isDynamicLiteral) {
         this.resolveCtor(this.expression)
@@ -43,6 +44,12 @@ module.exports = {
     }
   },
 
+  /**
+   * Check if v-component is being used together with v-if.
+   * If yes, we created a watcher for the v-if value and
+   * react to its value change in `this.ifCallback`.
+   */
+
   checkIf: function () {
     var condition = _.attr(this.el, 'if')
     if (condition !== null) {
@@ -58,6 +65,13 @@ module.exports = {
     }
   },
 
+  /**
+   * Callback when v-if value changes.
+   * Marks the active flag.
+   *
+   * @param {*} value
+   */
+
   ifCallback: function (value) {
     if (value) {
       this.active = true
@@ -68,31 +82,95 @@ module.exports = {
     }
   },
 
+  /**
+   * Check if the "keep-alive" flag is present.
+   * If yes, instead of destroying the active vm when
+   * hiding (v-if) or switching (dynamic literal) it,
+   * we simply remove it from the DOM and save it in a
+   * cache object, with its constructor id as the key.
+   */
+
+  checkKeepAlive: function () {
+    // check keep-alive flag
+    this.keepAlive = this.el.hasAttribute('keep-alive')
+    if (this.keepAlive) {
+      this.cache = Object.create(null)
+    }
+  },
+
+  /**
+   * Resolve the component constructor to use when creating
+   * the child vm. If the component id is empty string, the
+   * default 'Vue' constructor will be used.
+   */
+
   resolveCtor: function (id) {
-    var registry = this.vm.$options.components
-    this.Ctor = registry[id]
-    if (!this.Ctor) {
-      _.warn('Failed to resolve component: ' + id)
+    if (id === '') {
+      this.id = '__vue__'
+      this.Ctor = _.Vue
+    } else {
+      this.id = id
+      this.Ctor = this.vm.$options.components[id]
+      if (!this.Ctor) {
+        _.warn('Failed to resolve component: ' + id)
+      }
     }
   },
 
+  /**
+   * Instantiate/insert a new child vm.
+   * If keep alive and has cached instance, insert that
+   * instance; otherwise build a new one and cache it.
+   */
+
   build: function () {
-    if (this.active && this.Ctor && !this.childVM) {
+    if (!this.active) {
+      return
+    }
+    if (this.keepAlive) {
+      var vm = this.cache[this.id]
+      if (vm) {
+        this.childVM = vm
+        vm.$before(this.ref)
+        return
+      }
+    }
+    if (this.Ctor && !this.childVM) {
       this.childVM = new this.Ctor({
         el: this.el.cloneNode(true),
         parent: this.vm
       })
+      this.cache[this.id] = this.childVM
       this.childVM.$before(this.ref)
     }
   },
 
+  /**
+   * Teardown the active vm.
+   * If keep alive, simply remove it; otherwise destroy it.
+   *
+   * @param {Boolean} remove
+   */
+
   unbuild: function (remove) {
-    if (this.childVM) {
+    if (!this.childVM) {
+      return
+    }
+    if (this.keepAlive) {
+      if (remove) {
+        this.childVM.$remove()
+      }
+    } else {
       this.childVM.$destroy(remove)
-      this.childVM = null
     }
+    this.childVM = null
   },
 
+  /**
+   * Update callback for the dynamic literal scenario,
+   * e.g. v-component="{{view}}"
+   */
+
   update: function (value) {
     this.unbuild(true)
     if (value) {
@@ -101,7 +179,15 @@ module.exports = {
     }
   },
 
+  /**
+   * Unbind.
+   * Make sure keepAlive is set to false so that the
+   * instance is always destroyed. Teardown v-if watcher
+   * if present.
+   */
+
   unbind: function () {
+    this.keepAlive = false
     this.unbuild()
     if (this.ifWatcher) {
       this.ifWatcher.teardown()