mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
5819dc9001
|
@ -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. ` +
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -43,6 +43,7 @@ export {
|
|||
type WritableComputedOptions,
|
||||
type ComputedGetter,
|
||||
type ComputedSetter,
|
||||
type ComputedRefImpl,
|
||||
} from './computed'
|
||||
export { deferredComputed } from './deferredComputed'
|
||||
export {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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[] = []
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:*"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue