瀏覽代碼

rewrite lifecycle hook system

Evan You 12 年之前
父節點
當前提交
d75ee6234e
共有 3 個文件被更改,包括 120 次插入55 次删除
  1. 43 14
      src/compiler.js
  2. 7 13
      src/main.js
  3. 70 28
      test/unit/specs/api.js

+ 43 - 14
src/compiler.js

@@ -14,7 +14,14 @@ var Emitter     = require('./emitter'),
     makeHash    = utils.hash,
     extend      = utils.extend,
     def         = utils.defProtected,
-    hasOwn      = Object.prototype.hasOwnProperty
+    hasOwn      = Object.prototype.hasOwnProperty,
+
+    // hooks to register
+    hooks = [
+        'created', 'ready',
+        'beforeDestroy', 'afterDestroy',
+        'enteredView', 'leftView'
+    ]
 
 /**
  *  The DOM compiler
@@ -82,7 +89,7 @@ function Compiler (vm, options) {
     }
 
     // beforeCompile hook
-    compiler.execHook('beforeCompile', 'created')
+    compiler.execHook('created')
 
     // the user might have set some props on the vm 
     // so copy it back to the data...
@@ -130,7 +137,7 @@ function Compiler (vm, options) {
     compiler.init = false
 
     // post compile / ready hook
-    compiler.execHook('afterCompile', 'ready')
+    compiler.execHook('ready')
 }
 
 var CompilerProto = Compiler.prototype
@@ -179,11 +186,13 @@ CompilerProto.setupElement = function (options) {
  *  Setup observer.
  *  The observer listens for get/set/mutate events on all VM
  *  values/objects and trigger corresponding binding updates.
+ *  It also listens for lifecycle hooks.
  */
 CompilerProto.setupObserver = function () {
 
     var compiler = this,
         bindings = compiler.bindings,
+        options  = compiler.options,
         observer = compiler.observer = new Emitter()
 
     // a hash to hold event proxies for each root level key
@@ -206,6 +215,27 @@ CompilerProto.setupObserver = function () {
             check(key)
             bindings[key].pub()
         })
