diff --git a/packages/observer/src/effect.ts b/packages/observer/src/effect.ts index ea136aed..e7438f52 100644 --- a/packages/observer/src/effect.ts +++ b/packages/observer/src/effect.ts @@ -70,10 +70,13 @@ function run(effect: ReactiveEffect, fn: Function, args: any[]): any { } export function cleanup(effect: ReactiveEffect) { - for (let i = 0; i < effect.deps.length; i++) { - effect.deps[i].delete(effect) + const { deps } = effect + if (deps.length) { + for (let i = 0; i < deps.length; i++) { + deps[i].delete(effect) + } + deps.length = 0 } - effect.deps.length = 0 } export function track( diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index d3450081..4a36227c 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -5,11 +5,12 @@ import { observable, immutable } from '@vue/observer' -import { isFunction, EMPTY_OBJ } from '@vue/shared' +import { EMPTY_OBJ } from '@vue/shared' import { RenderProxyHandlers } from './componentProxy' import { ComponentPropsOptions, ExtractPropTypes } from './componentProps' import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags' import { Slots } from './componentSlots' +import { STATEFUL_COMPONENT } from './shapeFlags' export type Data = { [key: string]: any } @@ -148,16 +149,17 @@ export function setupStatefulComponent(instance: ComponentInstance) { } export function renderComponentRoot(instance: ComponentInstance): VNode { - const { type: Component, renderProxy } = instance - if (isFunction(Component)) { - return normalizeVNode(Component(instance)) - } else { - if (__DEV__ && !Component.render) { + const { type: Component, vnode } = instance + if (vnode.shapeFlag & STATEFUL_COMPONENT) { + if (__DEV__ && !(Component as any).render) { // TODO warn missing render } return normalizeVNode( - (Component.render as Function).call(renderProxy, instance) + (Component as any).render.call(instance.renderProxy, instance) ) + } else { + // functional + return normalizeVNode((Component as FunctionalComponent)(instance)) } } diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index de0a3d8e..186dfb77 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -14,14 +14,7 @@ import { createComponentInstance, setupStatefulComponent } from './component' -import { - isString, - isArray, - isFunction, - isObject, - EMPTY_OBJ, - EMPTY_ARR -} from '@vue/shared' +import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared' import { TEXT, CLASS, @@ -35,6 +28,7 @@ import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { effect, stop, ReactiveEffectOptions } from '@vue/observer' import { resolveProps } from './componentProps' import { resolveSlots } from './componentSlots' +import { ELEMENT, STATEFUL_COMPONENT, FUNCTIONAL_COMPONENT } from './shapeFlags' const prodEffectOptions = { scheduler: queueJob @@ -108,7 +102,7 @@ export function createRenderer(options: RendererOptions) { n2: VNode, container: HostNode, anchor?: HostNode, - optimized?: boolean + optimized: boolean = false ) { // patching & not same type, unmount old tree if (n1 != null && !isSameType(n1, n2)) { @@ -117,7 +111,7 @@ export function createRenderer(options: RendererOptions) { n1 = null } - const { type } = n2 + const { type, shapeFlag } = n2 switch (type) { case Text: processText(n1, n2, container, anchor) @@ -132,10 +126,14 @@ export function createRenderer(options: RendererOptions) { processPortal(n1, n2, container, anchor, optimized) break default: - if (isString(type)) { + if (shapeFlag & ELEMENT) { processElement(n1, n2, container, anchor, optimized) } else { - if (__DEV__ && !isFunction(type) && !isObject(type)) { + if ( + __DEV__ && + !(shapeFlag & STATEFUL_COMPONENT) && + !(shapeFlag & FUNCTIONAL_COMPONENT) + ) { // TODO warn invalid node type debugger } @@ -453,14 +451,14 @@ export function createRenderer(options: RendererOptions) { const instance: ComponentInstance = (vnode.component = createComponentInstance( Component )) - instance.update = effect(() => { - if (!instance.vnode) { + instance.update = effect(function updateComponent() { + if (instance.vnode === null) { // initial mount instance.vnode = vnode resolveProps(instance, vnode.props, Component.props) resolveSlots(instance, vnode.children) // setup stateful - if (typeof Component === 'object') { + if (vnode.shapeFlag & STATEFUL_COMPONENT) { setupStatefulComponent(instance) } const subTree = (instance.subTree = renderComponentRoot(instance)) diff --git a/packages/runtime-core/src/shapeFlags.ts b/packages/runtime-core/src/shapeFlags.ts new file mode 100644 index 00000000..4b75428b --- /dev/null +++ b/packages/runtime-core/src/shapeFlags.ts @@ -0,0 +1,3 @@ +export const ELEMENT = 1 +export const FUNCTIONAL_COMPONENT = 1 << 1 +export const STATEFUL_COMPONENT = 1 << 2 diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 728e995e..c248a87a 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -3,6 +3,7 @@ import { ComponentInstance } from './component' import { HostNode } from './createRenderer' import { RawSlots } from './componentSlots' import { CLASS } from './patchFlags' +import { ELEMENT, FUNCTIONAL_COMPONENT, STATEFUL_COMPONENT } from './shapeFlags' export const Fragment = Symbol('Fragment') export const Text = Symbol('Text') @@ -36,6 +37,7 @@ export interface VNode { target: HostNode | null // portal target // optimization only + shapeFlag: number patchFlag: number dynamicProps: string[] | null dynamicChildren: VNode[] | null @@ -93,6 +95,15 @@ export function createVNode( ): VNode { // Allow passing 0 for props, this can save bytes on generated code. props = props || null + + const shapeFlag = isString(type) + ? ELEMENT + : isFunction(type) + ? FUNCTIONAL_COMPONENT + : isObject(type) + ? STATEFUL_COMPONENT + : 0 + const vnode: VNode = { type, props, @@ -102,6 +113,7 @@ export function createVNode( el: null, anchor: null, target: null, + shapeFlag, patchFlag, dynamicProps, dynamicChildren: null @@ -123,7 +135,12 @@ export function createVNode( // component nodes also should always be tracked, because even if the // component doesn't need to update, it needs to persist the instance on to // the next vnode so that it can be properly unmounted later. - if (shouldTrack && (patchFlag || isObject(type) || isFunction(type))) { + if ( + shouldTrack && + (patchFlag || + shapeFlag & STATEFUL_COMPONENT || + shapeFlag & FUNCTIONAL_COMPONENT) + ) { trackDynamicNode(vnode) }