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 { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy' import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, PropValidator } from './componentProps' import { ComponentPropsOptions, PropValidator } from './componentProps'
import { PROPS, SLOTS } from './patchFlags' import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
export type Data = { [key: string]: any } export type Data = { [key: string]: any }
@ -183,15 +183,18 @@ export function shouldUpdateComponent(
prevVNode: VNode, prevVNode: VNode,
nextVNode: VNode nextVNode: VNode
): boolean { ): boolean {
const { props: prevProps } = prevVNode const { props: prevProps, children: prevChildren } = prevVNode
const { props: nextProps, patchFlag } = nextVNode const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
if (patchFlag !== null) { if (patchFlag !== null) {
if (patchFlag & SLOTS) { if (patchFlag & DYNAMIC_SLOTS) {
// slot content that references values that might have changed, // slot content that references values that might have changed,
// e.g. in a v-for // e.g. in a v-for
return true 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[] const dynamicProps = nextVNode.dynamicProps as string[]
for (let i = 0; i < dynamicProps.length; i++) { for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i] const key = dynamicProps[i]
@ -201,7 +204,11 @@ export function shouldUpdateComponent(
} }
} }
} else { } 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) { if (prevProps === nextProps) {
return false return false
} }
@ -211,16 +218,21 @@ export function shouldUpdateComponent(
if (nextProps === null) { if (nextProps === null) {
return prevProps !== null return prevProps !== null
} }
const nextKeys = Object.keys(nextProps) return hasPropsChanged(prevProps, nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) { }
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 return true
} }
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
return true
}
}
} }
return false return false
} }

View File

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

View File

@ -29,12 +29,18 @@ export const STYLE = 1 << 2
// them faster (without having to worry about removed props) // them faster (without having to worry about removed props)
export const PROPS = 1 << 3 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 // 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 // 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 // Indicates a component with dynamic slots (e.g. slot that references a v-for
// iterated value). Components with this flag are always force updated. // iterated value, or dynamic slot names).
export const SLOTS = 1 << 6 // Components with this flag are always force updated.
export const DYNAMIC_SLOTS = 1 << 7