错误处理
在应用运行过程中,组件执行、生命周期钩子、副作用等都可能抛出异常。Vitarx 提供了分层级的错误处理机制,让你可以在不同层级捕获和处理错误,避免一个组件的异常导致整个应用崩溃。
错误处理层级
Vitarx 的错误处理分为两个层级:
- 组件级 — 通过
onError在组件内部捕获错误 - 应用级 — 通过
app.config.errorHandler全局捕获错误
错误会从发生异常的组件开始,由内向外逐层冒泡,直到被某个处理器拦截或到达应用级。
子组件 onError → 父组件 onError → ... → app.config.errorHandlerapp.config.errorHandler — 全局错误处理器
在创建应用时,可以通过 config.errorHandler 设置全局错误处理器。当错误冒泡到应用级别时,该处理器会被调用。
interface AppConfig {
errorHandler?: (error: unknown, info: ErrorInfo) => void
}error:捕获到的错误对象info:错误信息对象,包含source(错误来源)和instance(发生错误的组件实例)
import { createApp } from 'vitarx'
function App() {
return <div>Hello</div>
}
const app = createApp(App, {
errorHandler: (error, info) => {
// 全局错误处理
console.error(`[全局错误] 来源:#123;info.source}`, error)
// 上报错误到监控平台
// reportError(error, info)
}
})
app.mount('#app')如果不设置 errorHandler,Vitarx 会使用默认处理器,将错误打印到控制台。
errorHandler也可以在创建应用后直接修改:app.config.errorHandler = myHandler
onError — 组件级错误处理器
onError 在组件内部注册错误处理函数,可以捕获当前组件及其子组件抛出的错误。
declare function onError(handler: ErrorHandler): void
type ErrorHandler = (error: unknown, info: ErrorInfo) => boolean | voiderror:捕获到的错误对象info:错误信息对象,包含source(错误来源)和instance(发生错误的组件实例)- 返回值:返回
false表示错误已被处理,阻止错误继续冒泡;不返回或返回其他值则错误继续冒泡
import { onError, ref, dynamic } from 'vitarx'
function SafeComponent() {
const hasError = ref(false)
onError((error, info) => {
console.error('组件错误:', error)
console.error('错误来源:', info.source)
hasError.value = true
// 返回 false 阻止错误继续冒泡到父组件
return false
})
// 使用 dynamic 包裹条件渲染,响应式数据变化时自动更新
return dynamic(() => (hasError.value ? <div>组件出现异常,请稍后重试</div> : <div>正常内容</div>))
}
onError必须在组件函数的顶层调用,不能在异步回调中使用。
错误冒泡机制
当组件内部发生错误时,Vitarx 会按照以下顺序处理:
- 先调用当前组件的
onError处理器 - 如果
onError返回false,停止冒泡 - 如果
onError未返回false,错误冒泡到父组件的onError - 重复以上步骤,直到某个处理器返回
false或到达根组件 - 如果根组件也没有拦截,错误交给
app.config.errorHandler处理
子组件 A 抛出错误
→ 子组件 A 的 onError(未拦截)
→ 父组件 B 的 onError(未拦截)
→ 根组件的 onError(未拦截)
→ app.config.errorHandlerErrorInfo 类型
ErrorInfo 是错误信息对象,包含以下字段:
interface ErrorInfo {
/** 错误来源 */
source: ErrorSource
/** 抛出异常的组件实例 */
instance: ComponentInstance
}ErrorSource 类型
ErrorSource 是一个字符串联合类型,标识错误的具体来源:
| 来源 | 说明 |
|---|---|
component:run | 执行函数组件时发生的错误 |
effect:${string} | 执行某个副作用时发生的错误 |
hook:${Lifecycle} | 执行生命周期钩子时发生的错误,如 hook:init、hook:mounted |
view:switch | 切换视图时发生的错误(DynamicView 发出) |
view:update | 更新视图时发生的错误(ElementView 发出) |
view:build | 构建视图时发生的错误(For 组件发出) |
通过 info.source 可以判断错误发生的具体位置,便于针对性处理。
常见错误处理模式
模式一:全局兜底
最简单的做法是只设置全局错误处理器,统一记录和处理所有未捕获的错误:
import { createApp } from 'vitarx'
function App() {
return <div>Hello</div>
}
const app = createApp(App, {
errorHandler: (error, info) => {
console.error(`[#123;info.source}]`, error)
}
})
app.mount('#app')模式二:错误边界
在关键组件上使用 onError 捕获子组件错误并展示降级 UI,阻止错误影响其他部分:
import { onError, ref, dynamic } from 'vitarx'
function ErrorBoundary(props: { children: any }) {
const error = ref<unknown>(null)
onError((err, info) => {
error.value = err
console.error('错误边界捕获:', err, '来源:', info.source)
// 阻止冒泡,错误到此为止
return false
})
return dynamic(() =>
error.value !== null ? (
<div class="error-fallback">
<h3>组件出现异常</h3>
<p>请刷新页面重试</p>
</div>
) : (
props.children
)
)
}
// 使用
function App() {
return (
<div>
<ErrorBoundary>
<MaybeBrokenComponent />
</ErrorBoundary>
<OtherComponent />
</div>
)
}模式三:分层处理
在组件级记录错误并展示降级 UI,同时允许错误冒泡到全局进行上报:
import { onError, ref } from 'vitarx'
function SafeSection() {
const hasError = ref(false)
onError((error, info) => {
// 展示降级 UI
hasError.value = true
// 不返回 false,让错误继续冒泡到全局处理器进行上报
})
// 使用三元表达式实现条件渲染
return <>{hasError.value ? '该区域暂时不可用' : '正常内容'}</>
}模式四:根据来源区分处理
通过 info.source 判断错误类型,进行针对性处理:
import { onError } from 'vitarx'
function MyComponent() {
onError((error, info) => {
if (info.source.startsWith('hook:')) {
// 生命周期钩子错误
console.error('生命周期错误:', error)
} else if (info.source.startsWith('effect:')) {
// 副作用错误
console.error('副作用错误:', error)
} else {
// 其他错误
console.error('未知错误:', error)
}
return false
})
return <div>内容</div>
}可捕获与不可捕获的错误
onError 和 app.config.errorHandler 只能捕获框架执行上下文中发生的错误。以下场景的错误可以被捕获:
- 组件函数执行时(
component:run) - 副作用执行时(
effect:*) - 生命周期钩子执行时(
hook:*) - 视图更新时(
view:update) - 视图切换时(
view:switch) - 视图构建时(
view:build)
以下场景的错误不会被捕获:
- DOM 事件处理器(如
onClick、onInput中的异常) setTimeout、setInterval等异步回调Promise的.then()/.catch()回调- 原生事件监听器(
addEventListener)
对于这些场景,你需要自行使用 try/catch 处理:
function MyComponent() {
const handleClick = () => {
try {
// 可能出错的操作
riskyOperation()
} catch (e) {
console.error('事件处理出错:', e)
}
}
return <button onClick={handleClick}>点击</button>
}完整示例
下面是一个展示错误处理完整流程的示例,包含组件级捕获、错误冒泡和全局处理:
import { createApp, onError, onMounted, ref, dynamic } from 'vitarx'
// 可能出错的子组件——在生命周期钩子中模拟错误
function BuggyComponent() {
const data = ref<string | null>(null)
onMounted(() => {
// 模拟挂载后数据加载失败
throw new Error('数据加载失败!')
})
return <div>{data.value ?? '加载中...'}</div>
}
// 错误边界组件 — 捕获子组件错误并展示降级 UI
function ErrorBoundary(props: { children: any }) {
const error = ref<unknown>(null)
onError((err, info) => {
error.value = err
console.warn('[ErrorBoundary] 捕获到错误:', err)
console.warn('[ErrorBoundary] 错误来源:', info.source)
// 阻止冒泡,不让错误传播到更上层
return false
})
return dynamic(() =>
error.value !== null ? (
<div class="error-fallback">
<p>子组件发生异常,已降级显示</p>
<button
onClick={() => {
error.value = null
}}
>
重试
</button>
</div>
) : (
props.children
)
)
}
// 根组件
function App() {
return (
<div>
<h1>错误处理示例</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
<p>组件挂载时会触发错误</p>
</div>
)
}
// 创建应用,设置全局错误处理器
const app = createApp(App, {
errorHandler: (error, info) => {
console.error('[全局错误处理器]', error)
console.error('[全局错误处理器] 来源:', info.source)
// 在这里可以将错误上报到监控平台
}
})
app.mount('#app')