Просмотр исходного кода

wip: refactor children resolution (remove thunk mechanism)

Evan You 9 лет назад
Родитель
Сommit
b5b963f51d

+ 1 - 1
flow/vnode.js

@@ -1,4 +1,4 @@
-declare type VNodeChildren = Array<any> | () => Array<any> | string
+declare type VNodeChildren = Array<any> | string
 
 declare type VNodeComponentOptions = {
   Ctor: Class<Component>;

+ 1 - 8
src/compiler/codegen/index.js

@@ -3,14 +3,12 @@
 import { genHandlers } from './events'
 import { baseWarn, pluckModuleFunction } from '../helpers'
 import baseDirectives from '../directives/index'
-import { no } from 'shared/util'
 
 // configurable state
 let warn
 let transforms
 let dataGenFns
 let platformDirectives
-let isPlatformReservedTag
 let staticRenderFns
 let currentOptions
 
@@ -29,7 +27,6 @@ export function generate (
   transforms = pluckModuleFunction(options.modules, 'transformCode')
   dataGenFns = pluckModuleFunction(options.modules, 'genData')
   platformDirectives = options.directives || {}
-  isPlatformReservedTag = options.isReservedTag || no
   const code = ast ? genElement(ast) : '_h("div")'
   // console.log(code)
   staticRenderFns = prevStaticRenderFns
@@ -60,11 +57,7 @@ function genElement (el: ASTElement): string {
       code = genComponent(el)
     } else {
       const data = genData(el)
-      // if the element is potentially a component,
-      // wrap its children as a thunk.
-      const children = !el.inlineTemplate
-        ? genChildren(el, !isPlatformReservedTag(el.tag) /* asThunk */)
-        : null
+      const children = el.inlineTemplate ? null : genChildren(el)
       code = `_h('${el.tag}'${
         data ? `,${data}` : '' // data
       }${

+ 10 - 0
src/core/instance/lifecycle.js

@@ -5,6 +5,8 @@ import { emptyVNode } from '../vdom/vnode'
 import { observerState } from '../observer/index'
 import { warn, validateProp, remove, noop } from '../util/index'
 
+export let activeInstance: any = null
+
 export function initLifecycle (vm: Component) {
   const options = vm.$options
 
@@ -76,6 +78,8 @@ export function lifecycleMixin (Vue: Class<Component>) {
       callHook(vm, 'beforeUpdate')
     }
     const prevEl = vm.$el
+    const prevActiveInstance = activeInstance
+    activeInstance = vm
     if (!vm._vnode) {
       // Vue.prototype.__patch__ is injected in entry points
       // based on the rendering backend used.
@@ -83,6 +87,7 @@ export function lifecycleMixin (Vue: Class<Component>) {
     } else {
       vm.$el = vm.__patch__(vm._vnode, vnode)
     }
+    activeInstance = prevActiveInstance
     vm._vnode = vnode
     // update __vue__ reference
     if (prevEl) {
@@ -107,6 +112,7 @@ export function lifecycleMixin (Vue: Class<Component>) {
     renderChildren: ?VNodeChildren
   ) {
     const vm: Component = this
+    const hasChildren = !!(vm.$options._renderChildren || renderChildren)
     vm.$options._parentVnode = parentVnode
     vm.$options._renderChildren = renderChildren
     // update props
@@ -131,6 +137,10 @@ export function lifecycleMixin (Vue: Class<Component>) {
       vm.$options._parentListeners = listeners
       vm._updateListeners(listeners, oldListeners)
     }
+    // force udpate if has children
+    if (hasChildren) {
+      vm.$forceUpdate()
+    }
   }
 
   Vue.prototype.$forceUpdate = function () {

+ 1 - 1
src/core/instance/render.js

@@ -184,7 +184,7 @@ export function renderMixin (Vue: Class<Component>) {
   }
 }
 
-export function resolveSlots (renderChildren: any): Object {
+export function resolveSlots (renderChildren: ?VNodeChildren): Object {
   const slots = {}
   if (!renderChildren) {
     return slots

+ 1 - 15
src/core/vdom/create-component.js

@@ -19,16 +19,6 @@ export function createComponent (
   children?: VNodeChildren,
   tag?: string
 ): VNode | void {
-  // ensure children is a thunk
-  if (process.env.NODE_ENV !== 'production' &&
-    children && typeof children !== 'function') {
-    warn(
-      'A component\'s children should be a function that returns the ' +
-      'children array. This allows the component to track the children ' +
-      'dependencies and optimizes re-rendering.'
-    )
-  }
-
   if (!Ctor) {
     return
   }
@@ -84,7 +74,7 @@ export function createComponent (
         props,
         parent,
         data,
-        children: () => normalizeChildren(children),
+        children: normalizeChildren(children),
         slots: () => resolveSlots(children)
       }
     )
@@ -156,10 +146,6 @@ function prepatch (
     vnode, // new parent vnode
     options.children // new children
   )
-  // always update abstract components.
-  if (child.$options.abstract) {
-    child.$forceUpdate()
-  }
 }
 
 function insert (vnode: MountedComponentVNode) {

+ 0 - 10
src/core/vdom/helpers.js

@@ -7,16 +7,6 @@ export function normalizeChildren (
   children: any,
   ns: string | void
 ): Array<VNode> | void {
-  // Invoke children thunks. Components always receive their children
-  // as thunks so that they can perform the actual render inside their
-  // own dependency collection cycle. Also, since JSX automatically
-  // wraps component children in a thunk, we handle nested thunks to
-  // prevent situations such as <MyComponent>{ children }</MyComponent>
-  // from failing when it produces a double thunk.
-  while (typeof children === 'function') {
-    children = children()
-  }
-
   if (isPrimitive(children)) {
     return [createTextVNode(children)]
   }

+ 3 - 2
src/core/vdom/patch.js

@@ -10,6 +10,7 @@
 import config from '../config'
 import VNode from './vnode'
 import { isPrimitive, _toString, warn } from '../util/index'
+import { activeInstance } from '../instance/lifecycle'
 
 const emptyData = {}
 const emptyNode = new VNode('', emptyData, [])
@@ -156,10 +157,10 @@ export function createPatchFunction (backend) {
   // of going through the normal attribute patching process.
   function setScope (vnode) {
     let i
-    if (isDef(i = vnode.host) && isDef(i = i.$options._scopeId)) {
+    if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) {
       nodeOps.setAttribute(vnode.elm, i, '')
     }
-    if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) {
+    if (activeInstance !== vnode.context && isDef(i = activeInstance.$options._scopeId)) {
       nodeOps.setAttribute(vnode.elm, i, '')
     }
   }

+ 10 - 1
src/server/render.js

@@ -40,6 +40,9 @@ export function createRenderFunction (
   const get = cache && normalizeAsync(cache, 'get')
   const has = cache && normalizeAsync(cache, 'has')
 
+  // used to track and apply scope ids
+  let activeInstance: any
+
   function renderNode (
     node: VNode,
     write: Function,
@@ -96,7 +99,10 @@ export function createRenderFunction (
     normalizeRender(child)
     const childNode = child._render()
     childNode.parent = node
+    const prevActive = activeInstance
+    activeInstance = child
     renderNode(childNode, write, next, isRoot)
+    activeInstance = prevActive
   }
 
   function renderComponentWithCache (node, write, next, isRoot, cache, key) {
@@ -179,7 +185,9 @@ export function createRenderFunction (
     }
     // attach scoped CSS ID
     let scopeId
-    if (node.host && (scopeId = node.host.$options._scopeId)) {
+    if (activeInstance &&
+        activeInstance !== node.context &&
+        (scopeId = activeInstance.$options._scopeId)) {
       markup += ` ${scopeId}`
     }
     while (node) {
@@ -196,6 +204,7 @@ export function createRenderFunction (
     write: (text: string, next: Function) => void,
     done: Function
   ) {
+    activeInstance = component
     normalizeRender(component)
     renderNode(component._render(), write, done, true)
   }

+ 2 - 2
test/unit/features/options/functional.spec.js

@@ -10,7 +10,7 @@ describe('Options functional', () => {
           functional: true,
           props: ['msg'],
           render (h, { props, children }) {
-            return h('div', null, [props.msg, ' '].concat(children()))
+            return h('div', null, [props.msg, ' '].concat(children))
           }
         }
       }
@@ -54,7 +54,7 @@ describe('Options functional', () => {
           functional: true,
           props: ['field'],
           render (h, { props, children, data: { on } }) {
-            props.child = children()[0]
+            props.child = children[0]
             return h('validate-control', { props, on })
           }
         },

+ 0 - 12
test/unit/features/options/render.spec.js

@@ -36,16 +36,4 @@ describe('Options render', () => {
     new Vue().$mount()
     expect('Failed to mount component: template or render function not defined.').toHaveBeenWarned()
   })
-
-  // Since JSX automatically thunkifies children, this will
-  // prevent <MyComponent>{ children }</MyComponent> from
-  // failing when it produces a double thunk.
-  it('should support nested thunk children', () => {
-    const vm = new Vue({
-      render: h => h('div',
-        () => () => () => ['hello ', h('strong', 'world')]
-      )
-    }).$mount()
-    expect(vm.$el.innerHTML).toBe('hello <strong>world</strong>')
-  })
 })

+ 2 - 14
test/unit/modules/compiler/codegen.spec.js

@@ -2,7 +2,6 @@ import { parse } from 'compiler/parser/index'
 import { optimize } from 'compiler/optimizer'
 import { generate } from 'compiler/codegen'
 import { isObject } from 'shared/util'
-import directives from 'web/compiler/directives/index'
 import { isReservedTag } from 'web/util/index'
 import { baseOptions } from 'web/compiler/index'
 
@@ -260,7 +259,7 @@ describe('codegen', () => {
   it('generate component', () => {
     assertCodegen(
       '<my-component name="mycomponent1" :msg="msg" @notify="onNotify"><div>hi</div></my-component>',
-      `with(this){return _h('my-component',{attrs:{"name":"mycomponent1","msg":msg},on:{"notify":onNotify}},function(){return [_m(0)]})}`,
+      `with(this){return _h('my-component',{attrs:{"name":"mycomponent1","msg":msg},on:{"notify":onNotify}},[_m(0)])}`,
       [`with(this){return _h('div',["hi"])}`]
     )
   })
@@ -268,7 +267,7 @@ describe('codegen', () => {
   it('generate svg component with children', () => {
     assertCodegen(
       '<svg><my-comp><circle :r="10"></circle></my-comp></svg>',
-      `with(this){return _h('svg',[_h('my-comp',function(){return [_h('circle',{attrs:{"r":10}})]})])}`
+      `with(this){return _h('svg',[_h('my-comp',[_h('circle',{attrs:{"r":10}})])])}`
     )
   })
 
@@ -310,16 +309,5 @@ describe('codegen', () => {
       { isReservedTag }
     )
   })
-
-  it('not specified isReservedTag option', () => {
-    // this causes all tags to be treated as components,
-    // thus all children are wrapped in thunks.
-    assertCodegen(
-      '<div><p>hello world</p></div>',
-      `with(this){return _m(0)}`,
-      [`with(this){return _h('div',function(){return [_h('p',function(){return ["hello world"]})]})}`],
-      { directives }
-    )
-  })
 })
 /* eslint-enable quotes */

+ 0 - 18
test/unit/modules/vdom/create-element.spec.js

@@ -121,24 +121,6 @@ describe('create-element', () => {
     expect(vnode.children[2].tag).toBe('br')
   })
 
-  it('warn message when using non-thunk children for component', () => {
-    const vm = new Vue({
-      data: { message: 'hello world' },
-      components: {
-        'my-component': {
-          props: ['msg']
-        }
-      }
-    })
-    const h = bind(createElement, vm)
-    renderState.activeInstance = vm
-    const vnode = h('my-component', { props: { msg: vm.message }}, [h('br'), 'hello world', h('br')])
-    expect(vnode.componentOptions.children[0].tag).toBe('br')
-    expect(vnode.componentOptions.children[1]).toBe('hello world')
-    expect(vnode.componentOptions.children[2].tag).toBe('br')
-    expect('A component\'s children should be a function').toHaveBeenWarned()
-  })
-
   it('render svg elements with correct namespace', () => {
     const vm = new Vue({})
     const h = bind(createElement, vm)