refactor: split componentRenderUtils

This commit is contained in:
Evan You 2019-09-06 11:25:11 -04:00
parent 0f25c29119
commit d87bed0138
6 changed files with 126 additions and 128 deletions

View File

@ -1,24 +1,17 @@
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode' import { VNode, VNodeChild } from './vnode'
import { ReactiveEffect, reactive, readonly } from '@vue/reactivity' import { ReactiveEffect, reactive, readonly } from '@vue/reactivity'
import { RenderProxyHandlers, ComponentRenderProxy } from './componentProxy' import { RenderProxyHandlers, ComponentRenderProxy } from './componentProxy'
import { ComponentPropsOptions } from './componentProps' import { ComponentPropsOptions } from './componentProps'
import { Slots } from './componentSlots' import { Slots } from './componentSlots'
import { PatchFlags } from './patchFlags'
import { ShapeFlags } from './shapeFlags'
import { warn } from './warning' import { warn } from './warning'
import { import {
ErrorTypes, ErrorTypes,
handleError,
callWithErrorHandling, callWithErrorHandling,
callWithAsyncErrorHandling callWithAsyncErrorHandling
} from './errorHandling' } from './errorHandling'
import { AppContext, createAppContext } from './apiApp' import { AppContext, createAppContext } from './apiApp'
import { Directive } from './directives' import { Directive } from './directives'
import { import { applyOptions, ComponentOptions } from './componentOptions'
applyOptions,
resolveAsset,
ComponentOptions
} from './componentOptions'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
isFunction, isFunction,
@ -309,109 +302,3 @@ function createSetupContext(instance: ComponentInstance): SetupContext {
} as any } as any
return __DEV__ ? Object.freeze(context) : context return __DEV__ ? Object.freeze(context) : context
} }
// mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render
export let currentRenderingInstance: ComponentInstance | null = null
export function renderComponentRoot(instance: ComponentInstance): VNode {
const {
type: Component,
vnode,
renderProxy,
props,
slots,
attrs,
emit
} = instance
let result
currentRenderingInstance = instance
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
result = normalizeVNode(
(instance.render as RenderFunction).call(renderProxy)
)
} else {
// functional
const render = Component as FunctionalComponent
result = normalizeVNode(
render.length > 1
? render(props, {
attrs,
slots,
emit
})
: render(props, null as any)
)
}
} catch (err) {
handleError(err, instance, ErrorTypes.RENDER_FUNCTION)
result = createVNode(Empty)
}
currentRenderingInstance = null
return result
}
export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode,
optimized?: boolean
): boolean {
const { props: prevProps, children: prevChildren } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
if (patchFlag) {
if (patchFlag & PatchFlags.DYNAMIC_SLOTS) {
// slot content that references values that might have changed,
// e.g. in a v-for
return true
}
if (patchFlag & PatchFlags.FULL_PROPS) {
// presence of this flag indicates props are always non-null
return hasPropsChanged(prevProps as Data, nextProps as Data)
} else if (patchFlag & PatchFlags.PROPS) {
const dynamicProps = nextVNode.dynamicProps as string[]
for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i]
if ((nextProps as any)[key] !== (prevProps as any)[key]) {
return true
}
}
}
} else if (!optimized) {
// 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
}
if (prevProps === null) {
return nextProps !== null
}
if (nextProps === null) {
return prevProps !== null
}
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
}
}
return false
}
export function resolveComponent(name: string): Component | undefined {
return resolveAsset('components', name) as any
}

View File

