Procházet zdrojové kódy

wip: fix $parent after children change

Evan You před 9 roky
rodič
revize
fe801b16e4

+ 0 - 1
flow/vnode.js

@@ -4,7 +4,6 @@ declare type VNodeComponentOptions = {
   Ctor: Class<Component>;
   propsData: ?Object;
   listeners: ?Object;
-  parent: Component;
   children: ?VNodeChildren;
   tag?: string;
 }

+ 0 - 13
src/core/instance/render.js

@@ -10,12 +10,6 @@ import {
 
 import { createElement } from '../vdom/create-element'
 
-export const renderState: {
-  activeInstance: ?Component
-} = {
-  activeInstance: null
-}
-
 export function initRender (vm: Component) {
   vm.$vnode = null // the placeholder node in parent tree
   vm._vnode = null // the root of the child tree
@@ -36,11 +30,6 @@ export function renderMixin (Vue: Class<Component>) {
 
   Vue.prototype._render = function (): VNode {
     const vm: Component = this
-
-    // set current active instance
-    const prev = renderState.activeInstance
-    renderState.activeInstance = vm
-
     const {
       render,
       staticRenderFns,
@@ -91,8 +80,6 @@ export function renderMixin (Vue: Class<Component>) {
     }
     // set parent
     vnode.parent = _parentVnode
-    // restore render state
-    renderState.activeInstance = prev
     return vnode
   }
 

+ 12 - 14
src/core/vdom/create-component.js

@@ -3,7 +3,7 @@
 import Vue from '../instance/index'
 import VNode from './vnode'
 import { normalizeChildren } from './helpers'
-import { callHook } from '../instance/lifecycle'
+import { activeInstance, callHook } from '../instance/lifecycle'
 import { resolveSlots } from '../instance/render'
 import { warn, isObject, hasOwn, hyphenate, validateProp } from '../util/index'
 
@@ -13,9 +13,7 @@ const hooksToMerge = Object.keys(hooks)
 export function createComponent (
   Ctor: Class<Component> | Function | Object | void,
   data?: VNodeData,
-  parent: Component,
   context: Component,
-  host: ?Component,
   children?: VNodeChildren,
   tag?: string
 ): VNode | void {
@@ -29,7 +27,7 @@ export function createComponent (
 
   if (typeof Ctor !== 'function') {
     if (process.env.NODE_ENV !== 'production') {
-      warn(`Invalid Component definition: ${Ctor}`, parent)
+      warn(`Invalid Component definition: ${Ctor}`, context)
     }
     return
   }
@@ -41,9 +39,8 @@ export function createComponent (
     } else {
       Ctor = resolveAsyncComponent(Ctor, () => {
         // it's ok to queue this on every render because
-        // $forceUpdate is buffered. this is only called
-        // if the
-        parent.$forceUpdate()
+        // $forceUpdate is buffered by the scheduler.
+        context.$forceUpdate()
       })
       if (!Ctor) {
         // return nothing if this is indeed an async component
@@ -69,10 +66,10 @@ export function createComponent (
     }
     return Ctor.options.render.call(
       null,
-      parent.$createElement,
+      context.$createElement,
       {
         props,
-        parent,
+        context,
         data,
         children: normalizeChildren(children),
         slots: () => resolveSlots(children)
@@ -99,19 +96,20 @@ export function createComponent (
   const name = Ctor.options.name || tag
   const vnode = new VNode(
     `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
-    data, undefined, undefined, undefined, undefined, context, host,
-    { Ctor, propsData, listeners, parent, tag, children }
+    data, undefined, undefined, undefined, undefined, context,
+    { Ctor, propsData, listeners, tag, children }
   )
   return vnode
 }
 
 export function createComponentInstanceForVnode (
-  vnode: any // we know it's MountedComponentVNode but flow doesn't
+  vnode: any, // we know it's MountedComponentVNode but flow doesn't
+  parent: any // activeInstance in lifecycle state
 ): Component {
   const vnodeComponentOptions = vnode.componentOptions
   const options: InternalComponentOptions = {
     _isComponent: true,
-    parent: vnodeComponentOptions.parent,
+    parent,
     propsData: vnodeComponentOptions.propsData,
     _componentTag: vnodeComponentOptions.tag,
     _parentVnode: vnode,
@@ -129,7 +127,7 @@ export function createComponentInstanceForVnode (
 
 function init (vnode: VNodeWithData, hydrating: boolean) {
   if (!vnode.child) {
-    const child = vnode.child = createComponentInstanceForVnode(vnode)
+    const child = vnode.child = createComponentInstanceForVnode(vnode, activeInstance)
     child.$mount(hydrating ? vnode.elm : undefined, hydrating)
   }
 }

+ 4 - 14
src/core/vdom/create-element.js

@@ -4,7 +4,6 @@ import VNode, { emptyVNode } from './vnode'
 import config from '../config'
 import { createComponent } from './create-component'
 import { normalizeChildren } from './helpers'
-import { renderState } from '../instance/render'
 import { warn, resolveAsset } from '../util/index'
 
 // wrapper function for providing a more flexible interface
@@ -28,15 +27,6 @@ function _createElement (
   data?: VNodeData,
   children?: VNodeChildren | void
 ): VNode | Array<VNode> | void {
-  const parent: ?Component = renderState.activeInstance
-  const host = context !== parent ? parent : undefined
-  if (!parent) {
-    process.env.NODE_ENV !== 'production' && warn(
-      'createElement cannot be called outside of component ' +
-      'render functions.'
-    )
-    return
-  }
   if (data && data.__ob__) {
     process.env.NODE_ENV !== 'production' && warn(
       `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
@@ -56,22 +46,22 @@ function _createElement (
       // platform built-in elements
       return new VNode(
         tag, data, normalizeChildren(children, ns),
-        undefined, undefined, ns, context, host
+        undefined, undefined, ns, context
       )
     } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
       // component
-      return createComponent(Ctor, data, parent, context, host, children, tag)
+      return createComponent(Ctor, data, context, children, tag)
     } else {
       // unknown or unlisted namespaced elements
       // check at runtime because it may get assigned a namespace when its
       // parent normalizes children
       return new VNode(
         tag, data, normalizeChildren(children, ns),
-        undefined, undefined, ns, context, host
+        undefined, undefined, ns, context
       )
     }
   } else {
     // direct component options / constructor
-    return createComponent(tag, data, parent, context, host, children)
+    return createComponent(tag, data, context, children)
   }
 }

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

@@ -160,7 +160,9 @@ export function createPatchFunction (backend) {
     if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) {
       nodeOps.setAttribute(vnode.elm, i, '')
     }
-    if (activeInstance !== vnode.context && isDef(i = activeInstance.$options._scopeId)) {
+    if (isDef(i = activeInstance) &&
+        i !== vnode.context &&
+        isDef(i = i.$options._scopeId)) {
       nodeOps.setAttribute(vnode.elm, i, '')
     }
   }

+ 0 - 3
src/core/vdom/vnode.js

@@ -8,7 +8,6 @@ export default class VNode {
   elm: Node | void;
   ns: string | void;
   context: Component | void; // rendered in this component's scope
-  host: ?Component; // inserted into this component as children
   key: string | number | void;
   componentOptions: VNodeComponentOptions | void;
   child: Component | void; // component instance
@@ -26,7 +25,6 @@ export default class VNode {
     elm?: Node,
     ns?: string | void,
     context?: Component,
-    host?: ?Component,
     componentOptions?: VNodeComponentOptions
   ) {
     this.tag = tag
@@ -36,7 +34,6 @@ export default class VNode {
     this.elm = elm
     this.ns = ns
     this.context = context
-    this.host = host
     this.key = data && data.key
     this.componentOptions = componentOptions
     this.child = undefined

+ 6 - 5
src/server/render.js

@@ -95,14 +95,15 @@ export function createRenderFunction (
   }
 
   function renderComponent (node, write, next, isRoot) {
-    const child = createComponentInstanceForVnode(node)
+    const prevActive = activeInstance
+    const child = activeInstance = createComponentInstanceForVnode(node, activeInstance)
     normalizeRender(child)
     const childNode = child._render()
     childNode.parent = node
-    const prevActive = activeInstance
-    activeInstance = child
-    renderNode(childNode, write, next, isRoot)
-    activeInstance = prevActive
+    renderNode(childNode, write, () => {
+      activeInstance = prevActive
+      next()
+    }, isRoot)
   }
 
   function renderComponentWithCache (node, write, next, isRoot, cache, key) {

+ 6 - 6
test/ssr/ssr-stream.spec.js

@@ -23,15 +23,15 @@ describe('SSR: renderToStream', () => {
       components: {
         bComp (resolve) {
           return resolve({
-            render () {
-              return this.$createElement('test-async-2')
+            render (h) {
+              return h('test-async-2')
             },
             components: {
               testAsync2 (resolve) {
                 return resolve({
                   created () { this.$parent.$parent.testClass = 'b' },
-                  render () {
-                    return this.$createElement('div', { class: [this.$parent.$parent.testClass] }, 'test')
+                  render (h) {
+                    return h('div', { class: [this.$parent.$parent.testClass] }, 'test')
                   }
                 })
               }
@@ -39,8 +39,8 @@ describe('SSR: renderToStream', () => {
           })
         },
         cComp: {
-          render () {
-            return this.$createElement('div', { class: [this.$parent.testClass] }, 'test')
+          render (h) {
+            return h('div', { class: [this.$parent.testClass] }, 'test')
           }
         }
       }

+ 26 - 0
test/unit/features/instance/properties.spec.js

@@ -54,6 +54,32 @@ describe('Instance properties', () => {
     }).then(done)
   })
 
+  it('$parent', () => {
+    const calls = []
+    const makeOption = name => ({
+      name,
+      template: `<div><slot></slot></div>`,
+      created () {
+        calls.push(`${name}:${this.$parent.$options.name}`)
+      }
+    })
+    new Vue({
+      template: `
+        <div>
+          <outer><middle><inner></inner></middle></outer>
+          <next></next>
+        </div>
+      `,
+      components: {
+        outer: makeOption('outer'),
+        middle: makeOption('middle'),
+        inner: makeOption('inner'),
+        next: makeOption('next')
+      }
+    }).$mount()
+    expect(calls).toEqual(['outer:undefined', 'middle:outer', 'inner:middle', 'next:undefined'])
+  })
+
   it('$isServer', () => {
     const vm = new Vue()
     expect(vm.$isServer).toBe(false)

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

@@ -1,20 +1,14 @@
 import Vue from 'vue'
-import { renderState } from 'core/instance/render'
 import { createElement } from 'core/vdom/create-element'
 import { emptyVNode } from 'core/vdom/vnode'
 import { bind } from 'shared/util'
 
 describe('create-element', () => {
-  afterEach(() => {
-    renderState.activeInstance = null
-  })
-
   it('render vnode with basic reserved tag using createElement', () => {
     const vm = new Vue({
       data: { msg: 'hello world' }
     })
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h('p', {})
     expect(vnode.tag).toBe('p')
     expect(vnode.data).toEqual({})
@@ -35,7 +29,6 @@ describe('create-element', () => {
       }
     })
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h('my-component', { props: { msg: vm.message }})
     expect(vnode.tag).toMatch(/vue-component-[0-9]+/)
     expect(vnode.componentOptions.propsData).toEqual({ msg: vm.message })
@@ -52,7 +45,6 @@ describe('create-element', () => {
     })
     const h = bind(createElement, vm)
     const tag = 'custom-tag'
-    renderState.activeInstance = vm
     const vnode = h(tag, {})
     expect(vnode.tag).toBe('custom-tag')
     expect(vnode.data).toEqual({})
@@ -69,7 +61,6 @@ describe('create-element', () => {
       data: { msg: 'hello world' }
     })
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h(null, {})
     expect(vnode).toEqual(emptyVNode())
   })
@@ -79,7 +70,6 @@ describe('create-element', () => {
       data: { msg: 'hello world' }
     })
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h(Vue.extend({ // Component class
       props: ['msg']
     }), { props: { msg: vm.message }})
@@ -92,19 +82,9 @@ describe('create-element', () => {
     expect(vnode.context).toEqual(vm)
   })
 
-  it('warn message that createElement cannot called when using createElement', () => {
-    const vm = new Vue({
-      data: { msg: 'hello world' }
-    })
-    const h = bind(createElement, vm)
-    h('p', {})
-    expect('createElement cannot be called outside of component').toHaveBeenWarned()
-  })
-
   it('render vnode with createElement with children', () => {
     const vm = new Vue({})
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h('p', void 0, [h('br'), 'hello world', h('br')])
     expect(vnode.children[0].tag).toBe('br')
     expect(vnode.children[1].text).toBe('hello world')
@@ -114,7 +94,6 @@ describe('create-element', () => {
   it('render vnode with children, omitting data', () => {
     const vm = new Vue({})
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h('p', [h('br'), 'hello world', h('br')])
     expect(vnode.children[0].tag).toBe('br')
     expect(vnode.children[1].text).toBe('hello world')
@@ -124,7 +103,6 @@ describe('create-element', () => {
   it('render svg elements with correct namespace', () => {
     const vm = new Vue({})
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h('svg', [h('a', [h('foo', [h('bar')])])])
     expect(vnode.ns).toBe('svg')
     // should apply ns to children recursively
@@ -136,7 +114,6 @@ describe('create-element', () => {
   it('render MathML elements with correct namespace', () => {
     const vm = new Vue({})
     const h = bind(createElement, vm)
-    renderState.activeInstance = vm
     const vnode = h('math', [h('matrix')])
     expect(vnode.ns).toBe('math')
     // should apply ns to children

+ 0 - 2
test/unit/modules/vdom/patch/element.spec.js

@@ -53,9 +53,7 @@ describe('element', () => {
   it('should create element with scope attribute', () => {
     const vnode = new VNode('div')
     vnode.context = new Vue({ _scopeId: 'foo' })
-    vnode.host = new Vue({ _scopeId: 'bar' })
     const elm = patch(null, vnode)
     expect(elm.hasAttribute('foo')).toBe(true)
-    expect(elm.hasAttribute('bar')).toBe(true)
   })
 })