Skip to content

Commit d75ee62

Browse files
committed
rewrite lifecycle hook system
1 parent 4fdeba5 commit d75ee62

File tree

3 files changed

+120
-55
lines changed

3 files changed

+120
-55
lines changed

src/compiler.js

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ var Emitter = require('./emitter'),
1414
makeHash = utils.hash,
1515
extend = utils.extend,
1616
def = utils.defProtected,
17-
hasOwn = Object.prototype.hasOwnProperty
17+
hasOwn = Object.prototype.hasOwnProperty,
18+
19+
// hooks to register
20+
hooks = [
21+
'created', 'ready',
22+
'beforeDestroy', 'afterDestroy',
23+
'enteredView', 'leftView'
24+
]
1825

1926
/**
2027
* The DOM compiler
@@ -82,7 +89,7 @@ function Compiler (vm, options) {
8289
}
8390

8491
// beforeCompile hook
85-
compiler.execHook('beforeCompile', 'created')
92+
compiler.execHook('created')
8693

8794
// the user might have set some props on the vm
8895
// so copy it back to the data...
@@ -130,7 +137,7 @@ function Compiler (vm, options) {
130137
compiler.init = false
131138

132139
// post compile / ready hook
133-
compiler.execHook('afterCompile', 'ready')
140+
compiler.execHook('ready')
134141
}
135142

136143
var CompilerProto = Compiler.prototype
@@ -179,11 +186,13 @@ CompilerProto.setupElement = function (options) {
179186
* Setup observer.
180187
* The observer listens for get/set/mutate events on all VM
181188
* values/objects and trigger corresponding binding updates.
189+
* It also listens for lifecycle hooks.
182190
*/
183191
CompilerProto.setupObserver = function () {
184192

185193
var compiler = this,
186194
bindings = compiler.bindings,
195+
options = compiler.options,
187196
observer = compiler.observer = new Emitter()
188197

189198
// a hash to hold event proxies for each root level key
@@ -206,6 +215,27 @@ CompilerProto.setupObserver = function () {
206215
check(key)
207216
bindings[key].pub()
208217
})
218+
219+
// register hooks
220+
hooks.forEach(function (hook) {
221+
var fns = options[hook]
222+
if (Array.isArray(fns)) {
223+
var i = fns.length
224+
// since hooks were merged with child at head,
225+
// we loop reversely.
226+
while (i--) {
227+
register(hook, fns[i])
228+
}
229+
} else if (fns) {
230+
register(hook, fns)
231+
}
232+
})
233+
234+
function register (hook, fn) {
235+
observer.on('hook:' + hook, function () {
236+
fn.call(compiler.vm, options)
237+
})
238+
}
209239