+    
+    // register hooks
+    hooks.forEach(function (hook) {
+        var fns = options[hook]
+        if (Array.isArray(fns)) {
+            var i = fns.length
+            // since hooks were merged with child at head,
+            // we loop reversely.
+            while (i--) {
+                register(hook, fns[i])
+            }
+        } else if (fns) {
+            register(hook, fns)
+        }
+    })
+
+    function register (hook, fn) {
+        observer.on('hook:' + hook, function () {
+            fn.call(compiler.vm, options)
+        })
+    }
 
     function check (key) {
         if (!bindings[key]) {
@@ -585,14 +615,12 @@ CompilerProto.getOption = function (type, id) {
 }
 
 /**
- *  Execute a user hook
+ *  Emit lifecycle events to trigger hooks
  */
-CompilerProto.execHook = function (id, alt) {
-    var opts = this.options,
-        hook = opts[id] || opts[alt]
-    if (hook) {
-        hook.call(this.vm, opts)
-    }
+CompilerProto.execHook = function (event) {
+    event = 'hook:' + event
+    this.observer.emit(event)
+    this.emitter.emit(event)
 }
 
 /**
@@ -627,10 +655,6 @@ CompilerProto.destroy = function () {
 
     compiler.execHook('beforeDestroy')
 
-    // unwatch
-    compiler.observer.off()
-    compiler.emitter.off()
-
     // unbind all direcitves
     i = directives.length
     while (i--) {
@@ -679,7 +703,12 @@ CompilerProto.destroy = function () {
         vm.$remove()
     }
 
+    // emit destroy hook
     compiler.execHook('afterDestroy')
+
+    // finally, unregister all listeners
+    compiler.observer.off()
+    compiler.emitter.off()
 }
 
 // Helpers --------------------------------------------------------------------

+ 7 - 13
src/main.js

@@ -133,8 +133,13 @@ function inheritOptions (child, parent, topLevel) {
             parentVal = parent[key],
             type = utils.typeOf(val)
         if (topLevel && type === 'Function' && parentVal) {
-            // merge hook functions
-            child[key] = mergeHook(val, parentVal)
+            // merge hook functions into an array
+            child[key] = [val]
+            if (Array.isArray(parentVal)) {
+                child[key] = child[key].concat(parentVal)
+            } else {
+                child[key].push(parentVal)
+            }
         } else if (topLevel && type === 'Object') {
             // merge toplevel object options
             inheritOptions(val, parentVal)
@@ -146,15 +151,4 @@ function inheritOptions (child, parent, topLevel) {
     return child
 }
 
-/**
- *  Merge hook functions
- *  so parent hooks also get called
- */
-function mergeHook (fn, parentFn) {
-    return function (opts) {
-        parentFn.call(this, opts)
-        fn.call(this, opts)
-    }
-}
-
 module.exports = ViewModel

+ 70 - 28
test/unit/specs/api.js

@@ -664,29 +664,23 @@ describe('UNIT: API', function () {
 
             describe('hooks', function () {
 
-                describe('beforeCompile / created', function () {
+                describe('created', function () {
                 
                     it('should be called before compile', function () {
                         
                         var called = false,
-                            Test = Vue.extend({ beforeCompile: function (options) {
-                                assert.ok(options.ok)
-                                called = true
-                            }}),
-                            Test2 = Vue.extend({ created: function (options) {
+                            Test = Vue.extend({ created: function (options) {
                                 assert.ok(options.ok)
                                 called = true
                             }})
                         new Test({ ok: true })
                         assert.ok(called)
-                        called = false
-                        new Test2({ ok: true })
-                        assert.ok(called)
+
                     })
 
                 })
 
-                describe('afterCompile / ready', function () {
+                describe('ready', function () {
 
                     it('should be called after compile with options', function () {
                         var called = false,
@@ -695,13 +689,9 @@ describe('UNIT: API', function () {
                                 assert.notOk(this.$compiler.init)
                                 called = true
                             },
-                            Test = Vue.extend({ afterCompile: hook }),
-                            Test2 = Vue.extend({ ready: hook })
+                            Test = Vue.extend({ ready: hook })
                         new Test({ ok: true })
                         assert.ok(called)
-                        called = false
-                        new Test2({ ok: true })
-                        assert.ok(called)
                     })
 
                 })
@@ -709,15 +699,20 @@ describe('UNIT: API', function () {
                 describe('beforeDestroy', function () {
                 
                     it('should be called before a vm is destroyed', function () {
-                        var called = false
+                        var called1 = false,
+                            called2 = false
                         var Test = Vue.extend({
                             beforeDestroy: function () {
-                                called = true
+                                called1 = true
                             }
                         })
                         var test = new Test()
+                        test.$on('hook:beforeDestroy', function () {
+                            called2 = true
+                        })
                         test.$destroy()
-                        assert.ok(called)
+                        assert.ok(called1)
+                        assert.ok(called2)
                     })
 
                 })
@@ -725,17 +720,62 @@ describe('UNIT: API', function () {
                 describe('afterDestroy', function () {
                     
                     it('should be called after a vm is destroyed', function () {
-                        var called = false,
+                        var called1 = false, called2 = false,
                             Test = Vue.extend({
                                 afterDestroy: function () {
                                     assert.notOk(this.$el.parentNode)
-                                    called = true
+                                    called1 = true
                                 }
                             })
                         var test = new Test()
                         document.body.appendChild(test.$el)
+                        test.$on('hook:afterDestroy', function () {
+                            called2 = true
+                        })
                         test.$destroy()
-                        assert.ok(called)
+                        assert.ok(called1)
+                        assert.ok(called2)
+                    })
+
+                })
+
+                describe('enteredView', function () {
+                    
+                    it('should be called after enter view', function () {
+                        var called1 = false, called2 = false,
+                            test = new Vue({
+                                enteredView: function () {
+                                    assert.strictEqual(this.$el.parentNode, document.getElementById('test'))
+                                    called1 = true
+                                }
+                            })
+                        test.$on('hook:enteredView', function () {
+                            called2 = true
+                        })
+                        test.$appendTo('#test')
+                        assert.ok(called1)
+                        assert.ok(called2)
+                    })
+
+                })
+
+                describe('leftView', function () {
+                    
+                    it('should be called after left view', function () {
+                        var called1 = false, called2 = false,
+                            test = new Vue({
+                                leftView: function () {
+                                    assert.strictEqual(this.$el.parentNode, null)
+                                    called1 = true
+                                }
+                            })
+                        test.$on('hook:leftView', function () {
+                            called2 = true
+                        })
+                        document.getElementById('test').appendChild(test.$el)
+                        test.$remove()
+                        assert.ok(called1)
+                        assert.ok(called2)
                     })
 
                 })
@@ -743,21 +783,23 @@ describe('UNIT: API', function () {
                 describe('Hook inheritance', function () {
                     
                     it('should merge hooks with parent Class', function () {
-                        var parentCreated = false,
-                            childCreated = false
+                        var called = []
                         var Parent = Vue.extend({
                             created: function () {
-                                parentCreated = true
+                                called.push('parent')
                             }
                         })
                         var Child = Parent.extend({
                             created: function () {
-                                childCreated = true
+                                called.push('child')
+                            }
+                        })
+                        new Child({
+                            created: function () {
+                                called.push('instance')
                             }
                         })
-                        new Child()
-                        assert.ok(parentCreated)
-                        assert.ok(childCreated)
+                        assert.deepEqual(called, ['parent', 'child', 'instance'])
                     })
 
                 })