ホームページ  >  記事  >  ウェブフロントエンド  >  Vue3 がグローバル例外を処理する方法を詳しく説明した記事

Vue3 がグローバル例外を処理する方法を詳しく説明した記事

青灯夜游
青灯夜游転載
2022-03-07 19:54:223696ブラウズ

Vue3 はグローバル例外処理をどのように実装していますか?以下の記事ではVue3におけるグローバル例外ハンドリングの方法を紹介していますので、ご参考になれば幸いです。

Vue3 がグローバル例外を処理する方法を詳しく説明した記事

コンポーネント ライブラリまたはプラグインを開発する場合、多くの場合、次のことを実現するためにグローバル例外処理が必要になります。

  • グローバルに統合された例外処理;
  • 開発者向けのプロンプトエラーメッセージ;
  • プログラムのダウングレード処理など。

では、上記の関数を実装するにはどうすればよいでしょうか? この記事では、まず例外処理メソッドを簡単に実装し、次にそれを Vue3 ソース コードの実装に基づいて詳細に紹介し、最後に例外処理を実装するためのいくつかのコア要素を要約します。 [関連する推奨事項: vuejs ビデオ チュートリアル]

この記事の Vue3 バージョンは 3.0.11

1. 一般的なフロントエンドの例外

フロントエンドには、次のような一般的な例外が多数あります:

  • JS 構文の例外、
  • Ajax リクエストの例外、
  • 静的リソース読み込み例外;
  • Promise 例外;
  • iframe 例外;
  • etc.

最も一般的に使用される例は次のとおりです:

1. window.onerror

window.onerrorドキュメントを見ると、JSの実行中にエラーが発生した場合(構文エラーを含む)、 window.onerror():

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

関数パラメータ:

  • message: エラー メッセージ (文字列)。 HTML onerror=""handler の event に使用できます。
  • source: エラーが発生したスクリプト URL (文字列)
  • lineno: エラーが発生した行番号 (数値)
  • colno: エラーが発生した列番号発生 (数値)
  • エラー: エラー オブジェクト (オブジェクト)

この関数が true を返した場合、デフォルトのイベント処理関数の実行は阻止されます。

2. try...catch 例外処理

さらに、例外を処理するために try...catch statement をよく使用します:

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

その他の処理方法については、以前に推奨された記事を参照してください。

3. 思考

考えてみてください、事業開発プロセス中にこれらのエラーに対処しなければならないことがよくありますか? それでは、Vue3 のような複雑なライブラリは、あらゆる場所で例外を処理するために try...catch を使用するのでしょうか? 次に見てみましょう。

2. 単純なグローバル例外処理の実装

プラグインまたはライブラリを開発する場合、try...catch を通じてグローバル例外処理メソッドをカプセル化できます。 require 実行されたメソッドはパラメータとして渡され、呼び出し元はグローバル例外処理メソッドの内部ロジックを知らなくても、呼び出し結果を気にするだけで済みます。 一般的な使用法は次のとおりです:

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(&#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 ソース コードでどのように処理されるかを見てみましょう。

3. Vue3 で例外処理を実装する方法

上記の例を理解した後、Vue3 ソース コードで例外処理を実装する方法を見てみましょう。実装も非常に簡単です。 。

1. 例外処理メソッドの実装

2 つのプロセス、callWithErrorHandlingcallWithAsyncErrorHandlingerrorHandling.ts## で定義されています。 # file グローバル例外メソッド。 名前が示すように、これら 2 つのメソッドはそれぞれ次の処理を行います:

  • 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 メソッドを通じて処理します。

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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。