210240
function check (key) {
211241
if (!bindings[key]) {
@@ -585,14 +615,12 @@ CompilerProto.getOption = function (type, id) {
585615
}
586616

587617
/**
588-
* Execute a user hook
618+
* Emit lifecycle events to trigger hooks
589619
*/
590-
CompilerProto.execHook = function (id, alt) {
591-
var opts = this.options,
592-
hook = opts[id] || opts[alt]
593-
if (hook) {
594-
hook.call(this.vm, opts)
595-
}
620+
CompilerProto.execHook = function (event) {
621+
event = 'hook:' + event
622+
this.observer.emit(event)
623+
this.emitter.emit(event)
596624
}
597625

598626
/**
@@ -627,10 +655,6 @@ CompilerProto.destroy = function () {
627655

628656
compiler.execHook('beforeDestroy')
629657

630-
// unwatch
631-
compiler.observer.off()
632-
compiler.emitter.off()
633-
634658
// unbind all direcitves
635659
i = directives.length
636660
while (i--) {
@@ -679,7 +703,12 @@ CompilerProto.destroy = function () {
679703
vm.$remove()
680704
}
681705

706+
// emit destroy hook
682707
compiler.execHook('afterDestroy')
708+
709+
// finally, unregister all listeners
710+
compiler.observer.off()
711+
compiler.emitter.off()
683712
}
684713

685714
// Helpers --------------------------------------------------------------------

src/main.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,13 @@ function inheritOptions (child, parent, topLevel) {
133133
parentVal = parent[key],
134134
type = utils.typeOf(val)
135135
if (topLevel && type === 'Function' && parentVal) {
136-
// merge hook functions
137-
child[key] = mergeHook(val, parentVal)
136+
// merge hook functions into an array
137+
child[key] = [val]
138+
if (Array.isArray(parentVal)) {
139+
child[key] = child[key].concat(parentVal)
140+
} else {
141+
child[key].push(parentVal)
142+
}
138143
} else if (topLevel && type === 'Object') {
139144
// merge toplevel object options
140145
inheritOptions(val, parentVal)
@@ -146,15 +151,4 @@ function inheritOptions (child, parent, topLevel) {
146151
return child
147152
}
148153

149-
/**
150-
* Merge hook functions
151-
* so parent hooks also get called
152-
*/
153-
function mergeHook (fn, parentFn) {
154-
return function (opts) {
155-
parentFn.call(this, opts)
156-
fn.call(this, opts)
157-
}
158-
}
159-
160154
module.exports = ViewModel

test/unit/specs/api.js

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -664,29 +664,23 @@ describe('UNIT: API', function () {
664664

665665
describe('hooks', function () {
666666

667-
describe('beforeCompile / created', function () {
667+
describe('created', function () {
668668

669669
it('should be called before compile', function () {
670670

671671
var called = false,
672-
Test = Vue.extend({ beforeCompile: function (options) {
673-
assert.ok(options.ok)
674-
called = true
675-
}}),
676-
Test2 = Vue.extend({ created: function (options) {
672+
Test = Vue.extend({ created: function (options) {
677673
assert.ok(options.ok)
678674
called = true
679675
}})
680676
new Test({ ok: true })
681677
assert.ok(called)
682-
called = false
683-
new Test2({ ok: true })
684-
assert.ok(called)
678+
685679
})
686680

687681
})
688682

689-
describe('afterCompile / ready', function () {
683+
describe('ready', function () {
690684

691685
it('should be called after compile with options', function () {
692686
var called = false,
@@ -695,69 +689,117 @@ describe('UNIT: API', function () {
695689
assert.notOk(this.$compiler.init)
696690
called = true
697691
},
698-
Test = Vue.extend({ afterCompile: hook }),
699-
Test2 = Vue.extend({ ready: hook })
692+
Test = Vue.extend({ ready: hook })
700693
new Test({ ok: true })
701694
assert.ok(called)
702-
called = false
703-
new Test2({ ok: true })
704-
assert.ok(called)
705695
})
706696

707697
})
708698

709699
describe('beforeDestroy', function () {
710700

711701
it('should be called before a vm is destroyed', function () {
712-
var called = false
702+
var called1 = false,
703+
called2 = false
713704
var Test = Vue.extend({
714705
beforeDestroy: function () {
715-
called = true
706+
called1 = true
716707
}
717708
})
718709
var test = new Test()
710+
test.$on('hook:beforeDestroy', function () {
711+
called2 = true
712+
})
719713
test.$destroy()
720-
assert.ok(called)
714+
assert.ok(called1)
715+
assert.ok(called2)
721716
})
722717

723718
})
724719

725720
describe('afterDestroy', function () {
726721

727722
it('should be called after a vm is destroyed', function () {
728-
var called = false,
723+
var called1 = false, called2 = false,
729724
Test = Vue.extend({
730725
afterDestroy: function () {
731726
assert.notOk(this.$el.parentNode)
732-
called = true
727+
called1 = true
733728
}
734729
})
735730
var test = new Test()
736731
document.body.appendChild(test.$el)
732+
test.$on('hook:afterDestroy', function () {
733+
called2 = true
734+
})
737735
test.$destroy()
738-
assert.ok(called)
736+
assert.ok(called1)
737+
assert.ok(called2)
738+
})
739+
740+
})
741+
742+
describe('enteredView', function () {
743+
744+
it('should be called after enter view', function () {
745+
var called1 = false, called2 = false,
746+
test = new Vue({
747+
enteredView: function () {
748+
assert.strictEqual(this.$el.parentNode, document.getElementById('test'))
749+
called1 = true
750+
}
751+
})
752+
test.$on('hook:enteredView', function () {
753+
called2 = true
754+
})
755+
test.$appendTo('#test')
756+
assert.ok(called1)
757+
assert.ok(called2)
758+
})
759+
760+
})
761+
762+
describe('leftView', function () {
763+
764+
it('should be called after left view', function () {
765+
var called1 = false, called2 = false,
766+
test = new Vue({
767+
leftView: function () {
768+
assert.strictEqual(this.$el.parentNode, null)
769+
called1 = true
770+
}
771+
})
772+
test.$on('hook:leftView', function () {
773+
called2 = true
774+
})
775+
document.getElementById('test').appendChild(test.$el)
776+
test.$remove()
777+
assert.ok(called1)
778+
assert.ok(called2)
739779
})
740780

741781
})
742782

743783
describe('Hook inheritance', function () {
744784

745785
it('should merge hooks with parent Class', function () {
746-
var parentCreated = false,
747-
childCreated = false
786+
var called = []
748787
var Parent = Vue.extend({
749788
created: function () {
750-
parentCreated = true
789+
called.push('parent')
751790
}
752791
})
753792
var Child = Parent.extend({
754793
created: function () {
755-
childCreated = true
794+
called.push('child')
795+
}
796+
})
797+
new Child({
798+
created: function () {
799+
called.push('instance')
756800
}
757801
})
758-
new Child()
759-
assert.ok(parentCreated)
760-
assert.ok(childCreated)
802+
assert.deepEqual(called, ['parent', 'child', 'instance'])
761803
})
762804

763805
})

0 commit comments

Comments
 (0)