Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
3c3b56ac04
|
@ -389,4 +389,20 @@ describe('compiler: transform <slot> outlets', () => {
|
|||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('dynamically named slot outlet with v-bind shorthand', () => {
|
||||
const ast = parseWithSlots(`<slot :name />`)
|
||||
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_SLOT,
|
||||
arguments: [
|
||||
`$slots`,
|
||||
{
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `name`,
|
||||
isStatic: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,12 +6,14 @@ import {
|
|||
type SlotOutletNode,
|
||||
createCallExpression,
|
||||
createFunctionExpression,
|
||||
createSimpleExpression,
|
||||
} from '../ast'
|
||||
import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'
|
||||
import { type PropsExpression, buildProps } from './transformElement'
|
||||
import { ErrorCodes, createCompilerError } from '../errors'
|
||||
import { RENDER_SLOT } from '../runtimeHelpers'
|
||||
import { camelize } from '@vue/shared'
|
||||
import { processExpression } from './transformExpression'
|
||||
|
||||
export const transformSlotOutlet: NodeTransform = (node, context) => {
|
||||
if (isSlotOutlet(node)) {
|
||||
|
@ -76,7 +78,15 @@ export function processSlotOutlet(
|
|||
}
|
||||
} else {
|
||||
if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {
|
||||
if (p.exp) slotName = p.exp
|
||||
if (p.exp) {
|
||||
slotName = p.exp
|
||||
} else if (p.arg && p.arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
const name = camelize(p.arg.content)
|
||||
slotName = p.exp = createSimpleExpression(name, false, p.arg.loc)
|
||||
if (!__BROWSER__) {
|
||||
slotName = p.exp = processExpression(p.exp, context)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
|
||||
p.arg.content = camelize(p.arg.content)
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
isReadonly,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
} from '../src'
|
||||
import { DirtyLevels } from '../src/constants'
|
||||
|
@ -481,8 +482,12 @@ describe('reactivity/computed', () => {
|
|||
c3.value
|
||||
|
||||
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
||||
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
||||
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
||||
expect(c2.effect._dirtyLevel).toBe(
|
||||
DirtyLevels.MaybeDirty_ComputedSideEffect,
|
||||
)
|
||||
expect(c3.effect._dirtyLevel).toBe(
|
||||
DirtyLevels.MaybeDirty_ComputedSideEffect,
|
||||
)
|
||||
})
|
||||
|
||||
it('should work when chained(ref+computed)', () => {
|
||||
|
@ -521,6 +526,49 @@ describe('reactivity/computed', () => {
|
|||
expect(fnSpy).toBeCalledTimes(2)
|
||||
})
|
||||
|
||||
// #10185
|
||||
it('should not override queried MaybeDirty result', () => {
|
||||
class Item {
|
||||
v = ref(0)
|
||||
}
|
||||
const v1 = shallowRef()
|
||||
const v2 = ref(false)
|
||||
const c1 = computed(() => {
|
||||
let c = v1.value
|
||||
if (!v1.value) {
|
||||
c = new Item()
|
||||
v1.value = c
|
||||
}
|
||||
return c.v.value
|
||||
})
|
||||
const c2 = computed(() => {
|
||||
if (!v2.value) return 'no'
|
||||
return c1.value ? 'yes' : 'no'
|
||||
})
|
||||
const c3 = computed(() => c2.value)
|
||||
|
||||
c3.value
|
||||
v2.value = true
|
||||
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
||||
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
||||
|
||||
c3.value
|
||||
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
||||
expect(c2.effect._dirtyLevel).toBe(
|
||||
DirtyLevels.MaybeDirty_ComputedSideEffect,
|
||||
)
|
||||
expect(c3.effect._dirtyLevel).toBe(
|
||||
DirtyLevels.MaybeDirty_ComputedSideEffect,
|
||||
)
|
||||
|
||||
v1.value.v.value = 999
|
||||
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
|
||||
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
||||
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
|
||||
|
||||
expect(c3.value).toBe('yes')
|
||||
})
|
||||
|
||||
it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
|
||||
const state = reactive<any>({})
|
||||
const consumer = computed(() => {
|
||||
|
@ -541,4 +589,26 @@ describe('reactivity/computed', () => {
|
|||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`2`)
|
||||
})
|
||||
|
||||
it('should not trigger effect scheduler by recurse computed effect', async () => {
|
||||
const v = ref('Hello')
|
||||
const c = computed(() => {
|
||||
v.value += ' World'
|
||||
return v.value
|
||||
})
|
||||
const Comp = {
|
||||
setup: () => {
|
||||
return () => c.value
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
|
||||
render(h(Comp), root)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('Hello World')
|
||||
|
||||
v.value += ' World'
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('Hello World World World World')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type DebuggerOptions, ReactiveEffect, scheduleEffects } from './effect'
|
||||
import { type DebuggerOptions, ReactiveEffect } from './effect'
|
||||
import { type Ref, trackRefValue, triggerRefValue } from './ref'
|
||||
import { NOOP, hasChanged, isFunction } from '@vue/shared'
|
||||
import { toRaw } from './reactive'
|
||||
|
@ -43,8 +43,13 @@ export class ComputedRefImpl<T> {
|
|||
) {
|
||||
this.effect = new ReactiveEffect(
|
||||
() => getter(this._value),
|
||||
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
|
||||
() => this.dep && scheduleEffects(this.dep),
|
||||
() =>
|
||||
triggerRefValue(
|
||||
this,
|
||||
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
|
||||
? DirtyLevels.MaybeDirty_ComputedSideEffect
|
||||
: DirtyLevels.MaybeDirty,
|
||||
),
|
||||
)
|
||||
this.effect.computed = this
|
||||
this.effect.active = this._cacheable = !isSSR
|
||||
|
@ -54,14 +59,15 @@ export class ComputedRefImpl<T> {
|
|||
get value() {
|
||||
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
|
||||
const self = toRaw(this)
|
||||
if (!self._cacheable || self.effect.dirty) {
|
||||
if (hasChanged(self._value, (self._value = self.effect.run()!))) {
|
||||
triggerRefValue(self, DirtyLevels.Dirty)
|
||||
}
|
||||
if (
|
||||
(!self._cacheable || self.effect.dirty) &&
|
||||
hasChanged(self._value, (self._value = self.effect.run()!))
|
||||
) {
|
||||
triggerRefValue(self, DirtyLevels.Dirty)
|
||||
}
|
||||
trackRefValue(self)
|
||||
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) {
|
||||
triggerRefValue(self, DirtyLevels.MaybeDirty)
|
||||
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
|
||||
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
|
||||
}
|
||||
return self._value
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ export enum ReactiveFlags {
|
|||
|
||||
export enum DirtyLevels {
|
||||
NotDirty = 0,
|
||||
MaybeDirty = 1,
|
||||
Dirty = 2,
|
||||
QueryingDirty = 1,
|
||||
MaybeDirty_ComputedSideEffect = 2,
|
||||
MaybeDirty = 3,
|
||||
Dirty = 4,
|
||||
}
|
||||
|
|
|
@ -76,7 +76,11 @@ export class ReactiveEffect<T = any> {
|
|||
}
|
||||
|
||||
public get dirty() {
|
||||
if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
|
||||
if (
|
||||
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
|
||||
this._dirtyLevel === DirtyLevels.MaybeDirty
|
||||
) {
|
||||
this._dirtyLevel = DirtyLevels.QueryingDirty
|
||||
pauseTracking()
|
||||
for (let i = 0; i < this._depsLength; i++) {
|
||||
const dep = this.deps[i]
|
||||
|
@ -87,7 +91,7 @@ export class ReactiveEffect<T = any> {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (this._dirtyLevel < DirtyLevels.Dirty) {
|
||||
if (this._dirtyLevel === DirtyLevels.QueryingDirty) {
|
||||
this._dirtyLevel = DirtyLevels.NotDirty
|
||||
}
|
||||
resetTracking()
|
||||
|
@ -140,7 +144,7 @@ function preCleanupEffect(effect: ReactiveEffect) {
|
|||
}
|
||||
|
||||
function postCleanupEffect(effect: ReactiveEffect) {
|
||||
if (effect.deps && effect.deps.length > effect._depsLength) {
|
||||
if (effect.deps.length > effect._depsLength) {
|
||||
for (let i = effect._depsLength; i < effect.deps.length; i++) {
|
||||
cleanupDepEffect(effect.deps[i], effect)
|
||||
}
|
||||
|
@ -291,35 +295,33 @@ export function triggerEffects(
|
|||
) {
|
||||
pauseScheduling()
|
||||
for (const effect of dep.keys()) {
|
||||
// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
|
||||
let tracking: boolean | undefined
|
||||
if (
|
||||
effect._dirtyLevel < dirtyLevel &&
|
||||
dep.get(effect) === effect._trackId
|
||||
(tracking ??= dep.get(effect) === effect._trackId)
|
||||
) {
|
||||
const lastDirtyLevel = effect._dirtyLevel
|
||||
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
|
||||
effect._dirtyLevel = dirtyLevel
|
||||
if (lastDirtyLevel === DirtyLevels.NotDirty) {
|
||||
effect._shouldSchedule = true
|
||||
if (__DEV__) {
|
||||
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
|
||||
}
|
||||
if (
|
||||
effect._shouldSchedule &&
|
||||
(tracking ??= dep.get(effect) === effect._trackId)
|
||||
) {
|
||||
if (__DEV__) {
|
||||
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
|
||||
}
|
||||
effect.trigger()
|
||||
if (
|
||||
(!effect._runnings || effect.allowRecurse) &&
|
||||
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
|
||||
) {
|
||||
effect._shouldSchedule = false
|
||||
if (effect.scheduler) {
|
||||
queueEffectSchedulers.push(effect.scheduler)
|
||||
}
|
||||
effect.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleEffects(dep)
|
||||
resetScheduling()
|
||||
}
|
||||
|
||||
export function scheduleEffects(dep: Dep) {
|
||||
for (const effect of dep.keys()) {
|
||||
if (
|
||||
effect.scheduler &&
|
||||
effect._shouldSchedule &&
|
||||
(!effect._runnings || effect.allowRecurse) &&
|
||||
dep.get(effect) === effect._trackId
|
||||
) {
|
||||
effect._shouldSchedule = false
|
||||
queueEffectSchedulers.push(effect.scheduler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,11 +49,10 @@ export function trackRefValue(ref: RefBase<any>) {
|
|||
ref = toRaw(ref)
|
||||
trackEffect(
|
||||
activeEffect,
|
||||
ref.dep ||
|
||||
(ref.dep = createDep(
|
||||
() => (ref.dep = undefined),
|
||||
ref instanceof ComputedRefImpl ? ref : undefined,
|
||||
)),
|
||||
(ref.dep ??= createDep(
|
||||
() => (ref.dep = undefined),
|
||||
ref instanceof ComputedRefImpl ? ref : undefined,
|
||||
)),
|
||||
__DEV__
|
||||
? {
|
||||
target: ref,
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
onMounted,
|
||||
ref,
|
||||
renderSlot,
|
||||
useCssVars,
|
||||
vModelCheckbox,
|
||||
vShow,
|
||||
withDirectives,
|
||||
|
@ -1538,5 +1539,20 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should not warn css v-bind', () => {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<div style="--foo:red;color:var(--foo);" />`
|
||||
const app = createSSRApp({
|
||||
setup() {
|
||||
useCssVars(() => ({
|
||||
foo: 'red',
|
||||
}))
|
||||
return () => h('div', { style: { color: 'var(--foo)' } })
|
||||
},
|
||||
})
|
||||
app.mount(container)
|
||||
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -518,6 +518,12 @@ export interface ComponentInternalInstance {
|
|||
* @internal
|
||||
*/
|
||||
ut?: (vars?: Record<string, string>) => void
|
||||
|
||||
/**
|
||||
* dev only. For style v-bind hydration mismatch checks
|
||||
* @internal
|
||||
*/
|
||||
getCssVars?: () => Record<string, string>
|
||||
}
|
||||
|
||||
const emptyAppContext = createAppContext()
|
||||
|
|
|
@ -449,7 +449,10 @@ export function createHydrationFunctions(
|
|||
) {
|
||||
for (const key in props) {
|
||||
// check hydration mismatch
|
||||
if (__DEV__ && propHasMismatch(el, key, props[key], vnode)) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
propHasMismatch(el, key, props[key], vnode, parentComponent)
|
||||
) {
|
||||
hasMismatch = true
|
||||
}
|
||||
if (
|
||||
|
@ -718,6 +721,7 @@ function propHasMismatch(
|
|||
key: string,
|
||||
clientValue: any,
|
||||
vnode: VNode,
|
||||
instance: ComponentInternalInstance | null,
|
||||
): boolean {
|
||||
let mismatchType: string | undefined
|
||||
let mismatchKey: string | undefined
|
||||
|
@ -748,6 +752,12 @@ function propHasMismatch(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cssVars = instance?.getCssVars?.()
|
||||
for (const key in cssVars) {
|
||||
expectedMap.set(`--${key}`, String(cssVars[key]))
|
||||
}
|
||||
|
||||
if (!isMapEqual(actualMap, expectedMap)) {
|
||||
mismatchType = mismatchKey = 'style'
|
||||
}
|
||||
|
|
|
@ -239,11 +239,6 @@ function setSelected(
|
|||
return
|
||||
}
|
||||
|
||||
// fast path for updates triggered by other changes
|
||||
if (isArrayValue && looseEqual(value, oldValue)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0, l = el.options.length; i < l; i++) {
|
||||
const option = el.options[i]
|
||||
const optionValue = getValue(option)
|
||||
|
|
|
@ -32,6 +32,10 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>) {
|
|||
).forEach(node => setVarsOnNode(node, vars))
|
||||
})
|
||||
|
||||
if (__DEV__) {
|
||||
instance.getCssVars = () => getter(instance.proxy)
|
||||
}
|
||||
|
||||
const setVars = () => {
|
||||
const vars = getter(instance.proxy)
|
||||
setVarsOnVNode(instance.subTree, vars)
|
||||
|
|
Loading…
Reference in New Issue