Merge remote-tracking branch 'upstream/main'

This commit is contained in:
三咲智子 Kevin Deng 2024-02-25 21:07:09 +08:00
commit 5819dc9001
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
17 changed files with 178 additions and 46 deletions

View File

@ -7,6 +7,7 @@ import {
} from './reactiveEffect'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
import { warn } from './warning'
type CollectionTypes = IterableCollections | WeakCollections
@ -223,7 +224,7 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
return function (this: CollectionTypes, ...args: unknown[]) {
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this),
)
@ -397,7 +398,7 @@ function checkIdentityKeys(
const rawKey = toRaw(key)
if (rawKey !== key && has.call(target, rawKey)) {
const type = toRawType(target)
console.warn(
warn(
`Reactive ${type} contains both the raw and reactive ` +
`versions of the same object${type === `Map` ? ` as keys` : ``}, ` +
`which can lead to inconsistencies. ` +

View File

@ -42,8 +42,13 @@ export class ComputedRefImpl<T> {
public _cacheable: boolean
/**
* Dev only
*/
_warnRecursive?: boolean
constructor(
getter: ComputedGetter<T>,
private getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean,
@ -74,7 +79,9 @@ export class ComputedRefImpl<T> {
}
trackRefValue(self)
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
__DEV__ && warn(COMPUTED_SIDE_EFFECT_WARN)
if (__DEV__ && (__TEST__ || this._warnRecursive)) {
warn(COMPUTED_SIDE_EFFECT_WARN, `\n\ngetter: `, this.getter)
}
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
}
return self._value

View File

@ -43,6 +43,7 @@ export {
type WritableComputedOptions,
type ComputedGetter,
type ComputedSetter,
type ComputedRefImpl,
} from './computed'
export { deferredComputed } from './deferredComputed'
export {

View File

@ -13,6 +13,7 @@ import {
} from './collectionHandlers'
import type { RawSymbol, Ref, UnwrapRefSimple } from './ref'
import { ReactiveFlags } from './constants'
import { warn } from './warning'
export interface Target {
[ReactiveFlags.SKIP]?: boolean
@ -247,7 +248,7 @@ function createReactiveObject(
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}

View File

@ -25,6 +25,7 @@ import type { ShallowReactiveMarker } from './reactive'
import { type Dep, createDep } from './dep'
import { ComputedRefImpl } from './computed'
import { getDepFromReactive } from './reactiveEffect'
import { warn } from './warning'
declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol
@ -345,7 +346,7 @@ export type ToRefs<T = any> = {
*/
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {

View File

@ -22,7 +22,7 @@ import {
watch,
watchEffect,
} from '@vue/runtime-test'
import { createApp, defineComponent } from 'vue'
import { computed, createApp, defineComponent, inject, provide } from 'vue'
import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
import { resetSuspenseId } from '../../src/components/Suspense'
@ -1039,6 +1039,99 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
})
// #10098
test('switching branches w/ nested suspense', async () => {
const RouterView = {
setup(_: any, { slots }: any) {
const route = inject('route') as any
const depth = inject('depth', 0)
provide('depth', depth + 1)
return () => {
const current = route.value[depth]
return slots.default({ Component: current })[0]
}
},
}
const OuterB = defineAsyncComponent({
setup: () => {
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
})
const InnerB = defineAsyncComponent({
setup: () => {
return () => h('div', 'innerB')
},
})
const OuterA = defineAsyncComponent({
setup: () => {
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
})
const InnerA = defineAsyncComponent({
setup: () => {
return () => h('div', 'innerA')
},
})
const toggle = ref(true)
const route = computed(() => {
return toggle.value ? [OuterA, InnerA] : [OuterB, InnerB]
})
const Comp = {
setup() {
provide('route', route)
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<!---->`)
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
deps.length = 0
toggle.value = false
await nextTick()
// toggle again
toggle.value = true
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
})
test('branch switch to 3rd branch before resolve', async () => {
const calls: string[] = []

View File

@ -1,10 +1,17 @@
import { computed as _computed } from '@vue/reactivity'
import { isInSSRComponentSetup } from './component'
import { type ComputedRefImpl, computed as _computed } from '@vue/reactivity'
import { getCurrentInstance, isInSSRComponentSetup } from './component'
export const computed: typeof _computed = (
getterOrOptions: any,
debugOptions?: any,
) => {
// @ts-expect-error
return _computed(getterOrOptions, debugOptions, isInSSRComponentSetup)
const c = _computed(getterOrOptions, debugOptions, isInSSRComponentSetup)
if (__DEV__) {
const i = getCurrentInstance()
if (i && i.appContext.config.warnRecursiveComputed) {
;(c as unknown as ComputedRefImpl<any>)._warnRecursive = true
}
}
return c
}

View File

@ -83,7 +83,7 @@ export type OptionMergeFunction = (to: unknown, from: unknown) => any
export interface AppConfig {
// @private
readonly isNativeTag?: (tag: string) => boolean
readonly isNativeTag: (tag: string) => boolean
performance: boolean
optionMergeStrategies: Record<string, OptionMergeFunction>
@ -109,6 +109,12 @@ export interface AppConfig {
* @deprecated use config.compilerOptions.isCustomElement
*/
isCustomElement?: (tag: string) => boolean
/**
* TODO document for 3.5
* Enable warnings for computed getters that recursively trigger itself.
*/
warnRecursiveComputed?: boolean
}
export interface AppContext {

View File

@ -62,7 +62,6 @@ import {
type Data,
EMPTY_OBJ,
type IfAny,
NO,
NOOP,
ShapeFlags,
extend,
@ -706,9 +705,11 @@ export const unsetCurrentInstance = () => {
const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')
export function validateComponentName(name: string, config: AppConfig) {
const appIsNativeTag = config.isNativeTag || NO
if (isBuiltInTag(name) || appIsNativeTag(name)) {
export function validateComponentName(
name: string,
{ isNativeTag }: AppConfig,
) {
if (isBuiltInTag(name) || isNativeTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component id: ' + name,
)

View File

@ -100,7 +100,9 @@ export const SuspenseImpl = {
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
if (parentSuspense && parentSuspense.deps > 0) {
n2.suspense = n1.suspense
n2.suspense = n1.suspense!
n2.suspense.vnode = n2
n2.el = n1.el
return
}
patchSuspense(

View File

@ -158,4 +158,13 @@ describe(`runtime-dom: style patching`, () => {
)
expect(el.style.display).toBe('flex')
})
it('should clear previous css string value', () => {
const el = document.createElement('div')
patchProp(el, 'style', {}, 'color:red')
expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
patchProp(el, 'style', 'color:red', { fontSize: '12px' })
expect(el.style.cssText.replace(/\s/g, '')).toBe('font-size:12px;')
})
})

View File

@ -209,25 +209,20 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
},
// set value in mounted & updated because <select> relies on its children
// <option>s.
mounted(el, { value, oldValue, modifiers: { number } }) {
setSelected(el, value, oldValue, number)
mounted(el, { value, modifiers: { number } }) {
setSelected(el, value, number)
},
beforeUpdate(el, _binding, vnode) {
el[assignKey] = getModelAssigner(vnode)
},
updated(el, { value, oldValue, modifiers: { number } }) {
updated(el, { value, modifiers: { number } }) {
if (!el._assigning) {
setSelected(el, value, oldValue, number)
setSelected(el, value, number)
}
},
}
function setSelected(
el: HTMLSelectElement,
value: any,
oldValue: any,
number: boolean,
) {
function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
const isMultiple = el.multiple
const isArrayValue = isArray(value)
if (isMultiple && !isArrayValue && !isSet(value)) {
@ -256,11 +251,9 @@ function setSelected(
} else {
option.selected = value.has(optionValue)
}
} else {
if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) el.selectedIndex = i
return
}
} else if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) el.selectedIndex = i
return
}
}
if (!isMultiple && el.selectedIndex !== -1) {

View File

@ -39,7 +39,8 @@ export function patchDOMProp(
el._value = value
// #4956: <option> value will fallback to its text content so we need to
// compare against its attribute value instead.
const oldValue = tag === 'OPTION' ? el.getAttribute('value') : el.value
const oldValue =
tag === 'OPTION' ? el.getAttribute('value') || '' : el.value
const newValue = value == null ? '' : value
if (oldValue !== newValue) {
el.value = newValue

View File

@ -13,10 +13,19 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
const currentDisplay = style.display
let hasControlledDisplay = false
if (next && !isCssString) {
if (prev && !isString(prev)) {
for (const key in prev) {
if (next[key] == null) {
setStyle(style, key, '')
if (prev) {
if (!isString(prev)) {
for (const key in prev) {
if (next[key] == null) {
setStyle(style, key, '')
}
}
} else {
for (const prevStyle of prev.split(';')) {
const key = prevStyle.slice(0, prevStyle.indexOf(':')).trim()
if (next[key] == null) {
setStyle(style, key, '')
}
}
}
}

View File

@ -13,7 +13,7 @@
"vite": "^5.0.12"
},
"dependencies": {
"@vue/repl": "^4.1.0",
"@vue/repl": "^4.1.1",
"file-saver": "^2.0.5",
"jszip": "^3.10.1",
"vue": "workspace:*"

View File

@ -12,7 +12,7 @@
},
"dependencies": {
"@vue/compiler-vapor": "workspace:^",
"monaco-editor": "^0.45.0",
"monaco-editor": "^0.46.0",
"source-map-js": "^1.0.2"
}
}

View File

@ -374,8 +374,8 @@ importers:
packages/sfc-playground:
dependencies:
'@vue/repl':
specifier: ^4.1.0
version: 4.1.0
specifier: ^4.1.1
version: 4.1.1
file-saver:
specifier: ^2.0.5
version: 2.0.5
@ -401,8 +401,8 @@ importers:
specifier: workspace:^
version: link:../compiler-vapor
monaco-editor:
specifier: ^0.45.0
version: 0.45.0
specifier: ^0.46.0
version: 0.46.0
source-map-js:
specifier: ^1.0.2
version: 1.0.2
@ -1896,8 +1896,8 @@ packages:
engines: {node: '>= 0.12.0'}
dev: true
/@vue/repl@4.1.0:
resolution: {integrity: sha512-4ZNEQWlLjl1Sq+WFiACm5siMdwUAmmqOES4XDgZRRFYeeW/BfabO9I6fpU+Y0zO9HFzKb8dwUUH0e0LK7mIYeg==}
/@vue/repl@4.1.1:
resolution: {integrity: sha512-gkbnU+rM01/ILdnDJbsWS8+PW6qMAzprBo/U2+7eVci0kx6VAR26fL/qrcEPwEYa6q0vzzptZ4il0SaUGGqZKw==}
dev: false
/@vueuse/core@10.7.2(vue@packages+vue):
@ -4415,8 +4415,8 @@ packages:
ufo: 1.3.1
dev: true
/monaco-editor@0.45.0:
resolution: {integrity: sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==}
/monaco-editor@0.46.0:
resolution: {integrity: sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ==}
dev: false
/mrmime@1.0.1: