refactor(runtime-core): adjust attr fallthrough behavior
BREAKING CHANGE: attribute fallthrough behavior has been adjusted according to https://github.com/vuejs/rfcs/pull/154
This commit is contained in:
parent
2103a485d7
commit
21bcdec943
@ -54,7 +54,8 @@ describe('api: setup context', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Child = defineComponent({
|
const Child = defineComponent({
|
||||||
setup(props: { count: number }) {
|
props: { count: Number },
|
||||||
|
setup(props) {
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
dummy = props.count
|
dummy = props.count
|
||||||
})
|
})
|
||||||
|
@ -222,7 +222,8 @@ describe('Suspense', () => {
|
|||||||
|
|
||||||
test('content update before suspense resolve', async () => {
|
test('content update before suspense resolve', async () => {
|
||||||
const Async = defineAsyncComponent({
|
const Async = defineAsyncComponent({
|
||||||
setup(props: { msg: string }) {
|
props: { msg: String },
|
||||||
|
setup(props: any) {
|
||||||
return () => h('div', props.msg)
|
return () => h('div', props.msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -569,7 +570,8 @@ describe('Suspense', () => {
|
|||||||
const calls: number[] = []
|
const calls: number[] = []
|
||||||
|
|
||||||
const AsyncChildWithSuspense = defineAsyncComponent({
|
const AsyncChildWithSuspense = defineAsyncComponent({
|
||||||
setup(props: { msg: string }) {
|
props: { msg: String },
|
||||||
|
setup(props: any) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
calls.push(0)
|
calls.push(0)
|
||||||
})
|
})
|
||||||
@ -583,7 +585,8 @@ describe('Suspense', () => {
|
|||||||
|
|
||||||
const AsyncInsideNestedSuspense = defineAsyncComponent(
|
const AsyncInsideNestedSuspense = defineAsyncComponent(
|
||||||
{
|
{
|
||||||
setup(props: { msg: string }) {
|
props: { msg: String },
|
||||||
|
setup(props: any) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
calls.push(2)
|
calls.push(2)
|
||||||
})
|
})
|
||||||
@ -594,7 +597,8 @@ describe('Suspense', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const AsyncChildParent = defineAsyncComponent({
|
const AsyncChildParent = defineAsyncComponent({
|
||||||
setup(props: { msg: string }) {
|
props: { msg: String },
|
||||||
|
setup(props: any) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
calls.push(1)
|
calls.push(1)
|
||||||
})
|
})
|
||||||
@ -604,7 +608,8 @@ describe('Suspense', () => {
|
|||||||
|
|
||||||
const NestedAsyncChild = defineAsyncComponent(
|
const NestedAsyncChild = defineAsyncComponent(
|
||||||
{
|
{
|
||||||
setup(props: { msg: string }) {
|
props: { msg: String },
|
||||||
|
setup(props: any) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
calls.push(3)
|
calls.push(3)
|
||||||
})
|
})
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
createStaticVNode,
|
createStaticVNode,
|
||||||
Suspense,
|
Suspense,
|
||||||
onMounted,
|
onMounted,
|
||||||
defineAsyncComponent
|
defineAsyncComponent,
|
||||||
|
defineComponent
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { renderToString } from '@vue/server-renderer'
|
import { renderToString } from '@vue/server-renderer'
|
||||||
import { mockWarn } from '@vue/shared'
|
import { mockWarn } from '@vue/shared'
|
||||||
@ -448,8 +449,9 @@ describe('SSR hydration', () => {
|
|||||||
const mountedCalls: number[] = []
|
const mountedCalls: number[] = []
|
||||||
const asyncDeps: Promise<any>[] = []
|
const asyncDeps: Promise<any>[] = []
|
||||||
|
|
||||||
const AsyncChild = {
|
const AsyncChild = defineComponent({
|
||||||
async setup(props: { n: number }) {
|
props: ['n'],
|
||||||
|
async setup(props) {
|
||||||
const count = ref(props.n)
|
const count = ref(props.n)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
mountedCalls.push(props.n)
|
mountedCalls.push(props.n)
|
||||||
@ -468,7 +470,7 @@ describe('SSR hydration', () => {
|
|||||||
count.value
|
count.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
const done = jest.fn()
|
const done = jest.fn()
|
||||||
const App = {
|
const App = {
|
||||||
|
@ -15,7 +15,7 @@ import { mockWarn } from '@vue/shared'
|
|||||||
describe('attribute fallthrough', () => {
|
describe('attribute fallthrough', () => {
|
||||||
mockWarn()
|
mockWarn()
|
||||||
|
|
||||||
it('should allow whitelisted attrs to fallthrough', async () => {
|
it('should allow attrs to fallthrough', async () => {
|
||||||
const click = jest.fn()
|
const click = jest.fn()
|
||||||
const childUpdated = jest.fn()
|
const childUpdated = jest.fn()
|
||||||
|
|
||||||
@ -30,12 +30,12 @@ describe('attribute fallthrough', () => {
|
|||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
h(Child, {
|
h(Child, {
|
||||||
foo: 1,
|
foo: count.value + 1,
|
||||||
id: 'test',
|
id: 'test',
|
||||||
class: 'c' + count.value,
|
class: 'c' + count.value,
|
||||||
style: { color: count.value ? 'red' : 'green' },
|
style: { color: count.value ? 'red' : 'green' },
|
||||||
onClick: inc,
|
onClick: inc,
|
||||||
'data-id': 1
|
'data-id': count.value + 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +47,6 @@ describe('attribute fallthrough', () => {
|
|||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{
|
{
|
||||||
id: props.id, // id is not whitelisted
|
|
||||||
class: 'c2',
|
class: 'c2',
|
||||||
style: { fontWeight: 'bold' }
|
style: { fontWeight: 'bold' }
|
||||||
},
|
},
|
||||||
@ -62,8 +61,8 @@ describe('attribute fallthrough', () => {
|
|||||||
|
|
||||||
const node = root.children[0] as HTMLElement
|
const node = root.children[0] as HTMLElement
|
||||||
|
|
||||||
expect(node.getAttribute('id')).toBe('test') // id is not whitelisted, but explicitly bound
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
expect(node.getAttribute('foo')).toBe(null) // foo is not whitelisted
|
expect(node.getAttribute('foo')).toBe('1')
|
||||||
expect(node.getAttribute('class')).toBe('c2 c0')
|
expect(node.getAttribute('class')).toBe('c2 c0')
|
||||||
expect(node.style.color).toBe('green')
|
expect(node.style.color).toBe('green')
|
||||||
expect(node.style.fontWeight).toBe('bold')
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
@ -71,6 +70,121 @@ describe('attribute fallthrough', () => {
|
|||||||
node.dispatchEvent(new CustomEvent('click'))
|
node.dispatchEvent(new CustomEvent('click'))
|
||||||
expect(click).toHaveBeenCalled()
|
expect(click).toHaveBeenCalled()
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(childUpdated).toHaveBeenCalled()
|
||||||
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
|
expect(node.getAttribute('foo')).toBe('2')
|
||||||
|
expect(node.getAttribute('class')).toBe('c2 c1')
|
||||||
|
expect(node.style.color).toBe('red')
|
||||||
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
|
expect(node.dataset.id).toBe('2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only allow whitelisted fallthrough on functional component with optional props', async () => {
|
||||||
|
const click = jest.fn()
|
||||||
|
const childUpdated = jest.fn()
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
function inc() {
|
||||||
|
count.value++
|
||||||
|
click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const Hello = () =>
|
||||||
|
h(Child, {
|
||||||
|
foo: count.value + 1,
|
||||||
|
id: 'test',
|
||||||
|
class: 'c' + count.value,
|
||||||
|
style: { color: count.value ? 'red' : 'green' },
|
||||||
|
onClick: inc
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = (props: any) => {
|
||||||
|
childUpdated()
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: 'c2',
|
||||||
|
style: { fontWeight: 'bold' }
|
||||||
|
},
|
||||||
|
props.foo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
render(h(Hello), root)
|
||||||
|
|
||||||
|
const node = root.children[0] as HTMLElement
|
||||||
|
|
||||||
|
// not whitelisted
|
||||||
|
expect(node.getAttribute('id')).toBe(null)
|
||||||
|
expect(node.getAttribute('foo')).toBe(null)
|
||||||
|
|
||||||
|
// whitelisted: style, class, event listeners
|
||||||
|
expect(node.getAttribute('class')).toBe('c2 c0')
|
||||||
|
expect(node.style.color).toBe('green')
|
||||||
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
|
node.dispatchEvent(new CustomEvent('click'))
|
||||||
|
expect(click).toHaveBeenCalled()
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(childUpdated).toHaveBeenCalled()
|
||||||
|
expect(node.getAttribute('id')).toBe(null)
|
||||||
|
expect(node.getAttribute('foo')).toBe(null)
|
||||||
|
expect(node.getAttribute('class')).toBe('c2 c1')
|
||||||
|
expect(node.style.color).toBe('red')
|
||||||
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow all attrs on functional component with declared props', async () => {
|
||||||
|
const click = jest.fn()
|
||||||
|
const childUpdated = jest.fn()
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
function inc() {
|
||||||
|
count.value++
|
||||||
|
click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const Hello = () =>
|
||||||
|
h(Child, {
|
||||||
|
foo: count.value + 1,
|
||||||
|
id: 'test',
|
||||||
|
class: 'c' + count.value,
|
||||||
|
style: { color: count.value ? 'red' : 'green' },
|
||||||
|
onClick: inc
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = (props: { foo: number }) => {
|
||||||
|
childUpdated()
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: 'c2',
|
||||||
|
style: { fontWeight: 'bold' }
|
||||||
|
},
|
||||||
|
props.foo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Child.props = ['foo']
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
render(h(Hello), root)
|
||||||
|
|
||||||
|
const node = root.children[0] as HTMLElement
|
||||||
|
|
||||||
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
|
expect(node.getAttribute('foo')).toBe(null) // declared as prop
|
||||||
|
expect(node.getAttribute('class')).toBe('c2 c0')
|
||||||
|
expect(node.style.color).toBe('green')
|
||||||
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
|
node.dispatchEvent(new CustomEvent('click'))
|
||||||
|
expect(click).toHaveBeenCalled()
|
||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(childUpdated).toHaveBeenCalled()
|
expect(childUpdated).toHaveBeenCalled()
|
||||||
expect(node.getAttribute('id')).toBe('test')
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
|
@ -55,6 +55,7 @@ export function renderComponentRoot(
|
|||||||
accessedAttrs = false
|
accessedAttrs = false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
let fallthroughAttrs
|
||||||
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
||||||
// withProxy is a proxy with a different `has` trap only for
|
// withProxy is a proxy with a different `has` trap only for
|
||||||
// runtime-compiled render functions using `with` block.
|
// runtime-compiled render functions using `with` block.
|
||||||
@ -62,6 +63,7 @@ export function renderComponentRoot(
|
|||||||
result = normalizeVNode(
|
result = normalizeVNode(
|
||||||
instance.render!.call(proxyToUse, proxyToUse!, renderCache)
|
instance.render!.call(proxyToUse, proxyToUse!, renderCache)
|
||||||
)
|
)
|
||||||
|
fallthroughAttrs = attrs
|
||||||
} else {
|
} else {
|
||||||
// functional
|
// functional
|
||||||
const render = Component as FunctionalComponent
|
const render = Component as FunctionalComponent
|
||||||
@ -74,14 +76,14 @@ export function renderComponentRoot(
|
|||||||
})
|
})
|
||||||
: render(props, null as any /* we know it doesn't need it */)
|
: render(props, null as any /* we know it doesn't need it */)
|
||||||
)
|
)
|
||||||
|
fallthroughAttrs = Component.props ? attrs : getFallthroughAttrs(attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attr merging
|
// attr merging
|
||||||
let fallthroughAttrs
|
|
||||||
if (
|
if (
|
||||||
Component.inheritAttrs !== false &&
|
Component.inheritAttrs !== false &&
|
||||||
attrs !== EMPTY_OBJ &&
|
fallthroughAttrs &&
|
||||||
(fallthroughAttrs = getFallthroughAttrs(attrs))
|
fallthroughAttrs !== EMPTY_OBJ
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
result.shapeFlag & ShapeFlags.ELEMENT ||
|
result.shapeFlag & ShapeFlags.ELEMENT ||
|
||||||
@ -140,14 +142,7 @@ export function renderComponentRoot(
|
|||||||
const getFallthroughAttrs = (attrs: Data): Data | undefined => {
|
const getFallthroughAttrs = (attrs: Data): Data | undefined => {
|
||||||
let res: Data | undefined
|
let res: Data | undefined
|
||||||
for (const key in attrs) {
|
for (const key in attrs) {
|
||||||
if (
|
if (key === 'class' || key === 'style' || isOn(key)) {
|
||||||
key === 'class' ||
|
|
||||||
key === 'style' ||
|
|
||||||
key === 'role' ||
|
|
||||||
isOn(key) ||
|
|
||||||
key.indexOf('aria-') === 0 ||
|
|
||||||
key.indexOf('data-') === 0
|
|
||||||
) {
|
|
||||||
;(res || (res = {}))[key] = attrs[key]
|
;(res || (res = {}))[key] = attrs[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user