mirror of
https://github.com/vuejs/vue.git
synced 2024-11-22 04:39:46 +00:00
wip
This commit is contained in:
parent
83210dfd5c
commit
1b9343f7e0
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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(_, __)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user