@ -1,7 +1,6 @@
import { import {
ComponentInstance, ComponentInstance,
Data, Data,
currentRenderingInstance,
currentInstance, currentInstance,
Component, Component,
SetupContext SetupContext
@ -35,6 +34,7 @@ import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { Directive } from './directives' import { Directive } from './directives'
import { VNodeChild } from './vnode' import { VNodeChild } from './vnode'
import { ComponentRenderProxy } from './componentProxy' import { ComponentRenderProxy } from './componentProxy'
import { currentRenderingInstance } from './componentRenderUtils'
interface ComponentOptionsBase< interface ComponentOptionsBase<
Props, Props,
@ -385,7 +385,15 @@ function applyMixins(instance: ComponentInstance, mixins: ComponentOptions[]) {
} }
} }
export function resolveAsset(type: 'components' | 'directives', name: string) { export function resolveComponent(name: string): Component | undefined {
return resolveAsset('components', name) as any
}
export function resolveDirective(name: string): Directive | undefined {
return resolveAsset('directives', name) as any
}
function resolveAsset(type: 'components' | 'directives', name: string) {
const instance = currentRenderingInstance || currentInstance const instance = currentRenderingInstance || currentInstance
if (instance) { if (instance) {
let camelized let camelized

View File

@ -0,0 +1,105 @@
import { ComponentInstance, FunctionalComponent, Data } from './component'
import { VNode, normalizeVNode, createVNode, Empty } from './vnode'
import { ShapeFlags } from './shapeFlags'
import { handleError, ErrorTypes } from './errorHandling'
import { PatchFlags } from './patchFlags'
// mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render
export let currentRenderingInstance: ComponentInstance | null = null
export function renderComponentRoot(instance: ComponentInstance): VNode {
const {
type: Component,
vnode,
renderProxy,
props,
slots,
attrs,
emit
} = instance
let result
currentRenderingInstance = instance
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
result = normalizeVNode((instance.render as Function).call(renderProxy))
} else {
// functional
const render = Component as FunctionalComponent
result = normalizeVNode(
render.length > 1
? render(props, {
attrs,
slots,
emit
})
: render(props, null as any)
)
}
} catch (err) {
handleError(err, instance, ErrorTypes.RENDER_FUNCTION)
result = createVNode(Empty)
}
currentRenderingInstance = null
return result
}
export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode,
optimized?: boolean
): boolean {
const { props: prevProps, children: prevChildren } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
if (patchFlag) {
if (patchFlag & PatchFlags.DYNAMIC_SLOTS) {
// slot content that references values that might have changed,
// e.g. in a v-for
return true
}
if (patchFlag & PatchFlags.FULL_PROPS) {
// presence of this flag indicates props are always non-null
return hasPropsChanged(prevProps as Data, nextProps as Data)
} else if (patchFlag & PatchFlags.PROPS) {
const dynamicProps = nextVNode.dynamicProps as string[]
for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i]
if ((nextProps as any)[key] !== (prevProps as any)[key]) {
return true
}
}
}
} else if (!optimized) {
// 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
}
if (prevProps === null) {
return nextProps !== null
}
if (nextProps === null) {
return prevProps !== null
}
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
}
}
return false
}

View File

@ -9,11 +9,13 @@ import {
} from './vnode' } from './vnode'
import { import {
ComponentInstance, ComponentInstance,
renderComponentRoot,
shouldUpdateComponent,
createComponentInstance, createComponentInstance,
setupStatefulComponent setupStatefulComponent
} from './component' } from './component'
import {
renderComponentRoot,
shouldUpdateComponent
} from './componentRenderUtils'
import { import {
isString, isString,
EMPTY_OBJ, EMPTY_OBJ,

View File

@ -14,10 +14,10 @@ return applyDirectives(h(comp), [
import { VNode, cloneVNode } from './vnode' import { VNode, cloneVNode } from './vnode'
import { extend, isArray, isFunction } from '@vue/shared' import { extend, isArray, isFunction } from '@vue/shared'
import { warn } from './warning' import { warn } from './warning'
import { ComponentInstance, currentRenderingInstance } from './component' import { ComponentInstance } from './component'
import { currentRenderingInstance } from './componentRenderUtils'
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling' import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
import { HostNode } from './createRenderer' import { HostNode } from './createRenderer'
import { resolveAsset } from './componentOptions'
import { ComponentRenderProxy } from './componentProxy' import { ComponentRenderProxy } from './componentProxy'
export interface DirectiveBinding { export interface DirectiveBinding {
@ -133,7 +133,3 @@ export function invokeDirectiveHook(
callWithAsyncErrorHandling(hook, instance, ErrorTypes.DIRECTIVE_HOOK, args) callWithAsyncErrorHandling(hook, instance, ErrorTypes.DIRECTIVE_HOOK, args)
} }
} }
export function resolveDirective(name: string): Directive | undefined {
return resolveAsset('directives', name) as any
}

View File

@ -36,9 +36,9 @@ export {
callWithAsyncErrorHandling callWithAsyncErrorHandling
} from './errorHandling' } from './errorHandling'
// For the compiler // For compiler generated code
export { resolveComponent } from './component' export { applyDirectives } from './directives'
export { applyDirectives, resolveDirective } from './directives' export { resolveComponent, resolveDirective } from './componentOptions'
// Types ----------------------------------------------------------------------- // Types -----------------------------------------------------------------------