feat(runtime-vapor): reset old props when setting dynamic props (#131)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
ygj6 2024-02-20 21:13:48 +08:00 committed by GitHub
parent 1710bfdd21
commit b11ecbda69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 78 additions and 6 deletions

View File

@ -5,6 +5,7 @@ import {
setAttr,
setClass,
setDOMProp,
setDynamicProps,
setHtml,
setStyle,
setText,
@ -414,6 +415,54 @@ describe('patchProp', () => {
test.todo('should be able to set something on SVG')
})
describe('setDynamicProps', () => {
test('basic set dynamic props', () => {
const el = document.createElement('div')
setDynamicProps(el, { foo: 'val' }, { bar: 'val' })
expect(el.getAttribute('foo')).toBe('val')
expect(el.getAttribute('bar')).toBe('val')
})
test('should merge props', () => {
const el = document.createElement('div')
setDynamicProps(el, { foo: 'val' }, { foo: 'newVal' })
expect(el.getAttribute('foo')).toBe('newVal')
})
test('should reset old props', () => {
const el = document.createElement('div')
setDynamicProps(el, { foo: 'val' })
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('foo')).toBe('val')
setDynamicProps(el, { bar: 'val' })
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('bar')).toBe('val')
expect(el.getAttribute('foo')).toBeNull()
})
test('should reset old modifier props', () => {
const el = document.createElement('div')
setDynamicProps(el, { ['.foo']: 'val' })
expect((el as any).foo).toBe('val')
setDynamicProps(el, { ['.bar']: 'val' })
expect((el as any).bar).toBe('val')
expect((el as any).foo).toBe('')
setDynamicProps(el, { ['^foo']: 'val' })
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('foo')).toBe('val')
setDynamicProps(el, { ['^bar']: 'val' })
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('bar')).toBe('val')
expect(el.getAttribute('foo')).toBeNull()
})
})
describe('setText', () => {
test('should set textContent', () => {
const el = document.createElement('div')

View File

@ -9,20 +9,28 @@ import {
normalizeStyle,
toDisplayString,
} from '@vue/shared'
import { currentInstance } from '../component'
import { type ElementMetadata, currentInstance } from '../component'
import { warn } from '../warning'
import { setStyle } from './style'
export function recordPropMetadata(el: Node, key: string, value: any): any {
function getMetadata(el: Node): ElementMetadata {
const EMPTY_METADATA = { props: {} }
if (!currentInstance) {
// TODO implement error handling
if (__DEV__) throw new Error('cannot be used out of component')
return
return EMPTY_METADATA
}
let metadata = currentInstance.metadata.get(el)
if (!metadata) {
currentInstance.metadata.set(el, (metadata = { props: {} }))
currentInstance.metadata.set(el, (metadata = EMPTY_METADATA))
}
return metadata
}
export function recordPropMetadata(el: Node, key: string, value: any): any {
const metadata = getMetadata(el)
const prev = metadata.props[key]
metadata.props[key] = value
return prev
@ -140,9 +148,21 @@ export function setDynamicProp(el: Element, key: string, value: any) {
}
export function setDynamicProps(el: Element, ...args: any) {
const oldProps = getMetadata(el).props
const props = args.length > 1 ? mergeProps(...args) : args[0]
// TODO remove all of old props before set new props since there is containing dynamic key
for (const key in oldProps) {
// TODO should these keys be allowed as dynamic keys? The current logic of the runtime-core will throw an error
if (key === 'textContent' || key === 'innerHTML') {
continue
}
const hasNewValue = props[key] || props['.' + key] || props['^' + key]
if (oldProps[key] && !hasNewValue) {
setDynamicProp(el, key, null)
}
}
for (const key in props) {
setDynamicProp(el, key, props[key])
}

View File

@ -11,7 +11,7 @@ const handleClick = () => {
</script>
<template>
<button @click="handleClick">{{ count }}</button>
<button @click="handleClick">click me to update props</button>
<!-- prop id's value should update reactively -->
<button :id="'before'" :[key]="'dynamic key after' + count">
@ -34,4 +34,7 @@ const handleClick = () => {
<!-- prop id's value should update only once since the prop id in object props was override -->
<button v-bind="obj" :id="'after'">{{ count }}</button>
<button v-bind="obj" :[key]="'dynamic key after'">{{ count }}</button>
<!-- old props will be reset after dynamic key changed -->
<button :[`key${count}`]="'dynamic key'">{{ count }}</button>
</template>