This commit is contained in:
Evan You 2016-04-20 17:18:07 -04:00
parent 83210dfd5c
commit 1b9343f7e0
7 changed files with 189 additions and 231 deletions

View File

@ -1,19 +1,48 @@
import { isArray, isObject } from '../../util/index'
import { extend, isArray, isObject } from '../../util/index'
import { setClass } from '../class-util'
function updateClass (oldVnode, vnode) {
const el = vnode.elm
let dynamicClass = vnode.data.class
let staticClass = vnode.data.staticClass
let transitionClass = el._transitionClasses
if (staticClass || dynamicClass || transitionClass) {
dynamicClass = genClass(dynamicClass)
transitionClass = genClass(transitionClass)
const cls = concat(concat(staticClass, dynamicClass), transitionClass)
if (cls !== oldVnode.class) {
setClass(el, cls)
}
vnode.class = cls
let data = vnode.data
if (!data.staticClass && !data.class) {
return
}
// check if this is a component container node
// or a child component root node
if (vnode.child) {
data = mergeClassData(vnode.child._vnode.data, data)
} else if (vnode.parent) {
data = mergeClassData(data, vnode.parent.data)
}
let cls = genClass(data)
// handle transition classes
const transitionClass = el._transitionClasses
if (transitionClass) {
cls = concat(cls, stringifyClass(transitionClass))
}
// set the class
if (cls !== el._prevClass) {
setClass(el, cls)
el._prevClass = cls
}
}
function mergeClassData (child, parent) {
return {
staticClass: concat(child.staticClass, parent.staticClass),
class: extend(child.class || {}, parent.class)
}
}
function genClass (data) {
const dynamicClass = data.class
const staticClass = data.staticClass
if (staticClass || dynamicClass) {
return concat(staticClass, stringifyClass(dynamicClass))
}
}
@ -21,7 +50,7 @@ function concat (a, b) {
return a ? b ? (a + ' ' + b) : a : (b || '')
}
function genClass (data) {
function stringifyClass (data) {
if (!data) {
return ''
}
@ -31,7 +60,7 @@ function genClass (data) {
if (isArray(data)) {
let res = ''
for (let i = 0, l = data.length; i < l; i++) {
if (data[i]) res += genClass(data[i]) + ' '
if (data[i]) res += stringifyClass(data[i]) + ' '
}
return res.slice(0, -1)
}

View File

@ -30,6 +30,12 @@ function nextFrame (fn) {
}
function beforeEnter (_, vnode) {
// if this is a component root node and the compoennt's
// parent container node also has transition, skip.
if (vnode.parent && vnode.parent.data.transition) {
return
}
const el = vnode.elm
const data = vnode.data.transition
if (data == null) {
@ -85,6 +91,12 @@ function beforeEnter (_, vnode) {
}
function onLeave (vnode, rm) {
// if this is a component root node and the compoennt's
// parent container node also has transition, skip.
if (vnode.parent && vnode.parent.data.transition) {
return
}
const el = vnode.elm
// call enter callback now
if (el._enterCb) {

View File

@ -66,8 +66,10 @@ export function lifecycleMixin (Vue) {
}
this._vnode = vnode
// set parent vnode element
if (this.$options._parentVnode) {
this.$options._parentVnode.elm = this.$el
const parentNode = this.$options._parentVnode
if (parentNode) {
parentNode.elm = this.$el
vnode.parent = parentNode
}
if (this._mounted) {
callHook(this, 'updated')

View File

@ -1,12 +1,12 @@
import { observerState } from '../observer/index'
import createElement from '../vdom/create-element'
import { flatten, updateListeners } from '../vdom/helpers'
import { flatten } from '../vdom/helpers'
import {
bind,
resolveAsset,
isArray,
isObject,
getPropValue
validateProp
} from '../util/index'
export const renderState = {
@ -96,29 +96,25 @@ export function renderMixin (Vue) {
}
}
Vue.prototype._updateFromParent = function (parentData, children, key) {
const oldParentData = this.$options._renderData
this.$options._renderData = parentData
Vue.prototype._updateFromParent = function (propsData, parentVnode, children) {
this.$options._parentVnode = parentVnode
this.$options._renderChildren = children
// update props and listeners
if (parentData) {
updateEvents(this, parentData, oldParentData)
// if any prop has changed it would trigger and queue an update,
// but if no props changed, nothing happens
const propsChanged = updateProps(this, parentData)
// diff parent data (attrs on the placeholder) and queue update
// if anything changed. only do this if props didn't change, because
// if props changed then an update has already been queued.
if (!propsChanged && parentDataChanged(parentData, oldParentData)) {
this.$forceUpdate()
// update props
if (propsData && this.$options.props) {
observerState.shouldConvert = false
const propKeys = this.$options.propKeys
for (let i = 0; i < propKeys.length; i++) {
let key = propKeys[i]
this[key] = validateProp(this, key, propsData)
}
observerState.shouldConvert = true
}
}
Vue.prototype._render = function () {
const prev = renderState.activeInstance
renderState.activeInstance = this
const { render, _renderData, _renderChildren } = this.$options
const { render, _renderChildren } = this.$options
// resolve slots. becaues slots are rendered in parent scope,
// we set the activeInstance to parent.
if (_renderChildren) {
@ -126,10 +122,6 @@ export function renderMixin (Vue) {
}
// render self
const vnode = render.call(this._renderProxy)
// update parent data
if (_renderData) {
mergeParentData(this, vnode.data, _renderData)
}
// restore render state
renderState.activeInstance = prev
return vnode
@ -157,115 +149,3 @@ function resolveSlots (vm, children) {
vm.$slots = slots
}
}
const keysToDiff = ['class', 'style', 'attrs', 'props', 'directives', 'transition']
function parentDataChanged (data, oldData) {
let key, old, cur, i, l, j, k
for (i = 0, l = keysToDiff.length; i < l; i++) {
key = keysToDiff[i]
cur = data[key]
old = oldData[key]
if (!old) {
if (!cur) {
continue
} else {
return true
}
}
if (isArray(old)) {
if (!isArray(cur)) return true
if (cur.length !== old.length) return true
for (j = 0, k = old.length; j < k; j++) {
if (isObject(old[i])) {
if (!isObject(cur[i])) return true
if (diffObject(cur, old)) return true
} else if (old[i] !== cur[i]) {
return true
}
}
} else if (diffObject(cur, old)) {
return true
}
}
return false
}
function diffObject (cur, old) {
for (let key in old) {
if (cur[key] !== old[key]) return true
}
}
function mergeParentData (vm, data, parentData) {
const props = vm.$options.props
if (parentData.attrs) {
const attrs = data.attrs || (data.attrs = {})
for (let key in parentData.attrs) {
if (!props || !props[key]) {
attrs[key] = parentData.attrs[key]
}
}
}
if (parentData.props) {
const props = data.props || (data.props = {})
for (let key in parentData.props) {
if (!props || !props[key]) {
props[key] = parentData.props[key]
}
}
}
if (parentData.staticClass) {
data.staticClass = data.staticClass
? data.staticClass + ' ' + parentData.staticClass
: parentData.staticClass
}
if (parentData.class) {
if (!data.class) {
data.class = parentData.class
} else {
data.class = (isArray(data.class) ? data.class : []).concat(parentData.class)
}
}
if (parentData.style) {
if (!data.style) {
data.style = parentData.style
} else {
data.style = (isArray(data.style) ? data.style : []).concat(parentData.style)
}
}
if (parentData.directives) {
data.directives = parentData.directives.concat(data.directives || [])
}
if (parentData.transition != null) {
data.transition = parentData.transition
}
}
function updateProps (vm, data) {
let changed = false
if (data.attrs || data.props) {
let keys = vm.$options.propKeys
if (keys) {
observerState.shouldConvert = false
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
let oldVal = vm[key]
let newVal = getPropValue(data, key, vm)
if (oldVal !== newVal) {
vm[key] = newVal
changed = true
}
}
observerState.shouldConvert = true
}
}
return changed
}
function updateEvents (vm, data, oldData) {
if (data.on) {
updateListeners(data.on, oldData.on || {}, (event, handler) => {
vm.$on(event, handler)
})
}
}

View File

@ -12,7 +12,7 @@ import {
hasOwn,
isPlainObject,
bind,
getPropValue
validateProp
} from '../util/index'
export function initState (vm) {
@ -26,17 +26,15 @@ export function initState (vm) {
function initProps (vm) {
const props = vm.$options.props
const propsData = vm.$options.propsData
if (props) {
const keys = vm.$options.propKeys = Object.keys(props)
const isRoot = !vm.$parent
const data = isRoot
? { props: vm.$options.propsData }
: vm.$options._renderData
// root instance props should be converted
observerState.shouldConvert = isRoot
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
defineReactive(vm, key, getPropValue(data, key, vm))
defineReactive(vm, key, validateProp(vm, key, propsData))
}
observerState.shouldConvert = true
}

View File

@ -1,26 +1,12 @@
import { hyphenate, hasOwn, isArray, isObject, isPlainObject } from '../../shared/util'
import { hasOwn, isArray, isObject, isPlainObject } from '../../shared/util'
import { observe, observerState } from '../observer/index'
import { warn } from './debug'
export function getPropValue (data, key, vm) {
if (!data) return
export function validateProp (vm, key, propsData) {
if (!propsData) return
const prop = vm.$options.props[key]
const altKey = hyphenate(key)
const props = data.props
const attrs = data.attrs
let value
let absent = false
if (attrs && hasOwn(attrs, key)) {
value = attrs[key]
} else if (attrs && hasOwn(attrs, altKey)) {
value = attrs[altKey]
} else if (props && hasOwn(props, key)) {
value = props[key]
} else if (props && hasOwn(props, altKey)) {
value = props[altKey]
} else {
absent = true
}
const absent = hasOwn(propsData, key)
let value = propsData[key]
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key)

View File

@ -1,6 +1,48 @@
import Vue from '../instance/index'
import VNode from './vnode'
import { callHook } from '../instance/lifecycle'
import { warn, isObject } from '../util/index'
import { warn, isObject, hasOwn, hyphenate } from '../util/index'
const hooks = {
init (vnode) {
const { Ctor, propsData, parent, children } = vnode.componentOptions
const child = new Ctor({
parent,
propsData,
_parentVnode: vnode,
_renderChildren: children
})
// the child sets the parent vnode's elm when mounted
// and when updated.
child.$mount()
vnode.child = child
},
prepatch (oldVnode, vnode) {
const old = oldVnode.componentOptions
const cur = vnode.componentOptions
if (cur.Ctor !== old.Ctor) {
// component changed, teardown and create new
// TODO: keep-alive?
oldVnode.child.$destroy()
hooks.init(vnode)
} else {
vnode.child = oldVnode.child
const propsData = extractProps(vnode.data)
vnode.child._updateFromParent(
propsData, // updated props
vnode, // new parent vnode
cur.children // new children
)
}
},
insert (vnode) {
callHook(vnode.child, 'ready')
}
}
const hooksToMerge = Object.keys(hooks)
export default function Component (Ctor, data, parent, children) {
if (process.env.NODE_ENV !== 'production' &&
@ -21,6 +63,7 @@ export default function Component (Ctor, data, parent, children) {
warn(`Invalid Component definition: ${Ctor}`, parent)
return
}
// async component
if (!Ctor.cid) {
if (Ctor.resolved) {
@ -34,64 +77,17 @@ export default function Component (Ctor, data, parent, children) {
return
}
}
// merge hooks on the placeholder node itself
const hook = { init, insert, prepatch }
if (data.hook) {
for (let key in data.hook) {
let existing = hook[key]
let fromParent = data.hook[key]
hook[key] = existing ? mergeHook(existing, fromParent) : fromParent
}
}
// merge component management hooks onto the placeholder node
mergeHooks(data)
// extract props
const propsData = extractProps(data, Ctor)
// return a placeholder vnode
return {
tag: 'vue-component-' + Ctor.cid,
key: data && data.key,
data: { hook, Ctor, data, parent, children }
}
}
function mergeHook (a, b) {
// since all hooks have at most two args, use fixed args
// to avoid having to use fn.apply().
return (_, __) => {
a(_, __)
b(_, __)
}
}
function init (vnode) {
const data = vnode.data
const child = new data.Ctor({
parent: data.parent,
_parentVnode: vnode,
_renderData: data.data,
_renderChildren: data.children
})
// the child sets the parent vnode's elm when mounted
// and when updated.
child.$mount()
vnode.child = child
}
function insert (vnode) {
callHook(vnode.child, 'ready')
}
function prepatch (oldVnode, vnode) {
const old = oldVnode.data
const cur = vnode.data
if (cur.Ctor !== old.Ctor) {
// component changed, teardown and create new
// TODO: keep-alive?
oldVnode.child.$destroy()
init(vnode)
} else {
vnode.child = oldVnode.child
// try re-render child. the child may optimize it
// and just does nothing.
vnode.child._updateFromParent(cur.data, cur.children, vnode.key)
}
const vnode = VNode('vue-component-' + Ctor.cid, data)
vnode.componentOptions = { Ctor, propsData, parent, children }
return vnode
}
function resolveAsyncComponent (factory, cb) {
@ -122,3 +118,58 @@ function resolveAsyncComponent (factory, cb) {
})
}
}
function extractProps (data, Ctor) {
// we are only extrating raw values here.
// validation and default values are handled in the child
// component itself.
const propOptions = Ctor.options.props
if (!propOptions) {
return
}
const res = {}
const attrs = data.attrs
const props = data.props
if (!attrs && !props) {
return res
}
for (let key in propOptions) {
let altKey = hyphenate(key)
if (attrs && hasOwn(attrs, key)) {
res[key] = attrs[key]
delete attrs[key]
} else if (attrs && hasOwn(attrs, altKey)) {
res[key] = attrs[altKey]
delete attrs[altKey]
} else if (props && hasOwn(props, key)) {
res[key] = props[key]
delete props[key]
} else if (props && hasOwn(props, altKey)) {
res[key] = props[altKey]
delete props[altKey]
}
}
return res
}
function mergeHooks (data) {
if (data.hook) {
for (let i = 0; i < hooksToMerge.length; i++) {
let key = hooksToMerge[i]
let fromParent = data.hook[key]
let ours = hooks[key]
data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
}
} else {
data.hook = hooks
}
}
function mergeHook (a, b) {
// since all hooks have at most two args, use fixed args
// to avoid having to use fn.apply().
return (_, __) => {
a(_, __)
b(_, __)
}
}