Vue3 はグローバル例外処理をどのように実装していますか?以下の記事ではVue3におけるグローバル例外ハンドリングの方法を紹介していますので、ご参考になれば幸いです。
コンポーネント ライブラリまたはプラグインを開発する場合、多くの場合、次のことを実現するためにグローバル例外処理が必要になります。
では、上記の関数を実装するにはどうすればよいでしょうか? この記事では、まず例外処理メソッドを簡単に実装し、次にそれを Vue3 ソース コードの実装に基づいて詳細に紹介し、最後に例外処理を実装するためのいくつかのコア要素を要約します。 [関連する推奨事項: vuejs ビデオ チュートリアル]
この記事の Vue3 バージョンは 3.0.11
フロントエンドには、次のような一般的な例外が多数あります:
最も一般的に使用される例は次のとおりです:
window.onerror
ドキュメントを見ると、JSの実行中にエラーが発生した場合(構文エラーを含む)、 window.onerror()
:
window.onerror = function(message, source, lineno, colno, error) { console.log('捕获到异常:',{message, source, lineno, colno, error}); }
関数パラメータ:
onerror=""
handler の event
に使用できます。 この関数が true を返した場合、デフォルトのイベント処理関数の実行は阻止されます。
さらに、例外を処理するために try...catch
statement をよく使用します:
try { // do something } catch (error) { console.error(error); }
その他の処理方法については、以前に推奨された記事を参照してください。
考えてみてください、事業開発プロセス中にこれらのエラーに対処しなければならないことがよくありますか?
それでは、Vue3 のような複雑なライブラリは、あらゆる場所で例外を処理するために try...catch
を使用するのでしょうか?
次に見てみましょう。
プラグインまたはライブラリを開発する場合、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('[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 ソース コードで例外処理を実装する方法を見てみましょう。実装も非常に簡単です。 。
2 つのプロセス、callWithErrorHandling
と callWithAsyncErrorHandling
が errorHandling.ts## で定義されています。 # file グローバル例外メソッド。
名前が示すように、これら 2 つのメソッドはそれぞれ次の処理を行います:
: 同期メソッドの例外を処理します;
: 例外を処理します非同期メソッドの異常。
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
生命周期钩子来捕获来自后代组件的错误。
文档参考:《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 中全局异常处理的核心逻辑了。我们在开发自己的错误处理方法时,也可以考虑这几个核心点:
支持同步和异步的异常处理;
设置业务错误码、业务错误信息;
支持自定义错误处理方法;
支持开发环境错误提示;
支持 Tree Shaking。
原文地址:https://juejin.cn/post/7071982812668100616
以上がVue3 がグローバル例外を処理する方法を詳しく説明した記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。