• 技术文章 >web前端 >Vue.js

    一文详解Vue3怎么进行全局异常处理

    青灯夜游青灯夜游2022-03-07 19:54:22转载146
    Vue3 如何实现全局异常处理?下面本篇文章给大家介绍一下Vue3进行全局异常处理的方法,希望对大家有所帮助!

    在开发组件库或者插件,经常会需要进行全局异常处理,从而实现:

    那么如何实现上面功能呢? 本文先简单实现一个异常处理方法,然后结合 Vue3 源码中的实现详细介绍,最后总结实现异常处理的几个核心。【相关推荐:vuejs视频教程

    本文 Vue3 版本为 3.0.11

    一、前端常见异常

    对于前端来说,常见的异常比较多,比如:

    最常用的比如:

    1. window.onerror

    通过 window.onerror文档可知,当 JS 运行时发生错误(包括语法错误),触发 window.onerror()

    window.onerror = function(message, source, lineno, colno, error) {
      console.log('捕获到异常:',{message, source, lineno, colno, error});
    }

    函数参数:

    若该函数返回true,则阻止执行默认事件处理函数。

    2. try...catch 异常处理

    另外,我们也经常会使用 try...catch 语句处理异常:

    try {
      // do something
    } catch (error) {
      console.error(error);
    }

    更多处理方式,可以阅读前面推荐的文章。

    3. 思考

    大家可以思考下,自己在业务开发过程中,是否也是经常要处理这些错误情况? 那么像 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 如何实现异常处理

    理解完上面示例,接下来看看在 Vue3 源码中是如何实现异常处理的,其实现起来也是很简单。

    1. 实现异常处理方法

    errorHandling.ts 文件中定义了 callWithErrorHandlingcallWithAsyncErrorHandling两个处理全局异常的方法。 顾名思义,这两个方法分别处理:

    使用方式如下:

    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方法进行处理。

    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 '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 生命周期方法的数组并循环调用其每一个钩子,然后再取出当前组件的父组件作为参数,最后继续递归调用下去。

    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]: '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)
      }
    }

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

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

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

    以上就是一文详解Vue3怎么进行全局异常处理的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Vue3 全局异常处理
    上一篇:浅析Vue中的Vue.set和this.$set,看看使用场景! 下一篇:一文彻底的弄懂Vue中的虚拟DOM和 Diff 算法
    PHP编程就业班

    相关文章推荐

    • Vue3.0新特性以及使用总结(整理分享)• 深入聊聊Vue3.2中的setup语法糖• Vue3中如何实现过渡动画?组件和动画库方法解析• 干货分享:Vue3组件通信的7种方式!• 聊聊Vue3中Provide和Inject的实现原理• 教你5个让Vue3开发更顺畅的知识点

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网