component wip

This commit is contained in:
Evan You 2016-04-14 05:21:48 -04:00
parent 102b6f12ce
commit f2c8880041
12 changed files with 170 additions and 56 deletions

View File

@ -7,9 +7,9 @@ export function generate (ast) {
}
function genElement (el) {
if (el['for']) {
if (el.for) {
return genFor(el)
} else if (el['if']) {
} else if (el.if) {
return genIf(el)
} else if (el.tag === 'template') {
return genChildren(el)
@ -21,15 +21,15 @@ function genElement (el) {
}
function genIf (el) {
const exp = el['if']
el['if'] = false // avoid recursion
const exp = el.if
el.if = false // avoid recursion
return `(${exp}) ? ${genElement(el)} : null`
}
function genFor (el) {
const exp = el['for']
const exp = el.for
const alias = el.alias
el['for'] = false // avoid recursion
el.for = false // avoid recursion
return `(${exp}) && (${exp}).map(function (${alias}, $index) {return ${genElement(el)}})`
}

View File

@ -134,7 +134,7 @@ function processFor (el) {
console.error(`Invalid v-for expression: ${exp}`)
}
el.alias = inMatch[1].trim()
el['for'] = inMatch[2].trim()
el.for = inMatch[2].trim()
if ((exp = getAndRemoveAttr(el, 'track-by'))) {
el.key = exp === '$index'
? exp
@ -146,7 +146,7 @@ function processFor (el) {
function processIf (el) {
let exp = getAndRemoveAttr(el, 'v-if')
if (exp) {
el['if'] = exp
el.if = exp
}
}

View File

@ -1,6 +1,6 @@
import config from './config'
import * as util from './util/index'
import h from './vdom/h'
import { createElement } from './vdom/index'
import {
set,
del,
@ -15,7 +15,7 @@ import {
} from './util/index'
export function initGlobalAPI (Vue) {
Vue.h = h
Vue.h = Vue.createElement = createElement
Vue.config = config
Vue.util = util
Vue.set = set
@ -98,12 +98,9 @@ export function initGlobalAPI (Vue) {
*/
function createClass (name) {
/* eslint-disable no-new-func */
return new Function(
'return function ' + classify(name) +
' (options) { this._init(options) }'
`return function ${classify(name)} (options) { this._init(options) }`
)()
/* eslint-enable no-new-func */
}
/**

View File

@ -7,6 +7,10 @@ import { nextTick, mergeOptions } from '../util/index'
let uid = 0
export default function Vue (options) {
this._init(options)
}
Vue.prototype._init = function (options) {
// a uid
this._uid = uid++
// a flag to avoid this being observed
@ -32,11 +36,11 @@ export default function Vue (options) {
initRender(this)
}
Vue.prototype.$nextTick = function (fn) {
nextTick(fn, this)
}
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
Vue.prototype.$nextTick = function (fn) {
nextTick(fn, this)
}

View File

@ -1,10 +1,12 @@
import Watcher from '../observer/watcher'
import { query, resolveAsset } from '../util/index'
import { h, patch } from '../vdom/index'
import { query, resolveAsset, hyphenate } from '../util/index'
import { createElement, patch } from '../vdom/index'
import { callHook } from './lifecycle'
export function initRender (vm) {
vm._vnode = null
vm._mounted = false
// TODO: handle _renderData and _renderChildren
const el = vm.$options.el
if (el) {
vm.$mount(el)
@ -13,33 +15,55 @@ export function initRender (vm) {
export function renderMixin (Vue) {
// shorthands used in render functions
Vue.prototype.__h__ = h
Vue.prototype.__h__ = createElement
Vue.prototype.__d__ = function (id) {
return resolveAsset(this.$options, 'directives', id, true)
}
Vue.prototype._update = function (vtree) {
Vue.prototype._update = function (vnode) {
callHook(this, 'beforeUpdate')
if (!this._tree) {
this.$el = patch(this.$el, vtree)
if (!this._vnode) {
this.$el = patch(this.$el, vnode)
} else {
this.$el = patch(this._tree, vtree)
this.$el = patch(this._vnode, vnode)
}
this._tree = vtree
this._vnode = vnode
callHook(this, 'updated')
}
Vue.prototype._tryUpdate = function (data, children) {
if (children) {
// TODO: handle content slots
this.$forceUpdate()
return
}
// check props
const props = this.$options.props
if (props && data.attrs) {
for (let key in props) {
let oldVal = this[key]
let newVal = data.attrs[key] || data.attrs[hyphenate(key)]
if (oldVal !== newVal) {
this.$forceUpdate()
}
}
}
}
Vue.prototype.$mount = function (el) {
callHook(this, 'beforeMount')
this.$el = el ? query(el) : document.createElement('div')
this.$el.innerHTML = ''
this.$el = el && query(el)
if (this.$el) {
this.$el.innerHTML = ''
}
this._watcher = new Watcher(this, this.$options.render, this._update)
this._update(this._watcher.value)
callHook(this, 'mounted')
this._mounted = true
return this
}
Vue.prototype.$forceUpdate = function () {
this._watcher.run()
this._watcher.update()
}
}

View File

@ -12,12 +12,17 @@ import {
export function initState (vm) {
vm._watchers = []
initProps(vm)
initData(vm)
initComputed(vm)
initMethods(vm)
initWatch(vm)
}
function initProps (vm) {
// TODO
}
function initData (vm) {
var data = vm.$options.data
data = vm._data = typeof data === 'function'

View File

@ -257,29 +257,30 @@ function guardComponents (options) {
*/
function guardProps (options) {
var props = options.props
var i, val
const res = {}
const props = options.props
let i, val
if (isArray(props)) {
options.props = {}
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
options.props[val] = null
res[camelize(val)] = null
} else if (val.name) {
options.props[val.name] = val
res[camelize(val.name)] = val
}
}
} else if (isPlainObject(props)) {
var keys = Object.keys(props)
const keys = Object.keys(props)
i = keys.length
while (i--) {
val = props[keys[i]]
if (typeof val === 'function') {
props[keys[i]] = { type: val }
}
res[camelize(keys[i])] = typeof val === 'function'
? { type: val }
: val
}
}
options.props = res
}
function guardDirectives (options) {

View File

@ -0,0 +1,45 @@
import Vue from '../instance/index'
export default function Component (Ctor, data, children) {
if (typeof Ctor === 'object') {
Ctor = Vue.extend(Ctor)
}
// return a placeholder vnode
return {
sel: 'component',
data: {
hooks: { init, prepatch, destroy },
Ctor, data, children
}
}
}
function init (vnode) {
const data = vnode.data
const child = new data.Ctor({
_renderData: data.data,
_renderChildren: data.children
}).$mount()
data.child = child
data.vnode = child._vnode
}
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?
old.child.$destroy()
init(vnode)
} else {
// try re-render child. the child may optimize it
// and just does nothing.
old.child._tryUpdate(cur.data, cur.children)
cur.vnode = old.child._vnode
}
}
function destroy (vnode) {
vnode.data.childComponent.$destroy()
}

View File

@ -1,7 +1,8 @@
import VNode from './vnode'
import Component from './component'
import { isPrimitive, isArray } from '../util/index'
export default function h (tag, data, children) {
export default function createElement (tag, data, children) {
if (isArray(children)) {
let _children = children
children = []
@ -22,5 +23,9 @@ export default function h (tag, data, children) {
}
}
}
return VNode(tag, data, children, undefined, undefined)
if (typeof tag === 'string') {
return VNode(tag, data, children)
} else {
return Component(tag, data, children)
}
}

View File

@ -5,7 +5,7 @@
*/
import createPatchFunction from './patch'
import h from './h'
import createElement from './create-element'
import classes from './modules/class'
import style from './modules/style'
import props from './modules/props'
@ -22,4 +22,4 @@ const patch = createPatchFunction([
directives
])
export { patch, h }
export { patch, createElement }

View File

@ -2,7 +2,7 @@ import VNode from './vnode'
import * as dom from './dom'
import { isPrimitive } from '../util/index'
const emptyNode = VNode('', {}, [], undefined, undefined)
const emptyNode = VNode('', {}, [])
const hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post']
const svgNS = 'http://www.w3.org/2000/svg'
@ -241,21 +241,25 @@ export default function createPatchFunction (modules, api) {
var insertedVnodeQueue = []
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
if (isUndef(oldVnode.sel)) {
oldVnode = emptyNodeAt(oldVnode)
}
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
elm = oldVnode.elm
parent = api.parentNode(elm)
if (!oldVnode) {
createElm(vnode, insertedVnodeQueue)
} else {
if (isUndef(oldVnode.sel)) {
oldVnode = emptyNodeAt(oldVnode)
}
if (parent !== null) {
api.insertBefore(parent, vnode.elm, api.nextSibling(elm))
removeVnodes(parent, [oldVnode], 0, 0)
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
elm = oldVnode.elm
parent = api.parentNode(elm)
createElm(vnode, insertedVnodeQueue)
if (parent !== null) {
api.insertBefore(parent, vnode.elm, api.nextSibling(elm))
removeVnodes(parent, [oldVnode], 0, 0)
}
}
}

29
src/runtime/vdom/thunk.js Normal file
View File

@ -0,0 +1,29 @@
import createElement from './create-element'
function init (thunk) {
var i, cur = thunk.data
cur.vnode = cur.fn.apply(undefined, cur.args)
}
function prepatch (oldThunk, thunk) {
var i, old = oldThunk.data, cur = thunk.data
var oldArgs = old.args, args = cur.args
cur.vnode = old.vnode
if (old.fn !== cur.fn || oldArgs.length !== args.length) {
cur.vnode = cur.fn.apply(undefined, args)
return
}
for (i = 0; i < args.length; ++i) {
if (oldArgs[i] !== args[i]) {
cur.vnode = cur.fn.apply(undefined, args)
return
}
}
}
export default function thunk (name, fn, args) {
return createElement('thunk' + name, {
hook: { init, prepatch },
fn, args
})
}