源碼剖析Vue3中如何進行錯誤處理
錯誤處理
錯誤處理是框架設計的核心要素之一??蚣艿腻e誤處理好壞,直接決定用戶應用程序的健壯性以及用戶開發(fā)應用時處理錯誤的心智負擔。同時,Vue 作為一個基礎地前端框架,也提供了統(tǒng)一地全局錯誤處理接口,用戶可以通過該接口(errorhandler)注冊自定義的錯誤處理程序來全局地處理框架產(chǎn)生的所有異常。
Vue3 中提供了兩種處理異常的函數(shù),同步異常處理函數(shù)(callWithErrorHandling)和異步異常處理函數(shù)(callWithAsyncErrorHandling)。異步異常處理函數(shù)是在同步異常處理函數(shù)的基礎上實現(xiàn)的。
// packages/runtime-core/src/errorHandling.ts
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
本篇文章的源碼均摘自 Vue.js 3.2.45
從整體上來看,callWithErrorHandling 函數(shù)的實現(xiàn)是比較簡單的,就是對 try catch 的封裝。catch 捕獲到的異常統(tǒng)一由 handleError 函數(shù)來處理。
// packages/runtime-core/src/errorHandling.ts
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true
) {
// 省略其他代碼
logError(err, type, contextVNode, throwInDev)
}
function logError(
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
throwInDev = true
) {
if (__DEV__) {
// 省略其他代碼
} else {
// recover in prod to reduce the impact on end-user
console.error(err)
}
}
在 handleError 中會將捕獲到的異常輸出來。
// packages/runtime-core/src/errorHandling.ts
export function callWithAsyncErrorHandling(
fn: Function | Function[],
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
): any[] {
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args)
if (res && isPromise(res)) {
res.catch(err => {
handleError(err, instance, type)
})
}
return res
}
const values = []
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
}
return values
}
異步異常處理函數(shù)(callWithAsyncErrorHandling),是基于同步異常處理函數(shù)(callWithErrorHandling)實現(xiàn)的,如果傳入的參數(shù)是函數(shù)數(shù)組(Function[]),則會遍歷這個函數(shù)數(shù)組,遞歸調(diào)用異步異常處理函數(shù)(callWithAsyncErrorHandling),將返回的結果保留到數(shù)組中(values),最后返回這個數(shù)組。
在異步異常處理函數(shù)中(callWithAsyncErrorHandling) ,它通過判斷傳入函數(shù)的返回值(res)是否為對象,并且是否有 then 、catch 回調(diào)來判斷返回值(res)是否為 Promise 實例。這種判斷是否為 Promise 實例的方式在我們平時的開發(fā)中也可以借鑒一下。
// packages/shared/src/index.ts
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function'
export const isObject = (val: unknown): val is Record<any, any> =>
val !== null && typeof val === 'object'
// 判斷是否為 Promise 實例
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}
Vue3 還提供了 API (errorHandler)用于給用戶注冊捕獲錯誤的全局處理函數(shù),使用方法如下:
app.config.errorHandler = (err, instance, info) => {
// 處理錯誤,例如:報告給一個服務
}
該 API (errorHandler)在 handleError 函數(shù)中實現(xiàn)
// packages/runtime-core/src/errorHandling.ts
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true
) {
const contextVNode = instance ? instance.vnode : null
// 省略其他代碼
if (instance) {
// 讀取用戶配置的全局錯誤處理函數(shù)
const appErrorHandler = instance.appContext.config.errorHandler
if (appErrorHandler) {
// 如果存在用戶配置的全局錯誤處理函數(shù)則放入 callWithErrorHandling 中執(zhí)行
callWithErrorHandling(
appErrorHandler,
null,
ErrorCodes.APP_ERROR_HANDLER,
[err, exposedInstance, errorInfo]
)
return
}
}
// 省略其他代碼
}
Vue3 會判斷用戶是否配置了全局錯誤處理函數(shù),如果配置了則會丟給內(nèi)置的同步異常處理函數(shù)執(zhí)行(callWithErrorHandling)。由于用戶配置的全局錯誤處理函數(shù)執(zhí)行是給同步異常處理函數(shù)執(zhí)行的,因此,用戶在自定義全局錯誤處理函數(shù)時,要注意兼容異步錯誤的情況,即最好在自定義全局處理函數(shù)中,加上對異步錯誤代碼的處理,因為 Vue3 內(nèi)部沒法捕獲到異步的錯誤。
如果要做前端的異常監(jiān)控,我們完全可以借助 errorHandler 函數(shù),完成前端異常的上報。
Vue3 還提供了在捕獲了后代組件傳遞的錯誤時調(diào)用的生命周期鉤子(onErrorCaptured()):
function onErrorCaptured(callback: ErrorCapturedHook): void type ErrorCapturedHook = ( err: unknown, instance: ComponentPublicInstance | null, info: string ) => boolean | void
該生命周期鉤子(onErrorCaptured()) 也是在 handleError 函數(shù)實現(xiàn)
// packages/runtime-core/src/errorHandling.ts
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true
) {
const contextVNode = instance ? instance.vnode : null
if (instance) {
// 1. 獲取父組件實例
let cur = instance.parent
const exposedInstance = instance.proxy
const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
// 2. 開啟 while 循環(huán),不斷向上遍歷,取得父組件實例
while (cur) {
// 3. 從父組件實例中獲取 onErrorCaptured 生命周期鉤子
const errorCapturedHooks = cur.ec
if (errorCapturedHooks) {
// 4. 遍歷 onErrorCaptured 生命周期鉤子數(shù)組
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (
// 5. 執(zhí)行 onErrorCaptured 生命周期鉤子,
// onErrorCaptured 返回 false 則退出 while 循環(huán),
// 錯誤會停止向上傳遞
errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
) {
return
}
}
}
cur = cur.parent
}
}
}
由于 Vue3 的組件可以多次注冊同一個生命周期鉤子,所以 Vue3 內(nèi)部使用了數(shù)組來存儲 onErrorCaptured 生命周期鉤子。Vue3 內(nèi)部會使用 while 循環(huán),不斷向上遍歷取得父組件實例的 onErrorCaptured 生命周期鉤子,然后遍歷執(zhí)行 onErrorCaptured 生命周期鉤子,如果 onErrorCaptured 生命周期鉤子返回 false ,則會退出 while 循環(huán),停止向上傳遞錯誤。
錯誤碼與錯誤信息
前端在與后端進行接口聯(lián)調(diào)的時候,肯定見過各種 HTTP 的狀態(tài)碼,比如 404(無法找到資源)、500(服務器內(nèi)部錯誤)等。狀態(tài)碼的好處是可以讓用戶快速地知道當前 HTTP 請求的狀態(tài),方便用戶排查錯誤等。
因此,在 Vue 中,也定義了各種錯誤碼(狀態(tài)碼),用來區(qū)分各種類型的錯誤,方便開發(fā)者(Vue 用戶)排查錯誤。
// packages/runtime-core/src/errorHandling.ts
export const enum ErrorCodes {
SETUP_FUNCTION,
RENDER_FUNCTION,
WATCH_GETTER,
WATCH_CALLBACK,
WATCH_CLEANUP,
NATIVE_EVENT_HANDLER,
COMPONENT_EVENT_HANDLER,
VNODE_HOOK,
DIRECTIVE_HOOK,
TRANSITION_HOOK,
APP_ERROR_HANDLER,
APP_WARN_HANDLER,
FUNCTION_REF,
ASYNC_COMPONENT_LOADER,
SCHEDULER
}
// 不同的狀態(tài)碼對應不同的錯誤信息
export const ErrorTypeStrings: Record<number | string, string> = {
// 省略其他代碼
[ErrorCodes.SETUP_FUNCTION]: 'setup function',
[ErrorCodes.RENDER_FUNCTION]: 'render function',
[ErrorCodes.WATCH_GETTER]: 'watcher getter',
[ErrorCodes.WATCH_CALLBACK]: 'watcher callback',
[ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
[ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
[ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
[ErrorCodes.VNODE_HOOK]: 'vnode hook',
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
[ErrorCodes.TRANSITION_HOOK]: 'transition hook',
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
[ErrorCodes.FUNCTION_REF]: 'ref function',
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
[ErrorCodes.SCHEDULER]:
'scheduler flush. This is likely a Vue internals bug. ' +
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core'
}
Vue 會根據(jù)不同的錯誤碼取不同的錯誤信息輸出,方便用戶排查錯誤。
// packages/runtime-core/src/errorHandling.ts
// 省略了一些無關的代碼
function logError(
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
throwInDev = true
) {
if (__DEV__) {
// 根據(jù)錯誤碼取出錯誤信息輸出
const info = ErrorTypeStrings[type]
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
} else {
console.error(err)
}
}
同時為了控制生產(chǎn)環(huán)境框架的代碼體積,利用了 Tree Shaking 機制,僅在開發(fā)環(huán)境(__DEV__)輸出內(nèi)部的錯誤信息。
總結
錯誤處理是框架設計的核心要素之一,Vue3 內(nèi)部提供了兩種處理異常的函數(shù),同步異常處理函數(shù)(callWithErrorHanding)和異步異常處理函數(shù)(callWithErrorHanding),異步異常處理函數(shù)、給用戶自定義的全局錯誤處理函數(shù)、捕獲了子組件異常后調(diào)用的生命周期鉤子(onErrorCaptured)均是基于同步異常處理函數(shù)(callWithErrorHanding)實現(xiàn),而同步異常處理函數(shù)(callWithErrorHanding)本質(zhì)是對 try catch 的封裝。
Vue3 還提供了錯誤碼和對應的錯誤信息來幫助開發(fā)者(Vue 的用戶)快速地排查錯誤。
從錯誤處理到錯誤碼和錯誤信息,可以看出 Vue 的錯誤處理做得是比較完善的。
到此這篇關于源碼剖析Vue3中如何進行錯誤處理的文章就介紹到這了,更多相關Vue3錯誤處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue checkbox 全選 數(shù)據(jù)的綁定及獲取和計算方法
下面小編就為大家分享一篇vue checkbox 全選 數(shù)據(jù)的綁定及獲取和計算方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02
vue.js 1.x與2.0中js實時監(jiān)聽input值的變化
這篇文章主要介紹了vue.js 1.x與vue.js2.0中js實時監(jiān)聽input值的變化的相關資料,文中介紹的非常詳細,對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03
Vue項目開發(fā)實現(xiàn)父組件與子組件數(shù)據(jù)間的雙向綁定原理及適用場景
在 Vue.js 中,實現(xiàn)父組件與子組件數(shù)據(jù)之間的雙向綁定,可以通過以下幾種方式,下面我將介紹幾種常見的方法,并解釋它們的實現(xiàn)原理和適用場景,感興趣的朋友跟隨小編一起看看吧2024-12-12
vue+relation-graph繪制關系圖實用組件操作方法
這篇文章主要介紹了vue+relation-graph繪制關系圖實用組件操作方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
ElementUI組件Dialog彈窗再次打開表單仍顯示上次數(shù)據(jù)的問題
這篇文章主要介紹了ElementUI組件Dialog彈窗再次打開表單仍顯示上次數(shù)據(jù)的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04

