您好,登錄后才能下訂單哦!
本篇內容主要講解“Vue3如何進行全局異常處理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue3如何進行全局異常處理”吧!
在開發組件庫或者插件,經常會需要進行全局異常處理,從而實現:
全局統一處理異常;
為開發者提示錯誤信息;
方案降級處理等等。
那么如何實現上面功能呢? 本文先簡單實現一個異常處理方法,然后結合 Vue3 源碼中的實現詳細介紹,最后總結實現異常處理的幾個核心。
本文 Vue3 版本為 3.0.11
對于前端來說,常見的異常比較多,比如:
JS 語法異常;
Ajax 請求異常;
靜態資源加載異常;
Promise 異常;
iframe 異常;
等等
最常用的比如:
通過 window.onerror
文檔可知,當 JS 運行時發生錯誤(包括語法錯誤),觸發 window.onerror()
:
window.onerror = function(message, source, lineno, colno, error) { console.log('捕獲到異常:',{message, source, lineno, colno, error}); }
函數參數:
message:錯誤信息(字符串)。可用于HTML onerror=""
處理程序中的 event
。
source:發生錯誤的腳本URL(字符串)
lineno:發生錯誤的行號(數字)
colno:發生錯誤的列號(數字)
error:Error對象(對象)
若該函數返回true,則阻止執行默認事件處理函數。
另外,我們也經常會使用 try...catch
語句處理異常:
try { // do something } catch (error) { console.error(error); }
更多處理方式,可以閱讀前面推薦的文章。
大家可以思考下,自己在業務開發過程中,是否也是經常要處理這些錯誤情況?
那么像 Vue3 這樣復雜的庫,是否也是到處通過 try...catch
來處理異常呢?
接下來一起看看。
在開發插件或庫時,我們可以通過 try...catch
封裝一個全局異常處理方法,將需要執行的方法作為參數傳入,調用方只要關心調用結果,而無需知道該全局異常處理方法內部邏輯。
大致使用方法如下:
const errorHandling = (fn, args) => { let result; try{ result = args ? fn(...args) : fn(); } catch (error){ console.error(error) } return result; }
測試一下:
const f1 = () => { console.log('[f1 running]') throw new Error('[f1 error!]') } errorHandling(f1); /* 輸出: [f1 running] Error: [f1 error!] at f1 (/Users/wangpingan/leo/www/node/www/a.js:14:11) at errorHandling (/Users/wangpingan/leo/www/node/www/a.js:4:39) at Object.<anonymous> (/Users/wangpingan/leo/www/node/www/a.js:17:1) at Module._compile (node:internal/modules/cjs/loader:1095:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10) at Module.load (node:internal/modules/cjs/loader:975:32) at Function.Module._load (node:internal/modules/cjs/loader:822:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) at node:internal/main/run_main_module:17:47 */
可以看到,當需要為方法做異常處理時,只要將該方法作為參數傳入即可。 但是上面示例跟實際業務開發的邏輯差得有點多,實際業務中,我們經常會遇到方法的嵌套調用,那么我們試一下:
const f1 = () => { console.log('[f1]') f2(); } const f2 = () => { console.log('[f2]') f3(); } const f3 = () => { console.log('[f3]') throw new Error('[f3 error!]') } errorHandling(f1) /* 輸出: [f1 running] [f2 running] [f3 running] Error: [f3 error!] at f3 (/Users/wangpingan/leo/www/node/www/a.js:24:11) at f2 (/Users/wangpingan/leo/www/node/www/a.js:19:5) at f1 (/Users/wangpingan/leo/www/node/www/a.js:14:5) at errorHandling (/Users/wangpingan/leo/www/node/www/a.js:4:39) at Object.<anonymous> (/Users/wangpingan/leo/www/node/www/a.js:27:1) at Module._compile (node:internal/modules/cjs/loader:1095:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10) at Module.load (node:internal/modules/cjs/loader:975:32) at Function.Module._load (node:internal/modules/cjs/loader:822:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) */
這樣也是沒問題的。那么接下來就是在 errorHandling
方法的 catch
分支實現對應異常處理即可。
接下來看看 Vue3 源碼中是如何處理的?
理解完上面示例,接下來看看在 Vue3 源碼中是如何實現異常處理的,其實現起來也是很簡單。
在 errorHandling.ts
文件中定義了 callWithErrorHandling
和 callWithAsyncErrorHandling
兩個處理全局異常的方法。
顧名思義,這兩個方法分別處理:
callWithErrorHandling
:處理同步方法的異常;
callWithAsyncErrorHandling
:處理異步方法的異常。
使用方式如下:
callWithAsyncErrorHandling( handler, instance, ErrorCodes.COMPONENT_EVENT_HANDLER, args )
代碼實現大致如下:
// 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 } // 處理異步方法的異常 export function callWithAsyncErrorHandling( fn: Function | Function[], instance: ComponentInternalInstance | null, type: ErrorTypes, args?: unknown[] ): any[] { // 省略其他代碼 const res = callWithErrorHandling(fn, instance, type, args) if (res && isPromise(res)) { res.catch(err => { handleError(err, instance, type) }) } // 省略其他代碼 }
callWithErrorHandling
方法處理的邏輯比較簡單,通過簡單的 try...catch
做一層封裝。
而 callWithAsyncErrorHandling
方法就比較巧妙,通過將需要執行的方法傳入 callWithErrorHandling
方法處理,并將其結果通過 .catch
方法進行處理。
在上面代碼中,遇到報錯的情況,都會通過 handleError()
處理異常。其實現大致如下:
// 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 ) { // 省略其他代碼 console.error(err) }
保留核心處理邏輯之后,可以看到這邊處理也是相當簡單,直接通過 console.error(err)
輸出錯誤內容。
在使用 Vue3 時,也支持指定自定義異常處理函數,來處理組件渲染函數和偵聽器執行期間拋出的未捕獲錯誤。這個處理函數被調用時,可獲取錯誤信息和相應的應用實例。
文檔參考:《errorHandler》
使用方法如下,在項目 main.js
文件中配置:
// src/main.js app.config.errorHandler = (err, vm, info) => { // 處理錯誤 // `info` 是 Vue 特定的錯誤信息,比如錯誤所在的生命周期鉤子 }
那么 errorHandler()
是何時執行的呢?我們繼續看看源碼中 handleError()
的內容,可以發現:
// 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) { // 省略其他代碼 // 讀取 errorHandler 配置項 const appErrorHandler = instance.appContext.config.errorHandler if (appErrorHandler) { callWithErrorHandling( appErrorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [err, exposedInstance, errorInfo] ) return } } logError(err, type, contextVNode, throwInDev) }
通過 instance.appContext.config.errorHandler
取到全局配置的自定義錯誤處理函數,存在時則執行,當然,這邊也是通過前面定義的 callWithErrorHandling
來調用。
在使用 Vue3 的時候,也可以通過 errorCaptured
生命周期鉤子來捕獲來自后代組件的錯誤。 如下:
(err: Error, instance: Component, info: string) => ?boolean
此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。
此鉤子可以返回 false
以阻止該錯誤繼續向上傳播。有興趣的同學可以通過文檔,查看具體的錯誤傳播規則。
使用方法如下,父組件監聽 onErrorCaptured
生命周期(示例代碼使用 Vue3 setup 語法):
<template> <Message></Message> </template> <script setup> // App.vue import { onErrorCaptured } from 'vue'; import Message from './components/Message.vue' onErrorCaptured(function(err, instance, info){ console.log('[errorCaptured]', err, instance, info) }) </script>
子組件如下:
<template> <button @click="sendMessage">發送消息</button> </template> <script setup> // Message.vue const sendMessage = () => { throw new Error('[test onErrorCaptured]') } </script>
當點擊「發送消息」按鈕,控制臺便輸出錯誤:
[errorCaptured] Error: [test onErrorCaptured] at Proxy.sendMessage (Message.vue:36:15) at _createElementVNode.onClick._cache.<computed>._cache.<computed> (Message.vue:3:39) at callWithErrorHandling (runtime-core.esm-bundler.js:6706:22) at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6715:21) at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:350:13) Proxy {sendMessage: ?, …} native event handler
可以看到 onErrorCaptured
生命周期鉤子正常執行,并輸出子組件 Message.vue
內的異常。
那么這個又是如何實現呢?還是看 errorHandling.ts
中的 handleError()
方法:
// 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) { let cur = instance.parent // the exposed instance is the render proxy to keep it consistent with 2.x const exposedInstance = instance.proxy // in production the hook receives only the error code const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type while (cur) { const errorCapturedHooks = cur.ec // ①取出組件配置的 errorCaptured 生命周期方法 if (errorCapturedHooks) { // ②循環執行 errorCaptured 中的每個 Hook for (let i = 0; i < errorCapturedHooks.length; i++) { if ( errorCapturedHooks[i](err, exposedInstance, errorInfo) === false ) { return } } } cur = cur.parent } // 省略其他代碼 } logError(err, type, contextVNode, throwInDev) }
這邊會先獲取 instance.parent
作為當前處理的組件實例進行遞歸,每次將取出組件配置的 errorCaptured
生命周期方法的數組并循環調用其每一個鉤子,然后再取出當前組件的父組件作為參數,最后繼續遞歸調用下去。
Vue3 還為異常定義了錯誤碼和錯誤信息,在不同的錯誤情況有不同的錯誤碼和錯誤信息,讓我們能很方便定位到發生異常的地方。 錯誤碼和錯誤信息如下:
// packages/runtime-core/src/errorHandling.ts export const enum ErrorCodes { SETUP_FUNCTION, RENDER_FUNCTION, WATCH_GETTER, WATCH_CALLBACK, // ... 省略其他 } export const ErrorTypeStrings: Record<number | string, string> = { // 省略其他 [LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook', [LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook', [ErrorCodes.SETUP_FUNCTION]: 'setup function', [ErrorCodes.RENDER_FUNCTION]: 'render function', // 省略其他 [ErrorCodes.SCHEDULER]: 'scheduler flush. This is likely a Vue internals bug. ' + 'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next' }
當不同錯誤情況,根據錯誤碼 ErrorCodes
來獲取 ErrorTypeStrings
錯誤信息進行提示:
// packages/runtime-core/src/errorHandling.ts function logError( err: unknown, type: ErrorTypes, contextVNode: VNode | null, throwInDev = true ) { if (__DEV__) { const info = ErrorTypeStrings[type] warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`) // 省略其他 } else { console.error(err) } }
關于 Vue3 實現 Tree Shaking 的介紹,可以看我之前寫的高效實現框架和 JS 庫瘦身。
其中,logError
方法中就使用到了:
// packages/runtime-core/src/errorHandling.ts function logError( err: unknown, type: ErrorTypes, contextVNode: VNode | null, throwInDev = true ) { if (__DEV__) { // 省略其他 } else { console.error(err) } }
當編譯成 production 環境后,__DEV__
分支的代碼不會被打包進去,從而優化包的體積。
到此,相信大家對“Vue3如何進行全局異常處理”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。