feat: warning traces & error handling for functional render
This commit is contained in:
parent
3a7bbecb22
commit
5327abb249
@ -42,7 +42,6 @@ export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
|
|||||||
|
|
||||||
data?(): Partial<D>
|
data?(): Partial<D>
|
||||||
render(props: Readonly<P>, slots: Slots, attrs: Data): any
|
render(props: Readonly<P>, slots: Slots, attrs: Data): any
|
||||||
renderError?(e: Error): any
|
|
||||||
renderTracked?(e: DebuggerEvent): void
|
renderTracked?(e: DebuggerEvent): void
|
||||||
renderTriggered?(e: DebuggerEvent): void
|
renderTriggered?(e: DebuggerEvent): void
|
||||||
beforeCreate?(): void
|
beforeCreate?(): void
|
||||||
@ -56,7 +55,8 @@ export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
|
|||||||
errorCaptured?(): (
|
errorCaptured?(): (
|
||||||
err: Error,
|
err: Error,
|
||||||
type: ErrorTypes,
|
type: ErrorTypes,
|
||||||
target: ComponentInstance
|
instance: ComponentInstance | null,
|
||||||
|
vnode: VNode
|
||||||
) => boolean | void
|
) => boolean | void
|
||||||
activated?(): void
|
activated?(): void
|
||||||
deactivated?(): void
|
deactivated?(): void
|
||||||
|
@ -2,10 +2,15 @@ import { VNodeFlags } from './flags'
|
|||||||
import { EMPTY_OBJ } from './utils'
|
import { EMPTY_OBJ } from './utils'
|
||||||
import { h } from './h'
|
import { h } from './h'
|
||||||
import { VNode, MountedVNode, createFragment } from './vdom'
|
import { VNode, MountedVNode, createFragment } from './vdom'
|
||||||
import { Component, ComponentInstance, ComponentClass } from './component'
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentInstance,
|
||||||
|
ComponentClass,
|
||||||
|
FunctionalComponent
|
||||||
|
} from './component'
|
||||||
import { createTextVNode, cloneVNode } from './vdom'
|
import { createTextVNode, cloneVNode } from './vdom'
|
||||||
import { initializeState } from './componentState'
|
import { initializeState } from './componentState'
|
||||||
import { initializeProps } from './componentProps'
|
import { initializeProps, resolveProps } from './componentProps'
|
||||||
import {
|
import {
|
||||||
initializeComputed,
|
initializeComputed,
|
||||||
resolveComputedOptions,
|
resolveComputedOptions,
|
||||||
@ -104,19 +109,24 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
|||||||
instance.$slots,
|
instance.$slots,
|
||||||
instance.$attrs
|
instance.$attrs
|
||||||
)
|
)
|
||||||
} catch (e1) {
|
} catch (err) {
|
||||||
handleError(e1, instance, ErrorTypes.RENDER)
|
handleError(err, instance, ErrorTypes.RENDER)
|
||||||
if (__DEV__ && instance.renderError) {
|
|
||||||
try {
|
|
||||||
vnode = instance.renderError.call(instance.$proxy, e1)
|
|
||||||
} catch (e2) {
|
|
||||||
handleError(e2, instance, ErrorTypes.RENDER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return normalizeComponentRoot(vnode, instance.$parentVNode)
|
return normalizeComponentRoot(vnode, instance.$parentVNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function renderFunctionalRoot(vnode: VNode): VNode {
|
||||||
|
const render = vnode.tag as FunctionalComponent
|
||||||
|
const { props, attrs } = resolveProps(vnode.data, render.props)
|
||||||
|
let subTree
|
||||||
|
try {
|
||||||
|
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs || EMPTY_OBJ)
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, vnode, ErrorTypes.RENDER)
|
||||||
|
}
|
||||||
|
return normalizeComponentRoot(subTree, vnode)
|
||||||
|
}
|
||||||
|
|
||||||
export function teardownComponentInstance(instance: ComponentInstance) {
|
export function teardownComponentInstance(instance: ComponentInstance) {
|
||||||
if (instance._unmounted) {
|
if (instance._unmounted) {
|
||||||
return
|
return
|
||||||
@ -132,7 +142,7 @@ export function teardownComponentInstance(instance: ComponentInstance) {
|
|||||||
teardownWatch(instance)
|
teardownWatch(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeComponentRoot(
|
function normalizeComponentRoot(
|
||||||
vnode: any,
|
vnode: any,
|
||||||
componentVNode: VNode | null
|
componentVNode: VNode | null
|
||||||
): VNode {
|
): VNode {
|
||||||
|
@ -16,15 +16,16 @@ import {
|
|||||||
FunctionalComponent,
|
FunctionalComponent,
|
||||||
ComponentClass
|
ComponentClass
|
||||||
} from './component'
|
} from './component'
|
||||||
import { updateProps, resolveProps } from './componentProps'
|
import { updateProps } from './componentProps'
|
||||||
import {
|
import {
|
||||||
renderInstanceRoot,
|
renderInstanceRoot,
|
||||||
|
renderFunctionalRoot,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
teardownComponentInstance,
|
teardownComponentInstance,
|
||||||
normalizeComponentRoot,
|
|
||||||
shouldUpdateFunctionalComponent
|
shouldUpdateFunctionalComponent
|
||||||
} from './componentUtils'
|
} from './componentUtils'
|
||||||
import { KeepAliveSymbol } from './optional/keepAlive'
|
import { KeepAliveSymbol } from './optional/keepAlive'
|
||||||
|
import { pushContext, popContext } from './warning'
|
||||||
|
|
||||||
interface NodeOps {
|
interface NodeOps {
|
||||||
createElement: (tag: string, isSVG?: boolean) => any
|
createElement: (tag: string, isSVG?: boolean) => any
|
||||||
@ -118,10 +119,8 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
const { flags } = vnode
|
const { flags } = vnode
|
||||||
if (flags & VNodeFlags.ELEMENT) {
|
if (flags & VNodeFlags.ELEMENT) {
|
||||||
mountElement(vnode, container, contextVNode, isSVG, endNode)
|
mountElement(vnode, container, contextVNode, isSVG, endNode)
|
||||||
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
} else if (flags & VNodeFlags.COMPONENT) {
|
||||||
mountStatefulComponent(vnode, container, contextVNode, isSVG, endNode)
|
mountComponent(vnode, container, contextVNode, isSVG, endNode)
|
||||||
} else if (flags & VNodeFlags.COMPONENT_FUNCTIONAL) {
|
|
||||||
mountFunctionalComponent(vnode, container, contextVNode, isSVG, endNode)
|
|
||||||
} else if (flags & VNodeFlags.TEXT) {
|
} else if (flags & VNodeFlags.TEXT) {
|
||||||
mountText(vnode, container, endNode)
|
mountText(vnode, container, endNode)
|
||||||
} else if (flags & VNodeFlags.FRAGMENT) {
|
} else if (flags & VNodeFlags.FRAGMENT) {
|
||||||
@ -198,7 +197,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountStatefulComponent(
|
function mountComponent(
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
container: RenderNode | null,
|
container: RenderNode | null,
|
||||||
contextVNode: MountedVNode | null,
|
contextVNode: MountedVNode | null,
|
||||||
@ -206,6 +205,27 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
endNode: RenderNode | null
|
endNode: RenderNode | null
|
||||||
) {
|
) {
|
||||||
vnode.contextVNode = contextVNode
|
vnode.contextVNode = contextVNode
|
||||||
|
if (__DEV__) {
|
||||||
|
pushContext(vnode)
|
||||||
|
}
|
||||||
|
const { flags } = vnode
|
||||||
|
if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
||||||
|
mountStatefulComponent(vnode, container, contextVNode, isSVG, endNode)
|
||||||
|
} else {
|
||||||
|
mountFunctionalComponent(vnode, container, contextVNode, isSVG, endNode)
|
||||||
|
}
|
||||||
|
if (__DEV__) {
|
||||||
|
popContext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountStatefulComponent(
|
||||||
|
vnode: VNode,
|
||||||
|
container: RenderNode | null,
|
||||||
|
contextVNode: MountedVNode | null,
|
||||||
|
isSVG: boolean,
|
||||||
|
endNode: RenderNode | null
|
||||||
|
) {
|
||||||
if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
|
if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
|
||||||
// kept-alive
|
// kept-alive
|
||||||
activateComponentInstance(vnode, container, endNode)
|
activateComponentInstance(vnode, container, endNode)
|
||||||
@ -228,14 +248,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG: boolean,
|
isSVG: boolean,
|
||||||
endNode: RenderNode | null
|
endNode: RenderNode | null
|
||||||
) {
|
) {
|
||||||
vnode.contextVNode = contextVNode
|
const subTree = (vnode.children = renderFunctionalRoot(vnode))
|
||||||
const { tag, data, slots } = vnode
|
|
||||||
const render = tag as FunctionalComponent
|
|
||||||
const { props, attrs } = resolveProps(data, render.props)
|
|
||||||
const subTree = (vnode.children = normalizeComponentRoot(
|
|
||||||
render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ),
|
|
||||||
vnode
|
|
||||||
))
|
|
||||||
mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
|
mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
|
||||||
vnode.el = subTree.el as RenderNode
|
vnode.el = subTree.el as RenderNode
|
||||||
}
|
}
|
||||||
@ -443,6 +456,9 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
contextVNode: MountedVNode | null,
|
contextVNode: MountedVNode | null,
|
||||||
isSVG: boolean
|
isSVG: boolean
|
||||||
) {
|
) {
|
||||||
|
if (__DEV__) {
|
||||||
|
pushContext(nextVNode)
|
||||||
|
}
|
||||||
nextVNode.contextVNode = contextVNode
|
nextVNode.contextVNode = contextVNode
|
||||||
const { tag, flags } = nextVNode
|
const { tag, flags } = nextVNode
|
||||||
if (tag !== prevVNode.tag) {
|
if (tag !== prevVNode.tag) {
|
||||||
@ -458,6 +474,9 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG
|
isSVG
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (__DEV__) {
|
||||||
|
popContext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
||||||
@ -512,11 +531,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
const { props, attrs } = resolveProps(nextData, render.props)
|
const nextTree = (nextVNode.children = renderFunctionalRoot(nextVNode))
|
||||||
const nextTree = (nextVNode.children = normalizeComponentRoot(
|
|
||||||
render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ),
|
|
||||||
nextVNode
|
|
||||||
))
|
|
||||||
patch(prevTree, nextTree, container, nextVNode as MountedVNode, isSVG)
|
patch(prevTree, nextTree, container, nextVNode as MountedVNode, isSVG)
|
||||||
nextVNode.el = nextTree.el
|
nextVNode.el = nextTree.el
|
||||||
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
|
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
|
||||||
@ -630,7 +645,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG: boolean
|
isSVG: boolean
|
||||||
) {
|
) {
|
||||||
const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
|
const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
|
||||||
reinsertVNode(prevVNode, container)
|
removeVNode(prevVNode, container)
|
||||||
mount(nextVNode, container, contextVNode, isSVG, refNode)
|
mount(nextVNode, container, contextVNode, isSVG, refNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,10 +672,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
case ChildrenFlags.NO_CHILDREN:
|
case ChildrenFlags.NO_CHILDREN:
|
||||||
reinsertVNode(prevChildren as MountedVNode, container)
|
removeVNode(prevChildren as MountedVNode, container)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
reinsertVNode(prevChildren as MountedVNode, container)
|
removeVNode(prevChildren as MountedVNode, container)
|
||||||
mountArrayChildren(
|
mountArrayChildren(
|
||||||
nextChildren as VNode[],
|
nextChildren as VNode[],
|
||||||
container,
|
container,
|
||||||
@ -781,7 +796,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
} else if (prevLength > nextLength) {
|
} else if (prevLength > nextLength) {
|
||||||
for (i = commonLength; i < prevLength; i++) {
|
for (i = commonLength; i < prevLength; i++) {
|
||||||
reinsertVNode(prevChildren[i], container)
|
removeVNode(prevChildren[i], container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -856,7 +871,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
} else if (j > nextEnd) {
|
} else if (j > nextEnd) {
|
||||||
while (j <= prevEnd) {
|
while (j <= prevEnd) {
|
||||||
reinsertVNode(prevChildren[j++], container)
|
removeVNode(prevChildren[j++], container)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let prevStart = j
|
let prevStart = j
|
||||||
@ -885,7 +900,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (canRemoveWholeContent) {
|
if (canRemoveWholeContent) {
|
||||||
canRemoveWholeContent = false
|
canRemoveWholeContent = false
|
||||||
while (i > prevStart) {
|
while (i > prevStart) {
|
||||||
reinsertVNode(prevChildren[prevStart++], container)
|
removeVNode(prevChildren[prevStart++], container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pos > j) {
|
if (pos > j) {
|
||||||
@ -902,10 +917,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canRemoveWholeContent && j > nextEnd) {
|
if (!canRemoveWholeContent && j > nextEnd) {
|
||||||
reinsertVNode(prevVNode, container)
|
removeVNode(prevVNode, container)
|
||||||
}
|
}
|
||||||
} else if (!canRemoveWholeContent) {
|
} else if (!canRemoveWholeContent) {
|
||||||
reinsertVNode(prevVNode, container)
|
removeVNode(prevVNode, container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -927,7 +942,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (canRemoveWholeContent) {
|
if (canRemoveWholeContent) {
|
||||||
canRemoveWholeContent = false
|
canRemoveWholeContent = false
|
||||||
while (i > prevStart) {
|
while (i > prevStart) {
|
||||||
reinsertVNode(prevChildren[prevStart++], container)
|
removeVNode(prevChildren[prevStart++], container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nextVNode = nextChildren[j]
|
nextVNode = nextChildren[j]
|
||||||
@ -943,10 +958,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
patch(prevVNode, nextVNode, container, contextVNode, isSVG)
|
patch(prevVNode, nextVNode, container, contextVNode, isSVG)
|
||||||
patched++
|
patched++
|
||||||
} else if (!canRemoveWholeContent) {
|
} else if (!canRemoveWholeContent) {
|
||||||
reinsertVNode(prevVNode, container)
|
removeVNode(prevVNode, container)
|
||||||
}
|
}
|
||||||
} else if (!canRemoveWholeContent) {
|
} else if (!canRemoveWholeContent) {
|
||||||
reinsertVNode(prevVNode, container)
|
removeVNode(prevVNode, container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1074,7 +1089,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
|
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
|
||||||
reinsertVNode(children as MountedVNode, vnode.tag as RenderNode)
|
removeVNode(children as MountedVNode, vnode.tag as RenderNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ref) {
|
if (ref) {
|
||||||
@ -1096,21 +1111,21 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reinsertVNode(vnode: MountedVNode, container: RenderNode) {
|
function removeVNode(vnode: MountedVNode, container: RenderNode) {
|
||||||
unmount(vnode)
|
unmount(vnode)
|
||||||
const { el, flags, children, childFlags } = vnode
|
const { el, flags, children, childFlags } = vnode
|
||||||
if (container && el) {
|
if (container && el) {
|
||||||
if (flags & VNodeFlags.FRAGMENT) {
|
if (flags & VNodeFlags.FRAGMENT) {
|
||||||
switch (childFlags) {
|
switch (childFlags) {
|
||||||
case ChildrenFlags.SINGLE_VNODE:
|
case ChildrenFlags.SINGLE_VNODE:
|
||||||
reinsertVNode(children as MountedVNode, container)
|
removeVNode(children as MountedVNode, container)
|
||||||
break
|
break
|
||||||
case ChildrenFlags.NO_CHILDREN:
|
case ChildrenFlags.NO_CHILDREN:
|
||||||
platformRemoveChild(container, el)
|
platformRemoveChild(container, el)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
for (let i = 0; i < (children as MountedVNode[]).length; i++) {
|
for (let i = 0; i < (children as MountedVNode[]).length; i++) {
|
||||||
reinsertVNode((children as MountedVNode[])[i], container)
|
removeVNode((children as MountedVNode[])[i], container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1130,7 +1145,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
platformClearContent(container)
|
platformClearContent(container)
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
reinsertVNode(children[i], container)
|
removeVNode(children[i], container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1220,6 +1235,9 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
instance: ComponentInstance,
|
instance: ComponentInstance,
|
||||||
isSVG: boolean
|
isSVG: boolean
|
||||||
) {
|
) {
|
||||||
|
if (__DEV__ && instance.$parentVNode) {
|
||||||
|
pushContext(instance.$parentVNode as VNode)
|
||||||
|
}
|
||||||
const prevVNode = instance.$vnode
|
const prevVNode = instance.$vnode
|
||||||
|
|
||||||
if (instance.beforeUpdate) {
|
if (instance.beforeUpdate) {
|
||||||
@ -1275,6 +1293,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && instance.$parentVNode) {
|
||||||
|
popContext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmountComponentInstance(instance: ComponentInstance) {
|
function unmountComponentInstance(instance: ComponentInstance) {
|
||||||
@ -1380,7 +1402,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
patch(prevVNode, vnode, container, null, false)
|
patch(prevVNode, vnode, container, null, false)
|
||||||
container.vnode = vnode
|
container.vnode = vnode
|
||||||
} else {
|
} else {
|
||||||
reinsertVNode(prevVNode, container)
|
removeVNode(prevVNode, container)
|
||||||
container.vnode = null
|
container.vnode = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
|
import { warn } from './warning'
|
||||||
|
import { VNode } from './vdom'
|
||||||
|
import { VNodeFlags } from './flags'
|
||||||
|
|
||||||
export const enum ErrorTypes {
|
export const enum ErrorTypes {
|
||||||
BEFORE_CREATE = 1,
|
BEFORE_CREATE = 1,
|
||||||
@ -11,7 +14,6 @@ export const enum ErrorTypes {
|
|||||||
DESTROYED,
|
DESTROYED,
|
||||||
ERROR_CAPTURED,
|
ERROR_CAPTURED,
|
||||||
RENDER,
|
RENDER,
|
||||||
RENDER_ERROR,
|
|
||||||
WATCH_CALLBACK,
|
WATCH_CALLBACK,
|
||||||
NATIVE_EVENT_HANDLER,
|
NATIVE_EVENT_HANDLER,
|
||||||
COMPONENT_EVENT_HANDLER
|
COMPONENT_EVENT_HANDLER
|
||||||
@ -28,7 +30,6 @@ const ErrorTypeStrings: Record<number, string> = {
|
|||||||
[ErrorTypes.DESTROYED]: 'destroyed lifecycle hook',
|
[ErrorTypes.DESTROYED]: 'destroyed lifecycle hook',
|
||||||
[ErrorTypes.ERROR_CAPTURED]: 'errorCaptured lifecycle hook',
|
[ErrorTypes.ERROR_CAPTURED]: 'errorCaptured lifecycle hook',
|
||||||
[ErrorTypes.RENDER]: 'render function',
|
[ErrorTypes.RENDER]: 'render function',
|
||||||
[ErrorTypes.RENDER_ERROR]: 'renderError function',
|
|
||||||
[ErrorTypes.WATCH_CALLBACK]: 'watcher callback',
|
[ErrorTypes.WATCH_CALLBACK]: 'watcher callback',
|
||||||
[ErrorTypes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
[ErrorTypes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
||||||
[ErrorTypes.COMPONENT_EVENT_HANDLER]: 'component event handler'
|
[ErrorTypes.COMPONENT_EVENT_HANDLER]: 'component event handler'
|
||||||
@ -36,21 +37,39 @@ const ErrorTypeStrings: Record<number, string> = {
|
|||||||
|
|
||||||
export function handleError(
|
export function handleError(
|
||||||
err: Error,
|
err: Error,
|
||||||
instance: ComponentInstance,
|
instance: ComponentInstance | VNode,
|
||||||
type: ErrorTypes
|
type: ErrorTypes
|
||||||
) {
|
) {
|
||||||
let cur = instance
|
const isFunctional = (instance as VNode)._isVNode
|
||||||
while (cur.$parent) {
|
let cur: ComponentInstance | null = null
|
||||||
cur = cur.$parent
|
if (isFunctional) {
|
||||||
|
let vnode = instance as VNode | null
|
||||||
|
while (vnode && !(vnode.flags & VNodeFlags.COMPONENT_STATEFUL)) {
|
||||||
|
vnode = vnode.contextVNode
|
||||||
|
}
|
||||||
|
if (vnode) {
|
||||||
|
cur = vnode.children as ComponentInstance
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur = (instance as ComponentInstance).$parent
|
||||||
|
}
|
||||||
|
while (cur) {
|
||||||
const handler = cur.errorCaptured
|
const handler = cur.errorCaptured
|
||||||
if (handler) {
|
if (handler) {
|
||||||
try {
|
try {
|
||||||
const captured = handler.call(cur, err, type, instance)
|
const captured = handler.call(
|
||||||
|
cur,
|
||||||
|
err,
|
||||||
|
type,
|
||||||
|
isFunctional ? null : instance,
|
||||||
|
isFunctional ? instance : (instance as ComponentInstance).$parentVNode
|
||||||
|
)
|
||||||
if (captured) return
|
if (captured) return
|
||||||
} catch (err2) {
|
} catch (err2) {
|
||||||
logError(err2, ErrorTypes.ERROR_CAPTURED)
|
logError(err2, ErrorTypes.ERROR_CAPTURED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cur = cur.$parent
|
||||||
}
|
}
|
||||||
logError(err, type)
|
logError(err, type)
|
||||||
}
|
}
|
||||||
@ -58,7 +77,7 @@ export function handleError(
|
|||||||
function logError(err: Error, type: ErrorTypes) {
|
function logError(err: Error, type: ErrorTypes) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const info = ErrorTypeStrings[type]
|
const info = ErrorTypeStrings[type]
|
||||||
console.warn(`Unhandled error${info ? ` in ${info}` : ``}:`)
|
warn(`Unhandled error${info ? ` in ${info}` : ``}`)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} else {
|
} else {
|
||||||
throw err
|
throw err
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
||||||
import { EMPTY_OBJ } from './utils'
|
import { EMPTY_OBJ } from './utils'
|
||||||
|
import { VNode } from './vdom'
|
||||||
|
|
||||||
// TODO push vnodes instead
|
let stack: VNode[] = []
|
||||||
// component vnodes get a new property (contextVNode) which points to the
|
|
||||||
// parent component (stateful or functional)
|
|
||||||
// this way we can use any component vnode to construct a trace that inludes
|
|
||||||
// functional and stateful components.
|
|
||||||
|
|
||||||
// in createRenderer, parentComponent should be replced by ctx
|
export function pushContext(vnode: VNode) {
|
||||||
// $parent logic should also accomodate
|
stack.push(vnode)
|
||||||
|
|
||||||
let stack: ComponentType[] = []
|
|
||||||
|
|
||||||
export function pushComponent(c: ComponentType) {
|
|
||||||
stack.push(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popComponent() {
|
export function popContext() {
|
||||||
stack.pop()
|
stack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,33 +18,42 @@ export function warn(msg: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getComponentTrace(): string {
|
function getComponentTrace(): string {
|
||||||
const current = stack[stack.length - 1]
|
let current: VNode | null | undefined = stack[stack.length - 1]
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return ''
|
return '\nat <Root/>'
|
||||||
}
|
}
|
||||||
// we can't just use the stack itself, because it will be incomplete
|
|
||||||
// during updates
|
// we can't just use the stack because it will be incomplete during updates
|
||||||
// check recursive
|
// that did not start from the root. Re-construct the parent chain using
|
||||||
|
// contextVNode information.
|
||||||
const normlaizedStack: Array<{
|
const normlaizedStack: Array<{
|
||||||
type: ComponentType
|
type: VNode
|
||||||
recurseCount: number
|
recurseCount: number
|
||||||
}> = []
|
}> = []
|
||||||
stack.forEach(c => {
|
|
||||||
const last = normlaizedStack[normlaizedStack.length - 1]
|
while (current) {
|
||||||
if (last && last.type === c) {
|
const last = normlaizedStack[0]
|
||||||
|
if (last && last.type === current) {
|
||||||
last.recurseCount++
|
last.recurseCount++
|
||||||
} else {
|
} else {
|
||||||
normlaizedStack.push({ type: c, recurseCount: 0 })
|
normlaizedStack.unshift({
|
||||||
|
type: current,
|
||||||
|
recurseCount: 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
current = current.contextVNode
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`\n\nfound in\n\n` +
|
`\nat ` +
|
||||||
normlaizedStack
|
normlaizedStack
|
||||||
.map(({ type, recurseCount }, i) => {
|
.map(({ type, recurseCount }, i) => {
|
||||||
const padding = i === 0 ? '---> ' : ' '.repeat(5 + i * 2)
|
const padding = i === 0 ? '' : ' '.repeat(i + 1)
|
||||||
const postfix =
|
const postfix =
|
||||||
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
|
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
|
||||||
return padding + formatComponentName(type) + postfix
|
return (
|
||||||
|
padding + formatComponentName(type.tag as ComponentType) + postfix
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
)
|
)
|
||||||
@ -66,12 +67,14 @@ function formatComponentName(c: ComponentType, includeFile?: boolean): string {
|
|||||||
let name: string
|
let name: string
|
||||||
let file: string | null = null
|
let file: string | null = null
|
||||||
|
|
||||||
if (c.prototype.render) {
|
if (c.prototype && c.prototype.render) {
|
||||||
|
// stateful
|
||||||
const cc = c as ComponentClass
|
const cc = c as ComponentClass
|
||||||
const options = cc.options || EMPTY_OBJ
|
const options = cc.options || EMPTY_OBJ
|
||||||
name = options.displayName || cc.name
|
name = options.displayName || cc.name
|
||||||
file = options.__file
|
file = options.__file
|
||||||
} else {
|
} else {
|
||||||
|
// functional
|
||||||
const fc = c as FunctionalComponent
|
const fc = c as FunctionalComponent
|
||||||
name = fc.displayName || fc.name
|
name = fc.displayName || fc.name
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user