From 64029b4a5451a504f71940e376919e39ed295e84 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 8 Nov 2018 18:20:07 -0500 Subject: [PATCH] feat: detailed info in renderTriggered + hint for skipping slot updates --- packages/runtime-core/src/componentUtils.ts | 44 +++++++++++++++++++-- packages/runtime-core/src/h.ts | 3 +- packages/runtime-core/src/vdom.ts | 8 ++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/runtime-core/src/componentUtils.ts b/packages/runtime-core/src/componentUtils.ts index 0ad7caf9..7115205f 100644 --- a/packages/runtime-core/src/componentUtils.ts +++ b/packages/runtime-core/src/componentUtils.ts @@ -204,9 +204,7 @@ export function shouldUpdateComponent( const { data: prevProps, childFlags: prevChildFlags } = prevVNode const { data: nextProps, childFlags: nextChildFlags } = nextVNode // If has different slots content, or has non-compiled slots, - // the child needs to be force updated. It's ok to call $forceUpdate - // again even if props update has already queued an update, as the - // scheduler will not queue the same update twice. + // the child needs to be force updated. if ( prevChildFlags !== nextChildFlags || (nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0 @@ -235,11 +233,49 @@ export function shouldUpdateComponent( return false } +// DEV only export function getReasonForComponentUpdate( prevVNode: VNode, nextVNode: VNode ): any { - // TODO: extract more detailed information on why the component is updating + const reasons = [] + const { childFlags: prevChildFlags } = prevVNode + const { childFlags: nextChildFlags } = nextVNode + if ( + prevChildFlags !== nextChildFlags || + (nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0 + ) { + reasons.push({ + type: `slots may have changed`, + tip: `use function slots + $stable: true to avoid slot-triggered child updates.` + }) + } + const prevProps = prevVNode.data || EMPTY_OBJ + const nextProps = nextVNode.data || EMPTY_OBJ + for (const key in nextProps) { + if (nextProps[key] !== prevProps[key]) { + reasons.push({ + type: 'prop changed', + key, + value: nextProps[key], + oldValue: prevProps[key] + }) + } + } + for (const key in prevProps) { + if (!(key in nextProps)) { + reasons.push({ + type: 'prop changed', + key, + value: undefined, + oldValue: prevProps[key] + }) + } + } + return { + type: 'triggered by parent', + reasons + } } export function createComponentClassFromOptions( diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index b9b43b2b..627ea0fa 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -22,7 +22,8 @@ export const Portal = Symbol() type RawChildType = VNode | string | number | boolean | null | undefined export type RawSlots = { - [name: string]: () => RawChildrenType + $stable?: boolean + [name: string]: RawChildType | (() => RawChildrenType) } export type RawChildrenType = RawChildType | RawChildType[] diff --git a/packages/runtime-core/src/vdom.ts b/packages/runtime-core/src/vdom.ts index 451a67ad..be222b67 100644 --- a/packages/runtime-core/src/vdom.ts +++ b/packages/runtime-core/src/vdom.ts @@ -10,6 +10,7 @@ import { RawChildrenType, RawSlots } from './h' import { FunctionalHandle } from './createRenderer' const handlersRE = /^on|^vnode/ +const STABLE_SLOTS_HINT = '$stable' // Vue core is platform agnostic, so we are not using Element for "DOM" nodes. export interface RenderNode { @@ -226,6 +227,10 @@ export function createComponentVNode( } else if (isObject(children) && !(children as any)._isVNode) { // slot object as children slots = children + // special manual optimization hint for raw render fn users + if (slots[STABLE_SLOTS_HINT]) { + childFlags = ChildrenFlags.STABLE_SLOTS + } } else { slots = { default: () => children } } @@ -418,6 +423,9 @@ function normalizeSlots(slots: { [name: string]: any }): Slots { } const normalized = { _normalized: true } as any for (const name in slots) { + if (name === STABLE_SLOTS_HINT) { + continue + } normalized[name] = (...args: any[]) => normalizeSlot(slots[name](...args)) } return normalized