Parcourir la source

annotation for vdom (wip)

Evan You il y a 10 ans
Parent
commit
45d96c7dbc

+ 1 - 0
.eslintignore

@@ -1 +1,2 @@
 flow
+dist

+ 5 - 28
flow/declarations.js → flow/component.js

@@ -38,7 +38,7 @@ declare interface Component {
   $createElement: (
     tag?: string | Component,
     data?: Object,
-    children?: Array<?VNode> | string,
+    children?: VNodeChildren,
     namespace?: string
   ) => VNode;
 
@@ -61,10 +61,10 @@ declare interface Component {
   _mount: () => Component;
   _update: (vnode: VNode) => void;
   _updateFromParent: (
-    propsData?: Object,
-    listeners?: { [key: string]: Function | Array<Function> },
+    propsData: ?Object,
+    listeners: ?{ [key: string]: Function | Array<Function> },
     parentVnode: VNode,
-    renderChildren: Array<VNode> | () => Array<VNode>
+    renderChildren: ?VNodeChildren
   ) => void;
   // rendering
   _render: () => VNode;
@@ -72,7 +72,7 @@ declare interface Component {
   __h__: (
     tag?: string | Component | Object,
     data?: Object,
-    children?: Array<?VNode> | string,
+    children?: VNodeChildren,
     namespace?: string
   ) => VNode;
   __toString__: (value: any) => string;
@@ -91,26 +91,3 @@ declare interface Component {
   // allow dynamic method registration
   [key: string]: any
 }
-
-declare interface GlobalAPI {
-  cid: number;
-  options: Object;
-  config: Config;
-  util: Object;
-
-  extend: (options: Object) => Function;
-  set: (obj: Object, key: string, value: any) => void;
-  delete: (obj: Object, key: string) => void;
-  nextTick: (fn: Function, context?: Object) => void;
-  use: (plugin: Function | Object) => void;
-  mixin: (mixin: Object) => void;
-  compile: (template: string) => { render: Function, staticRenderFns: Array<Function> };
-
-  directive: (id: string, def?: Function | Object) => Function | Object | void;
-  component: (id: string, def?: Class<Component> | Object) => Class<Component>;
-  transition: (id: string, def?: Object) => Object | void;
-  filter: (id: string, def?: Function) => Function | void;
-
-  // allow dynamic method registration
-  [key: string]: any
-}

+ 22 - 0
flow/global-api.js

@@ -0,0 +1,22 @@
+declare interface GlobalAPI {
+  cid: number;
+  options: Object;
+  config: Config;
+  util: Object;
+
+  extend: (options: Object) => Function;
+  set: (obj: Object, key: string, value: any) => void;
+  delete: (obj: Object, key: string) => void;
+  nextTick: (fn: Function, context?: Object) => void;
+  use: (plugin: Function | Object) => void;
+  mixin: (mixin: Object) => void;
+  compile: (template: string) => { render: Function, staticRenderFns: Array<Function> };
+
+  directive: (id: string, def?: Function | Object) => Function | Object | void;
+  component: (id: string, def?: Class<Component> | Object) => Class<Component>;
+  transition: (id: string, def?: Object) => Object | void;
+  filter: (id: string, def?: Function) => Function | void;
+
+  // allow dynamic method registration
+  [key: string]: any
+}

+ 3 - 0
flow/options.js

@@ -0,0 +1,3 @@
+declare type ComponentOptions = {
+
+}

+ 43 - 0
flow/vnode.js

@@ -0,0 +1,43 @@
+declare type VNodeChildren = Array<any> | () => Array<any> | string
+
+declare type VNodeComponentOptions = {
+  Ctor: Class<Component>,
+  propsData: ?Object,
+  listeners: ?Object,
+  parent: Component,
+  children: ?VNodeChildren
+}
+
+declare interface MountedComponentVNode {
+  componentOptions: VNodeComponentOptions;
+  child: Component;
+}
+
+declare interface VNodeData {
+  pre?: true;
+  key?: string | number;
+  slot?: string;
+  staticClass?: string;
+  class?: any;
+  style?: Array<Object> | Object;
+  show?: true;
+  props?: { [key: string]: any };
+  attrs?: { [key: string]: string };
+  staticAttrs?: { [key: string]: string };
+  hook?: { [key: string]: Function };
+  on?: { [key: string]: Function | Array<Function> };
+  transition?: {
+    definition: String | Object,
+    appear: boolean
+  };
+  inlineTemplate?: {
+    render: Function,
+    staticRenderFns: Array<Function>
+  };
+  directives?: Array<{
+    name: string,
+    value?: any,
+    arg?: string,
+    modifiers?: { [key: string]: boolean }
+  }>;
+}

+ 3 - 3
src/core/instance/lifecycle.js

@@ -79,10 +79,10 @@ export function lifecycleMixin (Vue: Class<Component>) {
   }
 
   Vue.prototype._updateFromParent = function (
-    propsData?: Object,
-    listeners?: Object,
+    propsData: ?Object,
+    listeners: ?Object,
     parentVnode: VNode,
-    renderChildren: Array<VNode> | () => Array<VNode>
+    renderChildren: ?VNodeChildren
   ) {
     const vm: Component = this
     vm.$options._parentVnode = parentVnode

+ 8 - 5
src/core/instance/render.js

@@ -3,7 +3,7 @@
 import type VNode from '../vdom/vnode'
 import createElement from '../vdom/create-element'
 import { emptyVNode } from '../vdom/vnode'
-import { flatten } from '../vdom/helpers'
+import { normalizeChildren } from '../vdom/helpers'
 import { bind, remove, isObject, renderString } from 'shared/util'
 import { resolveAsset, nextTick } from '../util/index'
 
@@ -32,12 +32,12 @@ export function renderMixin (Vue: Class<Component>) {
 
   Vue.prototype._render = function (): VNode {
     const vm: Component = this
+    const prev = renderState.activeInstance
+    renderState.activeInstance = vm
     if (!vm._isMounted) {
       // render static sub-trees for once on initial render
       renderStaticTrees(vm)
     }
-    const prev = renderState.activeInstance
-    renderState.activeInstance = vm
     const { render, _renderChildren, _parentVnode } = vm.$options
     // resolve slots. becaues slots are rendered in parent scope,
     // we set the activeInstance to parent.
@@ -131,9 +131,12 @@ function renderStaticTrees (vm: Component) {
   }
 }
 
-function resolveSlots (vm: Component, renderChildren: () => Array<?VNode> | void) {
+function resolveSlots (
+  vm: Component,
+  renderChildren: Array<any> | () => Array<any> | string
+) {
   if (renderChildren) {
-    const children = flatten(renderChildren())
+    const children = normalizeChildren(renderChildren)
     const slots = {}
     const defaultSlot = []
     let i = children.length

+ 51 - 21
src/core/vdom/create-component.js

@@ -1,3 +1,5 @@
+/* @flow */
+
 import Vue from '../instance/index'
 import VNode from './vnode'
 import { callHook } from '../instance/lifecycle'
@@ -6,7 +8,13 @@ import { warn, isObject, hasOwn, hyphenate } from '../util/index'
 const hooks = { init, prepatch, insert, destroy }
 const hooksToMerge = Object.keys(hooks)
 
-export function createComponent (Ctor, data, parent, children, context) {
+export function createComponent (
+  Ctor: Class<Component> | Function | Object | void,
+  data: VNodeData | void,
+  parent: Component,
+  context: Component,
+  children: ?VNodeChildren
+): VNode | void {
   if (process.env.NODE_ENV !== 'production' &&
     children && typeof children !== 'function') {
     warn(
@@ -21,8 +29,10 @@ export function createComponent (Ctor, data, parent, children, context) {
   if (isObject(Ctor)) {
     Ctor = Vue.extend(Ctor)
   }
-  if (process.env.NODE_ENV !== 'production' && typeof Ctor !== 'function') {
-    warn(`Invalid Component definition: ${Ctor}`, parent)
+  if (typeof Ctor !== 'function') {
+    if (process.env.NODE_ENV !== 'production') {
+      warn(`Invalid Component definition: ${Ctor}`, parent)
+    }
     return
   }
 
@@ -57,22 +67,30 @@ export function createComponent (Ctor, data, parent, children, context) {
   // child component listeners instead of DOM listeners
   const listeners = data.on
   if (listeners) {
-    data.on = null
+    delete data.on
   }
 
   // return a placeholder vnode
   const name = Ctor.options.name ? ('-' + Ctor.options.name) : ''
   const vnode = new VNode(
-    `vue-component-${Ctor.cid}${name}`, data,
-    undefined, undefined, undefined, undefined, context
+    `vue-component-${Ctor.cid}${name}`,
+    data, undefined, undefined, undefined, undefined, context,
+    { Ctor, propsData, listeners, parent, children }
   )
-  vnode.componentOptions = { Ctor, propsData, listeners, parent, children }
   return vnode
 }
 
-export function createComponentInstanceForVnode (vnode) {
-  const { Ctor, propsData, listeners, parent, children } = vnode.componentOptions
-  const options = {
+export function createComponentInstanceForVnode (vnode: VNode): Component {
+  const { Ctor, propsData, listeners, parent, children } = vnode.componentOptions || {}
+  const options: {
+    parent: Component,
+    propsData: ?Object,
+    _parentVnode: VNode,
+    _parentListeners: ?Object,
+    _renderChildren: ?VNodeChildren,
+    render?: Function,
+    staticRenderFns?: Array<Function>
+  } = {
     parent,
     propsData,
     _parentVnode: vnode,
@@ -80,7 +98,7 @@ export function createComponentInstanceForVnode (vnode) {
     _renderChildren: children
   }
   // check inline-template render functions
-  const inlineTemplate = vnode.data.inlineTemplate
+  const inlineTemplate = vnode.data && vnode.data.inlineTemplate
   if (inlineTemplate) {
     options.render = inlineTemplate.render
     options.staticRenderFns = inlineTemplate.staticRenderFns
@@ -88,12 +106,15 @@ export function createComponentInstanceForVnode (vnode) {
   return new Ctor(options)
 }
 
-function init (vnode) {
+function init (vnode: VNode) {
   const child = vnode.child = createComponentInstanceForVnode(vnode)
   child.$mount()
 }
 
-function prepatch (oldVnode, vnode) {
+function prepatch (
+  oldVnode: MountedComponentVNode,
+  vnode: MountedComponentVNode
+) {
   const { listeners, propsData, children } = vnode.componentOptions
   vnode.child = oldVnode.child
   vnode.child._updateFromParent(
@@ -104,15 +125,18 @@ function prepatch (oldVnode, vnode) {
   )
 }
 
-function insert (vnode) {
+function insert (vnode: MountedComponentVNode) {
   callHook(vnode.child, 'mounted')
 }
 
-function destroy (vnode) {
+function destroy (vnode: MountedComponentVNode) {
   vnode.child.$destroy()
 }
 
-function resolveAsyncComponent (factory, cb) {
+function resolveAsyncComponent (
+  factory: Function,
+  cb: Function
+): Class<Component> | void {
   if (factory.resolved) {
     return factory.resolved
   } else if (factory.requested) {
@@ -124,7 +148,7 @@ function resolveAsyncComponent (factory, cb) {
     let sync = true
     factory(
       // resolve
-      res => {
+      (res: Object | Class<Component>) => {
         if (isObject(res)) {
           res = Vue.extend(res)
         }
@@ -152,7 +176,7 @@ function resolveAsyncComponent (factory, cb) {
   }
 }
 
-function extractProps (data, Ctor) {
+function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
   // we are only extrating raw values here.
   // validation and default values are handled in the child
   // component itself.
@@ -176,7 +200,12 @@ function extractProps (data, Ctor) {
   return res
 }
 
-function checkProp (res, hash, key, altKey) {
+function checkProp (
+  res: Object,
+  hash: ?Object,
+  key: string,
+  altKey: string
+): boolean {
   if (hash) {
     if (hasOwn(hash, key)) {
       res[key] = hash[key]
@@ -188,9 +217,10 @@ function checkProp (res, hash, key, altKey) {
       return true
     }
   }
+  return false
 }
 
-function mergeHooks (data) {
+function mergeHooks (data: VNodeData) {
   if (data.hook) {
     for (let i = 0; i < hooksToMerge.length; i++) {
       const key = hooksToMerge[i]
@@ -203,7 +233,7 @@ function mergeHooks (data) {
   }
 }
 
-function mergeHook (a, b) {
+function mergeHook (a: Function, b: Function): Function {
   // since all hooks have at most two args, use fixed args
   // to avoid having to use fn.apply().
   return (_, __) => {

+ 22 - 8
src/core/vdom/create-element.js

@@ -1,13 +1,27 @@
+/* @flow */
+
 import VNode, { emptyVNode } from './vnode'
 import config from '../config'
 import { createComponent } from './create-component'
-import { flatten } from './helpers'
+import { normalizeChildren } from './helpers'
 import { renderState } from '../instance/render'
 import { warn, resolveAsset } from '../util/index'
 
-export default function createElement (tag, data, children, namespace) {
-  const context = this
-  const parent = renderState.activeInstance
+export default function createElement (
+  tag?: string | Class<Component> | Function | Object,
+  data?: VNodeData,
+  children?: VNodeChildren,
+  namespace?: string
+): VNode | void {
+  const context: Component = this
+  const parent: Component | null = renderState.activeInstance
+  if (!parent) {
+    process.env.NODE_ENV !== 'production' && warn(
+      'createElement cannot be called outside of component ' +
+      'render functions.'
+    )
+    return
+  }
   if (!tag) {
     // in case of component :is set to falsy value
     return emptyVNode
@@ -16,11 +30,11 @@ export default function createElement (tag, data, children, namespace) {
     let Ctor
     if (config.isReservedTag(tag)) {
       return new VNode(
-        tag, data, flatten(children),
+        tag, data, normalizeChildren(children),
         undefined, undefined, namespace, context
       )
     } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
-      return createComponent(Ctor, data, parent, children, context)
+      return createComponent(Ctor, data, parent, context, children)
     } else {
       if (process.env.NODE_ENV !== 'production') {
         if (!namespace && config.isUnknownElement(tag)) {
@@ -32,11 +46,11 @@ export default function createElement (tag, data, children, namespace) {
         }
       }
       return new VNode(
-        tag, data, flatten(children && children()),
+        tag, data, normalizeChildren(children),
         undefined, undefined, namespace, context
       )
     }
   } else {
-    return createComponent(tag, data, parent, children, context)
+    return createComponent(tag, data, parent, context, children)
   }
 }

+ 13 - 7
src/core/vdom/helpers.js

@@ -1,19 +1,24 @@
+/* @flow */
+
 import { isPrimitive } from '../util/index'
 import VNode from './vnode'
 
 const whitespace = new VNode(undefined, undefined, undefined, ' ')
 
-export function flatten (children) {
+export function normalizeChildren (children: any): Array<VNode> {
   if (typeof children === 'string') {
     return [new VNode(undefined, undefined, undefined, children)]
   }
+  if (typeof children === 'function') {
+    children = children()
+  }
   if (Array.isArray(children)) {
     const res = []
     for (let i = 0, l = children.length; i < l; i++) {
       const c = children[i]
-      // flatten nested
+      //  nested
       if (Array.isArray(c)) {
-        res.push.apply(res, flatten(c))
+        res.push.apply(res, normalizeChildren(c))
       } else if (isPrimitive(c)) {
         // optimize whitespace
         if (c === ' ') {
@@ -22,15 +27,16 @@ export function flatten (children) {
           // convert primitive to vnode
           res.push(new VNode(undefined, undefined, undefined, c))
         }
-      } else if (c) {
+      } else if (c instanceof VNode) {
         res.push(c)
       }
     }
     return res
   }
+  return []
 }
 
-export function updateListeners (on, oldOn, add) {
+export function updateListeners (on: Object, oldOn: Object, add: Function) {
   let name, cur, old, event, capture
   for (name in on) {
     cur = on[name]
@@ -56,7 +62,7 @@ export function updateListeners (on, oldOn, add) {
   }
 }
 
-function arrInvoker (arr) {
+function arrInvoker (arr: Array<Function>): Function {
   return function (ev) {
     const single = arguments.length === 1
     for (let i = 0; i < arr.length; i++) {
@@ -65,7 +71,7 @@ function arrInvoker (arr) {
   }
 }
 
-function fnInvoker (o) {
+function fnInvoker (o: { fn: Function }): Function {
   return function (ev) {
     const single = arguments.length === 1
     single ? o.fn(ev) : o.fn.apply(null, arguments)

+ 18 - 5
src/core/vdom/vnode.js

@@ -1,16 +1,27 @@
-import type Vue from 'core/instance/index'
+/* @flow */
 
 export default class VNode {
   tag: string | void;
-  data: Object | void;
+  data: VNodeData | void;
   children: Array<VNode> | void;
   text: string | void;
-  elm: Element | void;
+  elm: Node | void;
   ns: string | void;
-  context: Vue | void;
+  context: Component | void;
   key: string | number | void;
+  componentOptions: VNodeComponentOptions | void;
+  child: Component | void;
 
-  constructor (tag, data, children, text, elm, ns, context) {
+  constructor (
+    tag?: string,
+    data?: VNodeData,
+    children?: Array<VNode>,
+    text?: string,
+    elm?: Node,
+    ns?: string,
+    context?: Component,
+    componentOptions?: VNodeComponentOptions
+  ) {
     this.tag = tag
     this.data = data
     this.children = children
@@ -19,6 +30,8 @@ export default class VNode {
     this.ns = ns
     this.context = context
     this.key = data && data.key
+    this.componentOptions = componentOptions
+    this.child = undefined
   }
 }
 

+ 17 - 3
src/server/render.js

@@ -8,7 +8,12 @@ export function createRenderFunction (
   directives: Object,
   isUnaryTag: Function
 ) {
-  function renderNode (node: VNode, write: Function, next: Function, isRoot: boolean) {
+  function renderNode (
+    node: VNode,
+    write: Function,
+    next: Function,
+    isRoot: boolean
+  ) {
     if (node.componentOptions) {
       const child = createComponentInstanceForVnode(node)
       renderNode(child._render(), write, next, isRoot)
@@ -21,7 +26,12 @@ export function createRenderFunction (
     }
   }
 
-  function renderElement (el: VNode, write: Function, next: Function, isRoot: boolean) {
+  function renderElement (
+    el: VNode,
+    write: Function,
+    next: Function,
+    isRoot: boolean
+  ) {
     if (isRoot) {
       if (!el.data) el.data = {}
       if (!el.data.attrs) el.data.attrs = {}
@@ -81,7 +91,11 @@ export function createRenderFunction (
     return markup + '>'
   }
 
-  return function render (component: Component, write: Function, done: Function) {
+  return function render (
+    component: Component,
+    write: (text: string, next: Function) => void,
+    done: Function
+  ) {
     renderNode(component._render(), write, done, true)
   }
 }

+ 2 - 2
test/unit/features/directives/once.spec.js

@@ -28,7 +28,7 @@ describe('Directive v-once', () => {
         }
       }
     }).$mount()
-    expect(vm.$children.length).toBe(0)
+    expect(vm.$children.length).toBe(1)
     expect(vm.$el.innerHTML)
       .toBe('<span>hello</span><div>hello</div>')
     vm.a = 'world'
@@ -53,7 +53,7 @@ describe('Directive v-once', () => {
         }
       }
     }).$mount()
-    expect(vm.$children.length).toBe(0)
+    expect(vm.$children.length).toBe(1)
     expect(vm.$el.innerHTML)
       .toBe('<span>hello</span><div>hello</div>')
     vm.a = 'world'