wip: shouldUpdateComponent logic for slots

This commit is contained in:
Evan You 2019-05-31 12:25:11 +08:00
parent 333ceaa4b5
commit e0a66d0381
3 changed files with 87 additions and 56 deletions

View File

@ -8,7 +8,7 @@ import {
import { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, PropValidator } from './componentProps'
import { PROPS, SLOTS } from './patchFlags'
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
export type Data = { [key: string]: any }
@ -183,15 +183,18 @@ export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode
): boolean {
const { props: prevProps } = prevVNode
const { props: nextProps, patchFlag } = nextVNode
const { props: prevProps, children: prevChildren } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
if (patchFlag !== null) {
if (patchFlag & SLOTS) {
if (patchFlag & DYNAMIC_SLOTS) {
// slot content that references values that might have changed,
// e.g. in a v-for
return true
}
if (patchFlag & PROPS) {
if (patchFlag & FULL_PROPS) {
// presence of this flag indicates props are always non-null
return hasPropsChanged(prevProps as Data, nextProps as Data)
} else if (patchFlag & PROPS) {
const dynamicProps = nextVNode.dynamicProps as string[]
for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i]
@ -201,7 +204,11 @@ export function shouldUpdateComponent(
}
}
} else {
// TODO handle slots
// this path is only taken by manually written render functions
// so presence of any children leads to a forced update
if (prevChildren != null || nextChildren != null) {
return true
}
if (prevProps === nextProps) {
return false
}
@ -211,16 +218,21 @@ export function shouldUpdateComponent(
if (nextProps === null) {
return prevProps !== null
}
const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) {
return hasPropsChanged(prevProps, nextProps)
}
return false
}
function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) {
return true
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
return true
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
return true
}
}
}
return false
}

View File

@ -22,7 +22,15 @@ import {
EMPTY_OBJ,
EMPTY_ARR
} from '@vue/shared'
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
import {
TEXT,
CLASS,
STYLE,
PROPS,
KEYED,
UNKEYED,
FULL_PROPS
} from './patchFlags'
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
import { resolveProps } from './componentProps'
@ -223,45 +231,50 @@ export function createRenderer(options: RendererOptions) {
// in this path old node and new node are guaranteed to have the same shape
// (i.e. at the exact same position in the source template)
// class
// this flag is matched when the element has dynamic class bindings.
if (patchFlag & CLASS) {
// TODO handle full class API, potentially optimize at compilation stage?
if (oldProps.class !== newProps.class) {
hostPatchProp(el, 'class', newProps.class, null, false)
if (patchFlag & FULL_PROPS) {
// element props contain dynamic keys, full diff needed
patchProps(el, n2, oldProps, newProps)
} else {
// class
// this flag is matched when the element has dynamic class bindings.
if (patchFlag & CLASS) {
// TODO handle full class API, potentially optimize at compilation stage?
if (oldProps.class !== newProps.class) {
hostPatchProp(el, 'class', newProps.class, null, false)
}
}
}
// style
// this flag is matched when the element has dynamic style bindings
// TODO separate static and dynamic styles?
if (patchFlag & STYLE) {
hostPatchProp(el, 'style', newProps.style, oldProps.style, false)
}
// style
// this flag is matched when the element has dynamic style bindings
// TODO separate static and dynamic styles?
if (patchFlag & STYLE) {
hostPatchProp(el, 'style', newProps.style, oldProps.style, false)
}
// props
// This flag is matched when the element has dynamic prop/attr bindings
// other than class and style. The keys of dynamic prop/attrs are saved for
// faster iteration.
// Note dynamic keys like :[foo]="bar" will cause this optimization to
// bail out and go through a full diff because we need to unset the old key
if (patchFlag & PROPS) {
// if the flag is present then dynamicProps must be non-null
const propsToUpdate = n2.dynamicProps as string[]
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const prev = oldProps[key]
const next = newProps[key]
if (prev !== next) {
hostPatchProp(
el,
key,
next,
prev,
false,
n1.children as VNode[],
unmountChildren
)
// props
// This flag is matched when the element has dynamic prop/attr bindings
// other than class and style. The keys of dynamic prop/attrs are saved for
// faster iteration.
// Note dynamic keys like :[foo]="bar" will cause this optimization to
// bail out and go through a full diff because we need to unset the old key
if (patchFlag & PROPS) {
// if the flag is present then dynamicProps must be non-null
const propsToUpdate = n2.dynamicProps as string[]
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const prev = oldProps[key]
const next = newProps[key]
if (prev !== next) {
hostPatchProp(
el,
key,
next,
prev,
false,
n1.children as VNode[],
unmountChildren
)
}
}
}
}

View File

@ -29,12 +29,18 @@ export const STYLE = 1 << 2
// them faster (without having to worry about removed props)
export const PROPS = 1 << 3
// Indicates an element with props with dynamic keys. When keys change, a full
// diff is always needed to remove the old key. This flag is mutually exclusive
// with CLASS, STYLE and PROPS.
export const FULL_PROPS = 1 << 4
// Indicates a fragment or element with keyed or partially-keyed v-for children
export const KEYED = 1 << 4
export const KEYED = 1 << 5
// Indicates a fragment or element that contains unkeyed v-for children
export const UNKEYED = 1 << 5
export const UNKEYED = 1 << 6
// Indicates a component with dynamic slot (e.g. slot that references a v-for
// iterated value). Components with this flag are always force updated.
export const SLOTS = 1 << 6
// Indicates a component with dynamic slots (e.g. slot that references a v-for
// iterated value, or dynamic slot names).
// Components with this flag are always force updated.
export const DYNAMIC_SLOTS = 1 << 7