From e0a66d038148703b21a73ebf9853c71876578ce7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 31 May 2019 12:25:11 +0800 Subject: [PATCH] wip: shouldUpdateComponent logic for slots --- packages/runtime-core/src/component.ts | 40 ++++++---- packages/runtime-core/src/createRenderer.ts | 87 ++++++++++++--------- packages/runtime-core/src/patchFlags.ts | 16 ++-- 3 files changed, 87 insertions(+), 56 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index af5af229..496ee37c 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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 } diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 77a8ea39..deda9abb 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -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 + ) + } } } } diff --git a/packages/runtime-core/src/patchFlags.ts b/packages/runtime-core/src/patchFlags.ts index ff6c52ce..53881ad9 100644 --- a/packages/runtime-core/src/patchFlags.ts +++ b/packages/runtime-core/src/patchFlags.ts @@ -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