首頁 > web前端 > Vue.js > 一文詳解Vue3怎麼進行全域異常處理

一文詳解Vue3怎麼進行全域異常處理

青灯夜游
發布: 2022-03-07 19:54:22
轉載
3776 人瀏覽過

Vue3 如何實現全域異常處理?以下這篇文章為大家介紹Vue3進行全域異常處理的方法,希望對大家有幫助!

一文詳解Vue3怎麼進行全域異常處理

在開發元件庫或插件,經常會需要進行全域異常處理,從而實現:

  • 全域統一處理異常;
  • 為開發者提示錯誤訊息;
  • 方案降級處理等等。

那麼要如何實現上面功能呢? 本文先簡單實作一個異常處理方法,然後再結合 Vue3 原始碼中的實作詳細介紹,最後總結實作異常處理的幾個核心。 【相關推薦:vuejs影片教學

本文Vue3 版本為3.0.11

##一、前端常見例外

對於前端來說,常見的異常比較多,例如:JS 語法異常;Ajax 請求異常;

靜態資源載入異常;

Promise 異常;
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,則阻止執行預設事件處理函數。 2.  try...catch 例外處理

另外,我們也常會使用

try...catch

語句

處理例外:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">try { // do something } catch (error) { console.error(error); }</pre><div class="contentsignin">登入後複製</div></div>更多處理方式,可以閱讀前面推薦的文章。 3. 思考

大家可以思考下,自己在業務開發過程中,是否也是常常要處理這些錯誤狀況? 那麼像 Vue3 這樣複雜的函式庫,是否也是到處透過

try...catch

來處理異常呢? 接下來一起看看。

二、實作簡單的全域異常處理

在開發外掛程式或函式庫時,我們可以透過try...catch封裝一個全域異常處理方法,將需要執行的方法作為參數傳入,呼叫方只要關心呼叫結果,而無需知道該全域異常處理方法內部邏輯。 大致使用方法如下:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">const errorHandling = (fn, args) =&gt; { let result; try{ result = args ? fn(...args) : fn(); } catch (error){ console.error(error) } return result; }</pre><div class="contentsignin">登入後複製</div></div>測試一下:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">const f1 = () =&gt; { console.log(&amp;#39;[f1 running]&amp;#39;) throw new Error(&amp;#39;[f1 error!]&amp;#39;) } 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.&lt;anonymous&gt; (/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 */</pre><div class="contentsignin">登入後複製</div></div>可以看到,當需要為方法做例外處理時,只要將方法傳入即可作為參數。 但是上面範例跟實際業務開發的邏輯差得有點多,實際業務中,我們常常會遇到方法的巢狀調用,那麼我們試試看:

const f1 = () => {
    console.log(&#39;[f1]&#39;)
    f2();
}

const f2 = () => {
    console.log(&#39;[f2]&#39;)
    f3();
}

const f3 = () => {
    console.log(&#39;[f3]&#39;)
    throw new Error(&#39;[f3 error!]&#39;)
}

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 如何實作異常處理
  • 理解完上面範例,接下來看看在 Vue3 原始碼中是如何實作異常處理的,其實作起來也是很簡單。

1. 實作異常處理方法

errorHandling.ts

檔案中定義了callWithErrorHandlingcallWithAsyncErrorHandling兩個處理全域異常的方法。 顧名思義,這兩個方法分別處理:callWithErrorHandling:處理同步方法的例外;

callWithAsyncErrorHandling

:處理非同步方法的異常。

使用方式如下:

callWithAsyncErrorHandling(
  handler,
  instance,
  ErrorCodes.COMPONENT_EVENT_HANDLER,
  args
)
登入後複製

程式碼實作大致如下:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">// 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 &amp;&amp; isPromise(res)) { res.catch(err =&gt; { handleError(err, instance, type) }) } // 省略其他代码 }</pre><div class="contentsignin">登入後複製</div></div>

callWithErrorHandling###方法處理的邏輯比較簡單,透過簡單的# ##try...catch### 做一層封裝。 而 ###callWithAsyncErrorHandling### 方法就比較巧妙,透過將需要執行的方法傳入 ###callWithErrorHandling###方法處理,並將其結果透過 ###.catch###方法進行處理。 ######2. 處理異常######在上面程式碼中,遇到報錯的情況,都會透過 ###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)###輸出錯誤內容。 ###

3. 配置 errorHandler 自定义异常处理函数

在使用 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来调用。

4. 调用 errorCaptured 生命周期钩子

在使用 Vue3 的时候,也可以通过 errorCaptured生命周期钩子来捕获来自后代组件的错误。 文档参考:《errorCaptured》 入参如下:

(err: Error, instance: Component, info: string) => ?boolean
登入後複製

此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。 此钩子可以返回 false阻止该错误继续向上传播。有兴趣的同学可以通过文档,查看具体的错误传播规则。 使用方法如下,父组件监听 onErrorCaptured生命周期(示例代码使用 Vue3 setup 语法):

<template>
  <Message></Message>
</template>
<script setup>
// App.vue  
import { onErrorCaptured } from &#39;vue&#39;;
  
import Message from &#39;./components/Message.vue&#39;
  
onErrorCaptured(function(err, instance, info){
  console.log(&#39;[errorCaptured]&#39;, err, instance, info)
})
</script>
登入後複製

子组件如下:

<template>
  <button @click="sendMessage">发送消息</button>
</template>

<script setup>
// Message.vue
const sendMessage = () => {
  throw new Error(&#39;[test onErrorCaptured]&#39;)
}
</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 生命周期方法的数组并循环调用其每一个钩子,然后再取出当前组件的父组件作为参数,最后继续递归调用下去。

5. 实现错误码和错误消息

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]: &#39;renderTracked hook&#39;,
  [LifecycleHooks.RENDER_TRIGGERED]: &#39;renderTriggered hook&#39;,
  [ErrorCodes.SETUP_FUNCTION]: &#39;setup function&#39;,
  [ErrorCodes.RENDER_FUNCTION]: &#39;render function&#39;,
  // 省略其他
  [ErrorCodes.SCHEDULER]:
    &#39;scheduler flush. This is likely a Vue internals bug. &#39; +
    &#39;Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next&#39;
}
登入後複製

当不同错误情况,根据错误码 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)
  }
}
登入後複製

6. 实现 Tree Shaking

关于 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 中全局异常处理的核心逻辑了。我们在开发自己的错误处理方法时,也可以考虑这几个核心点:

  • 支持同步和异步的异常处理;

  • 设置业务错误码、业务错误信息;

  • 支持自定义错误处理方法;

  • 支持开发环境错误提示;

  • 支持 Tree Shaking。

原文地址:https://juejin.cn/post/7071982812668100616

(学习视频分享:vuejs教程web前端

以上是一文詳解Vue3怎麼進行全域異常處理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板