refactor(runtime-core): refactor props resolution
Improve performance in optimized mode + tests
This commit is contained in:
parent
c28a9196b2
commit
ec4a4c1e06
@ -120,7 +120,6 @@ describe('api: setup context', () => {
|
|||||||
// puts everything received in attrs
|
// puts everything received in attrs
|
||||||
// disable implicit fallthrough
|
// disable implicit fallthrough
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: {},
|
|
||||||
setup(props: any, { attrs }: any) {
|
setup(props: any, { attrs }: any) {
|
||||||
return () => h('div', attrs)
|
return () => h('div', attrs)
|
||||||
}
|
}
|
||||||
|
232
packages/runtime-core/__tests__/componentProps.spec.ts
Normal file
232
packages/runtime-core/__tests__/componentProps.spec.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import {
|
||||||
|
ComponentInternalInstance,
|
||||||
|
getCurrentInstance,
|
||||||
|
render,
|
||||||
|
h,
|
||||||
|
nodeOps,
|
||||||
|
FunctionalComponent,
|
||||||
|
defineComponent,
|
||||||
|
ref
|
||||||
|
} from '@vue/runtime-test'
|
||||||
|
import { render as domRender, nextTick } from 'vue'
|
||||||
|
import { mockWarn } from '@vue/shared'
|
||||||
|
|
||||||
|
describe('component props', () => {
|
||||||
|
mockWarn()
|
||||||
|
|
||||||
|
test('stateful', () => {
|
||||||
|
let props: any
|
||||||
|
let attrs: any
|
||||||
|
let proxy: any
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: ['foo'],
|
||||||
|
render() {
|
||||||
|
props = this.$props
|
||||||
|
attrs = this.$attrs
|
||||||
|
proxy = this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp, { foo: 1, bar: 2 }), root)
|
||||||
|
expect(proxy.foo).toBe(1)
|
||||||
|
expect(props).toEqual({ foo: 1 })
|
||||||
|
expect(attrs).toEqual({ bar: 2 })
|
||||||
|
|
||||||
|
render(h(Comp, { foo: 2, bar: 3, baz: 4 }), root)
|
||||||
|
expect(proxy.foo).toBe(2)
|
||||||
|
expect(props).toEqual({ foo: 2 })
|
||||||
|
expect(attrs).toEqual({ bar: 3, baz: 4 })
|
||||||
|
|
||||||
|
render(h(Comp, { qux: 5 }), root)
|
||||||
|
expect(proxy.foo).toBeUndefined()
|
||||||
|
expect(props).toEqual({})
|
||||||
|
expect(attrs).toEqual({ qux: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('stateful with setup', () => {
|
||||||
|
let props: any
|
||||||
|
let attrs: any
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: ['foo'],
|
||||||
|
setup(_props, { attrs: _attrs }) {
|
||||||
|
return () => {
|
||||||
|
props = _props
|
||||||
|
attrs = _attrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp, { foo: 1, bar: 2 }), root)
|
||||||
|
expect(props).toEqual({ foo: 1 })
|
||||||
|
expect(attrs).toEqual({ bar: 2 })
|
||||||
|
|
||||||
|
render(h(Comp, { foo: 2, bar: 3, baz: 4 }), root)
|
||||||
|
expect(props).toEqual({ foo: 2 })
|
||||||
|
expect(attrs).toEqual({ bar: 3, baz: 4 })
|
||||||
|
|
||||||
|
render(h(Comp, { qux: 5 }), root)
|
||||||
|
expect(props).toEqual({})
|
||||||
|
expect(attrs).toEqual({ qux: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('functional with declaration', () => {
|
||||||
|
let props: any
|
||||||
|
let attrs: any
|
||||||
|
|
||||||
|
const Comp: FunctionalComponent = (_props, { attrs: _attrs }) => {
|
||||||
|
props = _props
|
||||||
|
attrs = _attrs
|
||||||
|
}
|
||||||
|
Comp.props = ['foo']
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp, { foo: 1, bar: 2 }), root)
|
||||||
|
expect(props).toEqual({ foo: 1 })
|
||||||
|
expect(attrs).toEqual({ bar: 2 })
|
||||||
|
|
||||||
|
render(h(Comp, { foo: 2, bar: 3, baz: 4 }), root)
|
||||||
|
expect(props).toEqual({ foo: 2 })
|
||||||
|
expect(attrs).toEqual({ bar: 3, baz: 4 })
|
||||||
|
|
||||||
|
render(h(Comp, { qux: 5 }), root)
|
||||||
|
expect(props).toEqual({})
|
||||||
|
expect(attrs).toEqual({ qux: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('functional without declaration', () => {
|
||||||
|
let props: any
|
||||||
|
let attrs: any
|
||||||
|
const Comp: FunctionalComponent = (_props, { attrs: _attrs }) => {
|
||||||
|
props = _props
|
||||||
|
attrs = _attrs
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
|
||||||
|
render(h(Comp, { foo: 1 }), root)
|
||||||
|
expect(props).toEqual({ foo: 1 })
|
||||||
|
expect(attrs).toEqual({ foo: 1 })
|
||||||
|
expect(props).toBe(attrs)
|
||||||
|
|
||||||
|
render(h(Comp, { bar: 2 }), root)
|
||||||
|
expect(props).toEqual({ bar: 2 })
|
||||||
|
expect(attrs).toEqual({ bar: 2 })
|
||||||
|
expect(props).toBe(attrs)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('boolean casting', () => {
|
||||||
|
let proxy: any
|
||||||
|
const Comp = {
|
||||||
|
props: {
|
||||||
|
foo: Boolean,
|
||||||
|
bar: Boolean,
|
||||||
|
baz: Boolean,
|
||||||
|
qux: Boolean
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
proxy = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(
|
||||||
|
h(Comp, {
|
||||||
|
// absent should cast to false
|
||||||
|
bar: '', // empty string should cast to true
|
||||||
|
baz: 'baz', // same string should cast to true
|
||||||
|
qux: 'ok' // other values should be left in-tact (but raise warning)
|
||||||
|
}),
|
||||||
|
nodeOps.createElement('div')
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(proxy.foo).toBe(false)
|
||||||
|
expect(proxy.bar).toBe(true)
|
||||||
|
expect(proxy.baz).toBe(true)
|
||||||
|
expect(proxy.qux).toBe('ok')
|
||||||
|
expect('type check failed for prop "qux"').toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('default value', () => {
|
||||||
|
let proxy: any
|
||||||
|
const Comp = {
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
default: () => ({ a: 1 })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
proxy = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp, { foo: 2 }), root)
|
||||||
|
expect(proxy.foo).toBe(2)
|
||||||
|
expect(proxy.bar).toEqual({ a: 1 })
|
||||||
|
|
||||||
|
render(h(Comp, { foo: undefined, bar: { b: 2 } }), root)
|
||||||
|
expect(proxy.foo).toBe(1)
|
||||||
|
expect(proxy.bar).toEqual({ b: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('optimized props updates', async () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
props: ['foo'],
|
||||||
|
template: `<div>{{ foo }}</div>`
|
||||||
|
})
|
||||||
|
|
||||||
|
const foo = ref(1)
|
||||||
|
const id = ref('a')
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
foo,
|
||||||
|
id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: { Child },
|
||||||
|
template: `<Child :foo="foo" :id="id"/>`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Note this one is using the main Vue render so it can compile template
|
||||||
|
// on the fly
|
||||||
|
const root = document.createElement('div')
|
||||||
|
domRender(h(Comp), root)
|
||||||
|
expect(root.innerHTML).toBe('<div id="a">1</div>')
|
||||||
|
|
||||||
|
foo.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe('<div id="a">2</div>')
|
||||||
|
|
||||||
|
id.value = 'b'
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe('<div id="b">2</div>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warn props mutation', () => {
|
||||||
|
let instance: ComponentInternalInstance
|
||||||
|
let setupProps: any
|
||||||
|
const Comp = {
|
||||||
|
props: ['foo'],
|
||||||
|
setup(props: any) {
|
||||||
|
instance = getCurrentInstance()!
|
||||||
|
setupProps = props
|
||||||
|
return () => null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(h(Comp, { foo: 1 }), nodeOps.createElement('div'))
|
||||||
|
expect(setupProps.foo).toBe(1)
|
||||||
|
expect(instance!.props.foo).toBe(1)
|
||||||
|
setupProps.foo = 2
|
||||||
|
expect(`Set operation on key "foo" failed`).toHaveBeenWarned()
|
||||||
|
expect(() => {
|
||||||
|
;(instance!.proxy as any).foo = 2
|
||||||
|
}).toThrow(TypeError)
|
||||||
|
expect(`Attempting to mutate prop "foo"`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
@ -57,31 +57,6 @@ describe('component: proxy', () => {
|
|||||||
expect(instance!.renderContext.foo).toBe(2)
|
expect(instance!.renderContext.foo).toBe(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('propsProxy', () => {
|
|
||||||
let instance: ComponentInternalInstance
|
|
||||||
let instanceProxy: any
|
|
||||||
const Comp = {
|
|
||||||
props: {
|
|
||||||
foo: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return () => null
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
instance = getCurrentInstance()!
|
|
||||||
instanceProxy = this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render(h(Comp), nodeOps.createElement('div'))
|
|
||||||
expect(instanceProxy.foo).toBe(1)
|
|
||||||
expect(instance!.propsProxy!.foo).toBe(1)
|
|
||||||
expect(() => (instanceProxy.foo = 2)).toThrow(TypeError)
|
|
||||||
expect(`Attempting to mutate prop "foo"`).toHaveBeenWarned()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should not expose non-declared props', () => {
|
test('should not expose non-declared props', () => {
|
||||||
let instanceProxy: any
|
let instanceProxy: any
|
||||||
const Comp = {
|
const Comp = {
|
||||||
@ -110,7 +85,7 @@ describe('component: proxy', () => {
|
|||||||
}
|
}
|
||||||
render(h(Comp), nodeOps.createElement('div'))
|
render(h(Comp), nodeOps.createElement('div'))
|
||||||
expect(instanceProxy.$data).toBe(instance!.data)
|
expect(instanceProxy.$data).toBe(instance!.data)
|
||||||
expect(instanceProxy.$props).toBe(instance!.propsProxy)
|
expect(instanceProxy.$props).toBe(instance!.props)
|
||||||
expect(instanceProxy.$attrs).toBe(instance!.attrs)
|
expect(instanceProxy.$attrs).toBe(instance!.attrs)
|
||||||
expect(instanceProxy.$slots).toBe(instance!.slots)
|
expect(instanceProxy.$slots).toBe(instance!.slots)
|
||||||
expect(instanceProxy.$refs).toBe(instance!.refs)
|
expect(instanceProxy.$refs).toBe(instance!.refs)
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
isInSSRComponentSetup
|
isInSSRComponentSetup
|
||||||
} from './component'
|
} from './component'
|
||||||
import { isFunction, isObject, EMPTY_OBJ, NO } from '@vue/shared'
|
import { isFunction, isObject, NO } from '@vue/shared'
|
||||||
import { ComponentPublicInstance } from './componentProxy'
|
import { ComponentPublicInstance } from './componentProxy'
|
||||||
import { createVNode } from './vnode'
|
import { createVNode } from './vnode'
|
||||||
import { defineComponent } from './apiDefineComponent'
|
import { defineComponent } from './apiDefineComponent'
|
||||||
@ -181,11 +181,7 @@ export function defineAsyncComponent<
|
|||||||
|
|
||||||
function createInnerComp(
|
function createInnerComp(
|
||||||
comp: Component,
|
comp: Component,
|
||||||
{ props, slots }: ComponentInternalInstance
|
{ vnode: { props, children } }: ComponentInternalInstance
|
||||||
) {
|
) {
|
||||||
return createVNode(
|
return createVNode(comp, props, children)
|
||||||
comp,
|
|
||||||
props === EMPTY_OBJ ? null : props,
|
|
||||||
slots === EMPTY_OBJ ? null : slots
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { VNode, VNodeChild, isVNode } from './vnode'
|
|||||||
import {
|
import {
|
||||||
reactive,
|
reactive,
|
||||||
ReactiveEffect,
|
ReactiveEffect,
|
||||||
shallowReadonly,
|
|
||||||
pauseTracking,
|
pauseTracking,
|
||||||
resetTracking
|
resetTracking
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
@ -15,7 +14,7 @@ import {
|
|||||||
exposePropsOnDevProxyTarget,
|
exposePropsOnDevProxyTarget,
|
||||||
exposeRenderContextOnDevProxyTarget
|
exposeRenderContextOnDevProxyTarget
|
||||||
} from './componentProxy'
|
} from './componentProxy'
|
||||||
import { ComponentPropsOptions, resolveProps } from './componentProps'
|
import { ComponentPropsOptions, initProps } from './componentProps'
|
||||||
import { Slots, resolveSlots } from './componentSlots'
|
import { Slots, resolveSlots } from './componentSlots'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
@ -147,7 +146,6 @@ export interface ComponentInternalInstance {
|
|||||||
// alternative proxy used only for runtime-compiled render functions using
|
// alternative proxy used only for runtime-compiled render functions using
|
||||||
// `with` block
|
// `with` block
|
||||||
withProxy: ComponentPublicInstance | null
|
withProxy: ComponentPublicInstance | null
|
||||||
propsProxy: Data | null
|
|
||||||
setupContext: SetupContext | null
|
setupContext: SetupContext | null
|
||||||
refs: Data
|
refs: Data
|
||||||
emit: EmitFn
|
emit: EmitFn
|
||||||
@ -208,7 +206,6 @@ export function createComponentInstance(
|
|||||||
proxy: null,
|
proxy: null,
|
||||||
proxyTarget: null!, // to be immediately set
|
proxyTarget: null!, // to be immediately set
|
||||||
withProxy: null,
|
withProxy: null,
|
||||||
propsProxy: null,
|
|
||||||
setupContext: null,
|
setupContext: null,
|
||||||
effects: null,
|
effects: null,
|
||||||
provides: parent ? parent.provides : Object.create(appContext.provides),
|
provides: parent ? parent.provides : Object.create(appContext.provides),
|
||||||
@ -292,26 +289,24 @@ export let isInSSRComponentSetup = false
|
|||||||
|
|
||||||
export function setupComponent(
|
export function setupComponent(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
|
||||||
isSSR = false
|
isSSR = false
|
||||||
) {
|
) {
|
||||||
isInSSRComponentSetup = isSSR
|
isInSSRComponentSetup = isSSR
|
||||||
|
|
||||||
const { props, children, shapeFlag } = instance.vnode
|
const { props, children, shapeFlag } = instance.vnode
|
||||||
resolveProps(instance, props)
|
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
|
||||||
|
initProps(instance, props, isStateful, isSSR)
|
||||||
resolveSlots(instance, children)
|
resolveSlots(instance, children)
|
||||||
|
|
||||||
// setup stateful logic
|
const setupResult = isStateful
|
||||||
let setupResult
|
? setupStatefulComponent(instance, isSSR)
|
||||||
if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
: undefined
|
||||||
setupResult = setupStatefulComponent(instance, parentSuspense, isSSR)
|
|
||||||
}
|
|
||||||
isInSSRComponentSetup = false
|
isInSSRComponentSetup = false
|
||||||
return setupResult
|
return setupResult
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupStatefulComponent(
|
function setupStatefulComponent(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
|
||||||
isSSR: boolean
|
isSSR: boolean
|
||||||
) {
|
) {
|
||||||
const Component = instance.type as ComponentOptions
|
const Component = instance.type as ComponentOptions
|
||||||
@ -340,13 +335,7 @@ function setupStatefulComponent(
|
|||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
exposePropsOnDevProxyTarget(instance)
|
exposePropsOnDevProxyTarget(instance)
|
||||||
}
|
}
|
||||||
// 2. create props proxy
|
// 2. call setup()
|
||||||
// the propsProxy is a reactive AND readonly proxy to the actual props.
|
|
||||||
// it will be updated in resolveProps() on updates before render
|
|
||||||
const propsProxy = (instance.propsProxy = isSSR
|
|
||||||
? instance.props
|
|
||||||
: shallowReadonly(instance.props))
|
|
||||||
// 3. call setup()
|
|
||||||
const { setup } = Component
|
const { setup } = Component
|
||||||
if (setup) {
|
if (setup) {
|
||||||
const setupContext = (instance.setupContext =
|
const setupContext = (instance.setupContext =
|
||||||
@ -358,7 +347,7 @@ function setupStatefulComponent(
|
|||||||
setup,
|
setup,
|
||||||
instance,
|
instance,
|
||||||
ErrorCodes.SETUP_FUNCTION,
|
ErrorCodes.SETUP_FUNCTION,
|
||||||
[propsProxy, setupContext]
|
[instance.props, setupContext]
|
||||||
)
|
)
|
||||||
resetTracking()
|
resetTracking()
|
||||||
currentInstance = null
|
currentInstance = null
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { toRaw, lock, unlock } from '@vue/reactivity'
|
import { toRaw, lock, unlock, shallowReadonly } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
camelize,
|
camelize,
|
||||||
@ -13,8 +13,7 @@ import {
|
|||||||
PatchFlags,
|
PatchFlags,
|
||||||
makeMap,
|
makeMap,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
EMPTY_ARR,
|
EMPTY_ARR
|
||||||
ShapeFlags
|
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { Data, ComponentInternalInstance } from './component'
|
import { Data, ComponentInternalInstance } from './component'
|
||||||
@ -95,45 +94,117 @@ type NormalizedProp =
|
|||||||
// and an array of prop keys that need value casting (booleans and defaults)
|
// and an array of prop keys that need value casting (booleans and defaults)
|
||||||
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
|
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
|
||||||
|
|
||||||
// resolve raw VNode data.
|
export function initProps(
|
||||||
// - filter out reserved keys (key, ref)
|
|
||||||
// - extract class and style into $attrs (to be merged onto child
|
|
||||||
// component root)
|
|
||||||
// - for the rest:
|
|
||||||
// - if has declared props: put declared ones in `props`, the rest in `attrs`
|
|
||||||
// - else: everything goes in `props`.
|
|
||||||
|
|
||||||
export function resolveProps(
|
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
rawProps: Data | null
|
rawProps: Data | null,
|
||||||
|
isStateful: number, // result of bitwise flag comparison
|
||||||
|
isSSR = false
|
||||||
) {
|
) {
|
||||||
const _options = instance.type.props
|
const props: Data = {}
|
||||||
const hasDeclaredProps = !!_options
|
const attrs: Data = {}
|
||||||
if (!rawProps && !hasDeclaredProps) {
|
setFullProps(instance, rawProps, props, attrs)
|
||||||
instance.props = instance.attrs = EMPTY_OBJ
|
const options = instance.type.props
|
||||||
return
|
// validation
|
||||||
|
if (__DEV__ && options && rawProps) {
|
||||||
|
validateProps(props, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { 0: options, 1: needCastKeys } = normalizePropsOptions(_options)!
|
if (isStateful) {
|
||||||
const emits = instance.type.emits
|
// stateful
|
||||||
const props: Data = {}
|
instance.props = isSSR ? props : shallowReadonly(props)
|
||||||
let attrs: Data | undefined = undefined
|
} else {
|
||||||
|
if (!options) {
|
||||||
// update the instance propsProxy (passed to setup()) to trigger potential
|
// functional w/ optional props, props === attrs
|
||||||
// changes
|
instance.props = attrs
|
||||||
const propsProxy = instance.propsProxy
|
} else {
|
||||||
const setProp = propsProxy
|
// functional w/ declared props
|
||||||
? (key: string, val: unknown) => {
|
instance.props = props
|
||||||
props[key] = val
|
}
|
||||||
propsProxy[key] = val
|
}
|
||||||
}
|
instance.attrs = attrs
|
||||||
: (key: string, val: unknown) => {
|
}
|
||||||
props[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function updateProps(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
rawProps: Data | null,
|
||||||
|
optimized: boolean
|
||||||
|
) {
|
||||||
// allow mutation of propsProxy (which is readonly by default)
|
// allow mutation of propsProxy (which is readonly by default)
|
||||||
unlock()
|
unlock()
|
||||||
|
|
||||||
|
const {
|
||||||
|
props,
|
||||||
|
attrs,
|
||||||
|
vnode: { patchFlag }
|
||||||
|
} = instance
|
||||||
|
const rawOptions = instance.type.props
|
||||||
|
const rawCurrentProps = toRaw(props)
|
||||||
|
const { 0: options } = normalizePropsOptions(rawOptions)
|
||||||
|
|
||||||
|
if ((optimized || patchFlag > 0) && !(patchFlag & PatchFlags.FULL_PROPS)) {
|
||||||
|
if (patchFlag & PatchFlags.PROPS) {
|
||||||
|
// Compiler-generated props & no keys change, just set the updated
|
||||||
|
// the props.
|
||||||
|
const propsToUpdate = instance.vnode.dynamicProps!
|
||||||
|
for (let i = 0; i < propsToUpdate.length; i++) {
|
||||||
|
const key = propsToUpdate[i]
|
||||||
|
// PROPS flag guarantees rawProps to be non-null
|
||||||
|
const value = rawProps![key]
|
||||||
|
if (options) {
|
||||||
|
// attr / props separation was done on init and will be consistent
|
||||||
|
// in this code path, so just check if attrs have it.
|
||||||
|
if (hasOwn(attrs, key)) {
|
||||||
|
attrs[key] = value
|
||||||
|
} else {
|
||||||
|
const camelizedKey = camelize(key)
|
||||||
|
props[camelizedKey] = resolvePropValue(
|
||||||
|
options,
|
||||||
|
rawCurrentProps,
|
||||||
|
camelizedKey,
|
||||||
|
value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attrs[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// full props update.
|
||||||
|
setFullProps(instance, rawProps, props, attrs)
|
||||||
|
// in case of dynamic props, check if we need to delete keys from
|
||||||
|
// the props object
|
||||||
|
for (const key in rawCurrentProps) {
|
||||||
|
if (!rawProps || !hasOwn(rawProps, key)) {
|
||||||
|
delete props[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key in attrs) {
|
||||||
|
if (!rawProps || !hasOwn(rawProps, key)) {
|
||||||
|
delete attrs[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock readonly
|
||||||
|
lock()
|
||||||
|
|
||||||
|
if (__DEV__ && rawOptions && rawProps) {
|
||||||
|
validateProps(props, rawOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFullProps(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
rawProps: Data | null,
|
||||||
|
props: Data,
|
||||||
|
attrs: Data
|
||||||
|
) {
|
||||||
|
const { 0: options, 1: needCastKeys } = normalizePropsOptions(
|
||||||
|
instance.type.props
|
||||||
|
)
|
||||||
|
const emits = instance.type.emits
|
||||||
|
|
||||||
if (rawProps) {
|
if (rawProps) {
|
||||||
for (const key in rawProps) {
|
for (const key in rawProps) {
|
||||||
const value = rawProps[key]
|
const value = rawProps[key]
|
||||||
@ -144,95 +215,58 @@ export function resolveProps(
|
|||||||
// prop option names are camelized during normalization, so to support
|
// prop option names are camelized during normalization, so to support
|
||||||
// kebab -> camel conversion here we need to camelize the key.
|
// kebab -> camel conversion here we need to camelize the key.
|
||||||
let camelKey
|
let camelKey
|
||||||
if (hasDeclaredProps && hasOwn(options, (camelKey = camelize(key)))) {
|
if (options && hasOwn(options, (camelKey = camelize(key)))) {
|
||||||
setProp(camelKey, value)
|
props[camelKey] = value
|
||||||
} else if (!emits || !isEmitListener(emits, key)) {
|
} else if (!emits || !isEmitListener(emits, key)) {
|
||||||
// Any non-declared (either as a prop or an emitted event) props are put
|
// Any non-declared (either as a prop or an emitted event) props are put
|
||||||
// into a separate `attrs` object for spreading. Make sure to preserve
|
// into a separate `attrs` object for spreading. Make sure to preserve
|
||||||
// original key casing
|
// original key casing
|
||||||
;(attrs || (attrs = {}))[key] = value
|
attrs[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasDeclaredProps) {
|
if (needCastKeys) {
|
||||||
// set default values & cast booleans
|
|
||||||
for (let i = 0; i < needCastKeys.length; i++) {
|
for (let i = 0; i < needCastKeys.length; i++) {
|
||||||
const key = needCastKeys[i]
|
const key = needCastKeys[i]
|
||||||
let opt = options[key]
|
props[key] = resolvePropValue(options!, props, key, props[key])
|
||||||
if (opt == null) continue
|
|
||||||
const hasDefault = hasOwn(opt, 'default')
|
|
||||||
const currentValue = props[key]
|
|
||||||
// default values
|
|
||||||
if (hasDefault && currentValue === undefined) {
|
|
||||||
const defaultValue = opt.default
|
|
||||||
setProp(key, isFunction(defaultValue) ? defaultValue() : defaultValue)
|
|
||||||
}
|
|
||||||
// boolean casting
|
|
||||||
if (opt[BooleanFlags.shouldCast]) {
|
|
||||||
if (!hasOwn(props, key) && !hasDefault) {
|
|
||||||
setProp(key, false)
|
|
||||||
} else if (
|
|
||||||
opt[BooleanFlags.shouldCastTrue] &&
|
|
||||||
(currentValue === '' || currentValue === hyphenate(key))
|
|
||||||
) {
|
|
||||||
setProp(key, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// validation
|
|
||||||
if (__DEV__ && rawProps) {
|
|
||||||
for (const key in options) {
|
|
||||||
let opt = options[key]
|
|
||||||
if (opt == null) continue
|
|
||||||
validateProp(key, props[key], opt, !hasOwn(props, key))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case of dynamic props, check if we need to delete keys from
|
|
||||||
// the props proxy
|
|
||||||
const { patchFlag } = instance.vnode
|
|
||||||
if (
|
|
||||||
hasDeclaredProps &&
|
|
||||||
propsProxy &&
|
|
||||||
(patchFlag === 0 || patchFlag & PatchFlags.FULL_PROPS)
|
|
||||||
) {
|
|
||||||
const rawInitialProps = toRaw(propsProxy)
|
|
||||||
for (const key in rawInitialProps) {
|
|
||||||
if (!hasOwn(props, key)) {
|
|
||||||
delete propsProxy[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lock readonly
|
|
||||||
lock()
|
|
||||||
|
|
||||||
if (
|
|
||||||
instance.vnode.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT &&
|
|
||||||
!hasDeclaredProps
|
|
||||||
) {
|
|
||||||
// functional component with optional props: use attrs as props
|
|
||||||
instance.props = attrs || EMPTY_OBJ
|
|
||||||
} else {
|
|
||||||
instance.props = props
|
|
||||||
}
|
|
||||||
instance.attrs = attrs || EMPTY_OBJ
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePropName(key: string) {
|
function resolvePropValue(
|
||||||
if (key[0] !== '$') {
|
options: NormalizedPropsOptions[0],
|
||||||
return true
|
props: Data,
|
||||||
} else if (__DEV__) {
|
key: string,
|
||||||
warn(`Invalid prop name: "${key}" is a reserved property.`)
|
value: unknown
|
||||||
|
) {
|
||||||
|
let opt = options[key]
|
||||||
|
if (opt == null) {
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
return false
|
const hasDefault = hasOwn(opt, 'default')
|
||||||
|
// default values
|
||||||
|
if (hasDefault && value === undefined) {
|
||||||
|
const defaultValue = opt.default
|
||||||
|
value = isFunction(defaultValue) ? defaultValue() : defaultValue
|
||||||
|
}
|
||||||
|
// boolean casting
|
||||||
|
if (opt[BooleanFlags.shouldCast]) {
|
||||||
|
if (!hasOwn(props, key) && !hasDefault) {
|
||||||
|
value = false
|
||||||
|
} else if (
|
||||||
|
opt[BooleanFlags.shouldCastTrue] &&
|
||||||
|
(value === '' || value === hyphenate(key))
|
||||||
|
) {
|
||||||
|
value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizePropsOptions(
|
export function normalizePropsOptions(
|
||||||
raw: ComponentPropsOptions | void
|
raw: ComponentPropsOptions | undefined
|
||||||
): NormalizedPropsOptions {
|
): NormalizedPropsOptions | [] {
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
return EMPTY_ARR as any
|
return EMPTY_ARR as any
|
||||||
}
|
}
|
||||||
@ -307,9 +341,23 @@ function getTypeIndex(
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssertionResult = {
|
function validateProps(props: Data, rawOptions: ComponentPropsOptions) {
|
||||||
valid: boolean
|
const rawValues = toRaw(props)
|
||||||
expectedType: string
|
const options = normalizePropsOptions(rawOptions)[0]
|
||||||
|
for (const key in options) {
|
||||||
|
let opt = options[key]
|
||||||
|
if (opt == null) continue
|
||||||
|
validateProp(key, rawValues[key], opt, !hasOwn(rawValues, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePropName(key: string) {
|
||||||
|
if (key[0] !== '$') {
|
||||||
|
return true
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn(`Invalid prop name: "${key}" is a reserved property.`)
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateProp(
|
function validateProp(
|
||||||
@ -354,6 +402,11 @@ const isSimpleType = /*#__PURE__*/ makeMap(
|
|||||||
'String,Number,Boolean,Function,Symbol'
|
'String,Number,Boolean,Function,Symbol'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AssertionResult = {
|
||||||
|
valid: boolean
|
||||||
|
expectedType: string
|
||||||
|
}
|
||||||
|
|
||||||
function assertType(value: unknown, type: PropConstructor): AssertionResult {
|
function assertType(value: unknown, type: PropConstructor): AssertionResult {
|
||||||
let valid
|
let valid
|
||||||
const expectedType = getType(type)
|
const expectedType = getType(type)
|
||||||
|
@ -57,7 +57,7 @@ const publicPropertiesMap: Record<
|
|||||||
$: i => i,
|
$: i => i,
|
||||||
$el: i => i.vnode.el,
|
$el: i => i.vnode.el,
|
||||||
$data: i => i.data,
|
$data: i => i.data,
|
||||||
$props: i => i.propsProxy,
|
$props: i => i.props,
|
||||||
$attrs: i => i.attrs,
|
$attrs: i => i.attrs,
|
||||||
$slots: i => i.slots,
|
$slots: i => i.slots,
|
||||||
$refs: i => i.refs,
|
$refs: i => i.refs,
|
||||||
@ -87,7 +87,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
const {
|
const {
|
||||||
renderContext,
|
renderContext,
|
||||||
data,
|
data,
|
||||||
propsProxy,
|
props,
|
||||||
accessCache,
|
accessCache,
|
||||||
type,
|
type,
|
||||||
sink,
|
sink,
|
||||||
@ -109,7 +109,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
case AccessTypes.CONTEXT:
|
case AccessTypes.CONTEXT:
|
||||||
return renderContext[key]
|
return renderContext[key]
|
||||||
case AccessTypes.PROPS:
|
case AccessTypes.PROPS:
|
||||||
return propsProxy![key]
|
return props![key]
|
||||||
// default: just fallthrough
|
// default: just fallthrough
|
||||||
}
|
}
|
||||||
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
||||||
@ -121,10 +121,10 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
} else if (type.props) {
|
} else if (type.props) {
|
||||||
// only cache other properties when instance has declared (thus stable)
|
// only cache other properties when instance has declared (thus stable)
|
||||||
// props
|
// props
|
||||||
if (hasOwn(normalizePropsOptions(type.props)[0], key)) {
|
if (hasOwn(normalizePropsOptions(type.props)[0]!, key)) {
|
||||||
accessCache![key] = AccessTypes.PROPS
|
accessCache![key] = AccessTypes.PROPS
|
||||||
// return the value from propsProxy for ref unwrapping and readonly
|
// return the value from propsProxy for ref unwrapping and readonly
|
||||||
return propsProxy![key]
|
return props![key]
|
||||||
} else {
|
} else {
|
||||||
accessCache![key] = AccessTypes.OTHER
|
accessCache![key] = AccessTypes.OTHER
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
accessCache![key] !== undefined ||
|
accessCache![key] !== undefined ||
|
||||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
||||||
hasOwn(renderContext, key) ||
|
hasOwn(renderContext, key) ||
|
||||||
(type.props && hasOwn(normalizePropsOptions(type.props)[0], key)) ||
|
(type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
|
||||||
hasOwn(publicPropertiesMap, key) ||
|
hasOwn(publicPropertiesMap, key) ||
|
||||||
hasOwn(sink, key) ||
|
hasOwn(sink, key) ||
|
||||||
hasOwn(appContext.config.globalProperties, key)
|
hasOwn(appContext.config.globalProperties, key)
|
||||||
@ -284,7 +284,7 @@ export function exposePropsOnDevProxyTarget(
|
|||||||
type: { props: propsOptions }
|
type: { props: propsOptions }
|
||||||
} = instance
|
} = instance
|
||||||
if (propsOptions) {
|
if (propsOptions) {
|
||||||
Object.keys(normalizePropsOptions(propsOptions)[0]).forEach(key => {
|
Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => {
|
||||||
Object.defineProperty(proxyTarget, key, {
|
Object.defineProperty(proxyTarget, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
isVNode
|
isVNode
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import { handleError, ErrorCodes } from './errorHandling'
|
import { handleError, ErrorCodes } from './errorHandling'
|
||||||
import { PatchFlags, ShapeFlags, EMPTY_OBJ, isOn } from '@vue/shared'
|
import { PatchFlags, ShapeFlags, isOn } from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
// mark the current rendering instance for asset resolution (e.g.
|
// mark the current rendering instance for asset resolution (e.g.
|
||||||
@ -94,7 +94,7 @@ export function renderComponentRoot(
|
|||||||
if (
|
if (
|
||||||
Component.inheritAttrs !== false &&
|
Component.inheritAttrs !== false &&
|
||||||
fallthroughAttrs &&
|
fallthroughAttrs &&
|
||||||
fallthroughAttrs !== EMPTY_OBJ
|
Object.keys(fallthroughAttrs).length
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
root.shapeFlag & ShapeFlags.ELEMENT ||
|
root.shapeFlag & ShapeFlags.ELEMENT ||
|
||||||
|
@ -438,7 +438,8 @@ function createSuspenseBoundary(
|
|||||||
// consider the comment placeholder case.
|
// consider the comment placeholder case.
|
||||||
hydratedEl ? null : next(instance.subTree),
|
hydratedEl ? null : next(instance.subTree),
|
||||||
suspense,
|
suspense,
|
||||||
isSVG
|
isSVG,
|
||||||
|
optimized
|
||||||
)
|
)
|
||||||
updateHOCHostEl(instance, vnode.el)
|
updateHOCHostEl(instance, vnode.el)
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
@ -156,7 +156,8 @@ export function createHydrationFunctions(
|
|||||||
null,
|
null,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVGContainer(container)
|
isSVGContainer(container),
|
||||||
|
optimized
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// async component
|
// async component
|
||||||
|
@ -42,7 +42,7 @@ import {
|
|||||||
invalidateJob
|
invalidateJob
|
||||||
} from './scheduler'
|
} from './scheduler'
|
||||||
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
|
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
|
||||||
import { resolveProps } from './componentProps'
|
import { updateProps } from './componentProps'
|
||||||
import { resolveSlots } from './componentSlots'
|
import { resolveSlots } from './componentSlots'
|
||||||
import { pushWarningContext, popWarningContext, warn } from './warning'
|
import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||||
import { ComponentPublicInstance } from './componentProxy'
|
import { ComponentPublicInstance } from './componentProxy'
|
||||||
@ -226,7 +226,8 @@ export type MountComponentFn = (
|
|||||||
anchor: RendererNode | null,
|
anchor: RendererNode | null,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
parentSuspense: SuspenseBoundary | null,
|
||||||
isSVG: boolean
|
isSVG: boolean,
|
||||||
|
optimized: boolean
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
type ProcessTextOrCommentFn = (
|
type ProcessTextOrCommentFn = (
|
||||||
@ -242,7 +243,8 @@ export type SetupRenderEffectFn = (
|
|||||||
container: RendererElement,
|
container: RendererElement,
|
||||||
anchor: RendererNode | null,
|
anchor: RendererNode | null,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
parentSuspense: SuspenseBoundary | null,
|
||||||
isSVG: boolean
|
isSVG: boolean,
|
||||||
|
optimized: boolean
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
export const enum MoveType {
|
export const enum MoveType {
|
||||||
@ -961,7 +963,8 @@ function baseCreateRenderer(
|
|||||||
anchor,
|
anchor,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVG
|
isSVG,
|
||||||
|
optimized
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -978,7 +981,7 @@ function baseCreateRenderer(
|
|||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
pushWarningContext(n2)
|
pushWarningContext(n2)
|
||||||
}
|
}
|
||||||
updateComponentPreRender(instance, n2)
|
updateComponentPreRender(instance, n2, optimized)
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
popWarningContext()
|
popWarningContext()
|
||||||
}
|
}
|
||||||
@ -1006,7 +1009,8 @@ function baseCreateRenderer(
|
|||||||
anchor,
|
anchor,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVG
|
isSVG,
|
||||||
|
optimized
|
||||||
) => {
|
) => {
|
||||||
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
|
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
|
||||||
initialVNode,
|
initialVNode,
|
||||||
@ -1034,7 +1038,7 @@ function baseCreateRenderer(
|
|||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
startMeasure(instance, `init`)
|
startMeasure(instance, `init`)
|
||||||
}
|
}
|
||||||
setupComponent(instance, parentSuspense)
|
setupComponent(instance)
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
endMeasure(instance, `init`)
|
endMeasure(instance, `init`)
|
||||||
}
|
}
|
||||||
@ -1063,7 +1067,8 @@ function baseCreateRenderer(
|
|||||||
container,
|
container,
|
||||||
anchor,
|
anchor,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVG
|
isSVG,
|
||||||
|
optimized
|
||||||
)
|
)
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@ -1078,7 +1083,8 @@ function baseCreateRenderer(
|
|||||||
container,
|
container,
|
||||||
anchor,
|
anchor,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVG
|
isSVG,
|
||||||
|
optimized
|
||||||
) => {
|
) => {
|
||||||
// create reactive effect for rendering
|
// create reactive effect for rendering
|
||||||
instance.update = effect(function componentEffect() {
|
instance.update = effect(function componentEffect() {
|
||||||
@ -1162,7 +1168,7 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
updateComponentPreRender(instance, next)
|
updateComponentPreRender(instance, next, optimized)
|
||||||
} else {
|
} else {
|
||||||
next = vnode
|
next = vnode
|
||||||
}
|
}
|
||||||
@ -1232,12 +1238,13 @@ function baseCreateRenderer(
|
|||||||
|
|
||||||
const updateComponentPreRender = (
|
const updateComponentPreRender = (
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
nextVNode: VNode
|
nextVNode: VNode,
|
||||||
|
optimized: boolean
|
||||||
) => {
|
) => {
|
||||||
nextVNode.component = instance
|
nextVNode.component = instance
|
||||||
instance.vnode = nextVNode
|
instance.vnode = nextVNode
|
||||||
instance.next = null
|
instance.next = null
|
||||||
resolveProps(instance, nextVNode.props)
|
updateProps(instance, nextVNode.props, optimized)
|
||||||
resolveSlots(instance, nextVNode.children)
|
resolveSlots(instance, nextVNode.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +352,7 @@ export function cloneVNode<T, U>(
|
|||||||
props: extraProps
|
props: extraProps
|
||||||
? vnode.props
|
? vnode.props
|
||||||
? mergeProps(vnode.props, extraProps)
|
? mergeProps(vnode.props, extraProps)
|
||||||
: extraProps
|
: extend({}, extraProps)
|
||||||
: vnode.props,
|
: vnode.props,
|
||||||
key: vnode.key,
|
key: vnode.key,
|
||||||
ref: vnode.ref,
|
ref: vnode.ref,
|
||||||
|
@ -70,13 +70,11 @@ describe('class', () => {
|
|||||||
|
|
||||||
const childClass: ClassItem = { value: 'd' }
|
const childClass: ClassItem = { value: 'd' }
|
||||||
const child = {
|
const child = {
|
||||||
props: {},
|
|
||||||
render: () => h('div', { class: ['c', childClass.value] })
|
render: () => h('div', { class: ['c', childClass.value] })
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentClass: ClassItem = { value: 'b' }
|
const parentClass: ClassItem = { value: 'b' }
|
||||||
const parent = {
|
const parent = {
|
||||||
props: {},
|
|
||||||
render: () => h(child, { class: ['a', parentClass.value] })
|
render: () => h(child, { class: ['a', parentClass.value] })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,21 +99,18 @@ describe('class', () => {
|
|||||||
|
|
||||||
test('class merge between multiple nested components sharing same element', () => {
|
test('class merge between multiple nested components sharing same element', () => {
|
||||||
const component1 = defineComponent({
|
const component1 = defineComponent({
|
||||||
props: {},
|
|
||||||
render() {
|
render() {
|
||||||
return this.$slots.default!()[0]
|
return this.$slots.default!()[0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const component2 = defineComponent({
|
const component2 = defineComponent({
|
||||||
props: {},
|
|
||||||
render() {
|
render() {
|
||||||
return this.$slots.default!()[0]
|
return this.$slots.default!()[0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const component3 = defineComponent({
|
const component3 = defineComponent({
|
||||||
props: {},
|
|
||||||
render() {
|
render() {
|
||||||
return h(
|
return h(
|
||||||
'div',
|
'div',
|
||||||
|
@ -145,11 +145,7 @@ function renderComponentVNode(
|
|||||||
parentComponent: ComponentInternalInstance | null = null
|
parentComponent: ComponentInternalInstance | null = null
|
||||||
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
|
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
|
||||||
const instance = createComponentInstance(vnode, parentComponent, null)
|
const instance = createComponentInstance(vnode, parentComponent, null)
|
||||||
const res = setupComponent(
|
const res = setupComponent(instance, true /* isSSR */)
|
||||||
instance,
|
|
||||||
null /* parentSuspense (no need to track for SSR) */,
|
|
||||||
true /* isSSR */
|
|
||||||
)
|
|
||||||
if (isPromise(res)) {
|
if (isPromise(res)) {
|
||||||
return res
|
return res
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
Loading…
Reference in New Issue
Block a user