從原生到框架之前端的錯(cuò)誤監(jiān)聽詳解(Vue和React兩大框架)
前言
在前端開發(fā)中,錯(cuò)誤就像隱藏在代碼里的 “暗礁”—— 可能是用戶操作時(shí)的偶發(fā)異常,也可能是特定環(huán)境下的兼容性問題。如果沒有完善的錯(cuò)誤監(jiān)聽機(jī)制,這些 “暗礁” 會(huì)悄悄摧毀用戶體驗(yàn),甚至導(dǎo)致應(yīng)用崩潰。
今天,我們就從原生 JavaScript 出發(fā),逐步深入 Vue 和 React 兩大框架的錯(cuò)誤處理方案。
一、原生 JavaScript:錯(cuò)誤監(jiān)聽的 “基石”
無論使用哪種框架,原生 JS 的錯(cuò)誤處理都是基礎(chǔ)。它能覆蓋框架未處理的底層錯(cuò)誤,比如第三方庫異常、DOM 操作失誤等
1. try…catch
try...catch 常用于捕獲已知可能發(fā)生的代碼塊,可精確控制捕獲范圍。
try {
// 可能拋出錯(cuò)誤的代碼
const result = JSON.parse(invalidJson);
} catch (error) {
// 錯(cuò)誤處理邏輯
console.error('解析JSON失敗:', error.message);
// 可記錄錯(cuò)誤到服務(wù)端
reportErrorToServer(error);
}
try...catch 在同/異步任務(wù)的表現(xiàn):
try代碼塊會(huì)同步執(zhí)行,執(zhí)行過程中如果遇到錯(cuò)誤,會(huì)立即被catch捕獲。- 如果
try里包含異步任務(wù)(比如setTimeout、Promise、async/await中的異步操作)- 這類異步任務(wù)的回調(diào)會(huì)脫離
try的執(zhí)行棧,錯(cuò)誤無法被外層catch捕獲。 - 如果想要讓異步錯(cuò)誤被
catch捕獲,關(guān)鍵是讓“異步操作的錯(cuò)誤重新回到try的執(zhí)行棧”,這需要借助async/await或Promise.catch配合
- 這類異步任務(wù)的回調(diào)會(huì)脫離
// 錯(cuò)誤示例:setTimeout 內(nèi)的錯(cuò)誤無法被捕獲
try {
setTimeout(() => {
// 這里的錯(cuò)誤會(huì)在異步隊(duì)列執(zhí)行,此時(shí) try...catch 已結(jié)束
JSON.parse('invalid-json'); // 拋出錯(cuò)誤
}, 1000);
} catch (error) {
// 永遠(yuǎn)不會(huì)進(jìn)入這里!
console.log('捕獲到錯(cuò)誤:', error);
}
// 錯(cuò)誤示例:未 await 的 Promise 錯(cuò)誤無法被捕獲
try {
// Promise 回調(diào)是異步的,throw 時(shí) try...catch 已執(zhí)行完
Promise.resolve().then(() => {
throw new Error('Promise 內(nèi)的錯(cuò)誤');
});
} catch (error) {
// 也不會(huì)進(jìn)入這里!
console.log('捕獲到錯(cuò)誤:', error);
}
// 正確示例:await 配合 try...catch 捕獲異步錯(cuò)誤
async function fetchData() {
try {
// await 會(huì)等待 Promise 執(zhí)行,若失敗則拋出錯(cuò)誤
const response = await fetch('https://api.example.com/invalid-url');
} catch (error) {
// 能捕獲到 fetch 失敗、HTTP 錯(cuò)誤、JSON 解析錯(cuò)誤等所有異步相關(guān)錯(cuò)誤
console.log('捕獲到異步錯(cuò)誤:', error.message);
// 可做降級(jí)處理(如返回默認(rèn)數(shù)據(jù))
return { defaultData: [] };
}
}
fetchData();
// 示例:Promise.catch 配合 try...catch
function fetchData() {
return new Promise((resolve, reject) => {
fetch('https://api.example.com/invalid-url')
.then(response => {
if (!response.ok) throw new Error(`請(qǐng)求失敗: ${response.status}`);
return response.json();
})
.then(data => resolve(data))
.catch(error => {
// 手動(dòng)把異步錯(cuò)誤拋到外層
reject(error);
});
});
}
// 外層用 try...catch 捕獲 reject 的錯(cuò)誤
async function wrapper() {
try {
await fetchData();
} catch (error) {
console.log('捕獲到異步錯(cuò)誤:', error.message);
}
}
wrapper();
2. window.onerror
如果錯(cuò)誤沒有被 try...catch 捕獲,window.onerror 會(huì)成為最后一道防線,監(jiān)聽全局錯(cuò)誤。
這里要注意, window.onerror 不捕獲 Promise 未處理的拒絕錯(cuò)誤,而像 setTimeout/setInterval 這類異步執(zhí)行的普通錯(cuò)誤,它是會(huì)捕獲的。
// 全局錯(cuò)誤監(jiān)聽
window.onerror = function(message, source, lineno, colno, error) {
// 關(guān)鍵參數(shù)說明:
// message:錯(cuò)誤描述(如"Uncaught ReferenceError: xxx is not defined")
// source:錯(cuò)誤發(fā)生的腳本URL(方便定位哪個(gè)文件出錯(cuò))
// lineno/colno:錯(cuò)誤行號(hào)/列號(hào)
// error:完整的Error對(duì)象(包含stack堆棧信息)
// 過濾掉無關(guān)錯(cuò)誤(比如某些第三方庫的非致命警告)
if (message.includes('script error') && !source) return true;
console.error('全局同步錯(cuò)誤:', {
message,
source: source?.split('/').pop(), // 簡化文件名
line: lineno,
column: colno,
stack: error?.stack
});
// 上報(bào)錯(cuò)誤(生產(chǎn)環(huán)境必備)
reportGlobalError(error);
// 返回true:阻止瀏覽器默認(rèn)錯(cuò)誤提示(避免用戶看到難看的控制臺(tái)報(bào)錯(cuò))
return true;
};
注意??
- 跨域腳本(如 CDN 資源)默認(rèn)只顯示
Script error,需兩步解決:- 給
<script>標(biāo)簽加crossorigin屬性:<script src="https://cdn.example.com/xxx.js" crossorigin="anonymous"></script> - CDN 服務(wù)器配置
Access-Control-Allow-Origin: *(或指定你的域名)
- 給
- 壓縮后的代碼(如 webpack 打包產(chǎn)物)需配合 sourceMap,才能還原真實(shí)的錯(cuò)誤行號(hào)。
- 打包時(shí)生成 sourceMap 文件
- 壓縮 JS 文件的末尾添加一行注釋,指定 sourceMap 的路徑
- 把 sourceMap 文件也部署到 CDN
3. Promise 錯(cuò)誤:unhandledrejection
當(dāng) Promise 被 reject 但沒有 catch 時(shí),會(huì)觸發(fā) unhandledrejection 事件
window.addEventListener('unhandledrejection', (event) => {
// 阻止瀏覽器默認(rèn)提示(部分瀏覽器會(huì)在控制臺(tái)警告)
event.preventDefault();
const error = event.reason; // Promise拒絕的原因(Error對(duì)象)
console.error('未捕獲的Promise錯(cuò)誤:', {
message: error.message,
stack: error.stack,
// 額外信息:比如是哪個(gè)接口請(qǐng)求失敗
requestUrl: error.config?.url || '未知'
});
// 上報(bào)異步錯(cuò)誤
reportAsyncError(error);
});
Promise.reject(new Error('Unhandled Promise rejection'))
二、Vue:框架級(jí)錯(cuò)誤處理的“分層策略”
Vue 內(nèi)部封裝了一套 “組件內(nèi)捕獲 + 全局匯總” 的錯(cuò)誤處理機(jī)制,既能精確監(jiān)控組件錯(cuò)誤,又能統(tǒng)一管理全局異常。
Vue 會(huì)對(duì) 組件渲染、指令執(zhí)行、生命周期鉤子 等核心流程等錯(cuò)誤進(jìn)行攔截,但對(duì)非核心流程的錯(cuò)誤不攔截。
為什么攔截?
Vue 的設(shè)計(jì)理念是 “局部錯(cuò)誤隔離”—— 某個(gè)組件出錯(cuò),只銷毀該組件,不影響其他組件渲染。如果將錯(cuò)誤拋給 window.onerror,可能導(dǎo)致開發(fā)者誤判為 “全局錯(cuò)誤”,且不符合框架的容錯(cuò)邏輯。
為什么不攔截?
非核心流程這類錯(cuò)誤屬于 “開發(fā)者主動(dòng)編寫的業(yè)務(wù)邏輯錯(cuò)誤”(而非框架渲染鏈路錯(cuò)誤),Vue 認(rèn)為開發(fā)者應(yīng)自行處理(如事件中用 try/catch),因此不主動(dòng)攔截。
1. 組件級(jí):errorCaptured 生命周期
如果某個(gè)組件是 “高危區(qū)域”(如復(fù)雜表單、第三方圖表),用 errorCaptured 在組件內(nèi)攔截錯(cuò)誤,避免影響全局。
特點(diǎn):
- 監(jiān)聽所有下級(jí)組件的錯(cuò)誤
- 返回 false 會(huì)阻止向上傳播(阻止傳播到 window.onerror)
- 不能監(jiān)聽異步錯(cuò)誤
<!-- ErrorSafeChart.vue:包裹易出錯(cuò)的圖表組件 -->
<template>
<div class="chart-container">
<!-- 錯(cuò)誤時(shí)顯示降級(jí)UI -->
<div v-if="hasError" class="error-tip">
<icon name="warning" /> 圖表加載失敗,點(diǎn)擊重試
<button @click="resetError">重試</button>
</div>
<ComplexChart v-else :data="chartData" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import ComplexChart from './ComplexChart.vue';
const hasError = ref(false);
const chartData = ref([]);
// 組件內(nèi)錯(cuò)誤捕獲生命周期
const errorCaptured = (error, instance, info) => {
// 參數(shù)說明:
// error:錯(cuò)誤對(duì)象
// instance:發(fā)生錯(cuò)誤的子組件實(shí)例(這里是ComplexChart)
// info:錯(cuò)誤發(fā)生的場(chǎng)景(如"render"渲染時(shí)、"watch"監(jiān)聽時(shí))
hasError.value = true;
console.error('圖表組件錯(cuò)誤:', {
component: instance.$options.name || 'ComplexChart',
scene: info,
error: error.message
});
// 上報(bào)組件錯(cuò)誤
reportVueComponentError(error, instance, info);
// 關(guān)鍵:返回false阻止錯(cuò)誤向上傳播(不會(huì)觸發(fā)全局errorHandler和window.onerror)
// 適合處理已知的非致命錯(cuò)誤,避免全局告警
return false;
};
// 重置錯(cuò)誤狀態(tài)(重試邏輯)
const resetError = () => {
hasError.value = false;
// 重新加載圖表數(shù)據(jù)
fetchChartData();
};
// 加載圖表數(shù)據(jù)
const fetchChartData = async () => {
// ... 數(shù)據(jù)請(qǐng)求邏輯
};
</script>
適用場(chǎng)景:
- 監(jiān)控特定組件(如支付表單、地圖組件)的錯(cuò)誤
- 對(duì)錯(cuò)誤進(jìn)行差異化降級(jí)(如圖表錯(cuò)了顯示靜態(tài)圖片,表單錯(cuò)了保留用戶輸入)
2. 全局級(jí):app.config.errorHandler
所有未被errorCaptured攔截的組件錯(cuò)誤,都會(huì)匯總到errorHandler,適合做全局統(tǒng)一處理。
特點(diǎn):
- Vue 全局錯(cuò)誤監(jiān)聽,所有組件錯(cuò)誤都匯總到這里
- 如果
errorCaptured返回false,不會(huì)傳播到這里 - 和
window.onerror互斥 - 不能監(jiān)聽異步錯(cuò)誤
// main.js:Vue入口文件
import { createApp } from 'vue';
import App from './App.vue';
import { reportVueGlobalError } from './utils/errorReport';
const app = createApp(App);
// 配置Vue全局錯(cuò)誤處理器
app.config.errorHandler = (error, instance, info) => {
// 過濾掉已處理過的錯(cuò)誤(比如某些組件手動(dòng)標(biāo)記過)
if (error.__vue_handled__) return;
console.error('Vue全局錯(cuò)誤:', {
component: instance?.$options.name || '根組件',
scene: info, // 如"render"、"watch"、"v-on handler"
message: error.message,
stack: error.stack
});
// 標(biāo)記錯(cuò)誤已處理,避免重復(fù)上報(bào)
error.__vue_handled__ = true;
// 上報(bào)全局錯(cuò)誤(生產(chǎn)環(huán)境必加)
reportVueGlobalError(error, instance, info);
// 可選:全局提示用戶(如頂部Toast)
showGlobalToast('系統(tǒng)出現(xiàn)小錯(cuò)誤,請(qǐng)刷新頁面重試');
};
// 配置Vue警告處理器(開發(fā)環(huán)境輔助調(diào)試)
if (import.meta.env.DEV) {
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Vue警告:', {
message: msg,
component: instance?.$options.name,
trace: trace // 警告的調(diào)用棧
});
};
}
app.mount('#app');

三、React:ErrorBoundary 組件的“優(yōu)雅降級(jí)”
React 的錯(cuò)誤處理核心是 ErrorBoundary —— 一個(gè)特殊的組件,能捕獲子組件樹的渲染錯(cuò)誤,并顯示降級(jí) UI,類似 Vue 的 errorCaptured ,但功能更聚焦。
1. 實(shí)現(xiàn)一個(gè)通用 ErrorBoundary
// components/ErrorBoundary.jsx
import React from 'react';
import { reportReactError } from '../utils/errorReport';
import ErrorFallback from './ErrorFallback'; // 自定義降級(jí)UI組件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false, // 是否發(fā)生錯(cuò)誤
error: null, // 錯(cuò)誤對(duì)象
errorInfo: null // 錯(cuò)誤詳情(含組件棧)
};
}
// 靜態(tài)方法:更新錯(cuò)誤狀態(tài)(渲染前觸發(fā))
static getDerivedStateFromError(error) {
// 返回新狀態(tài),讓下一次渲染顯示降級(jí)UI
return { hasError: true, error };
}
// 錯(cuò)誤發(fā)生后觸發(fā)(可執(zhí)行副作用,如日志上報(bào))
componentDidCatch(error, errorInfo) {
// 記錄錯(cuò)誤詳情(errorInfo含componentStack,能定位哪個(gè)組件出錯(cuò))
this.setState({ errorInfo });
console.error('React組件錯(cuò)誤:', {
error: error.message,
componentStack: errorInfo.componentStack,
// 從props獲取額外上下文(如當(dāng)前頁面路由)
page: this.props.currentPage
});
// 上報(bào)React錯(cuò)誤
reportReactError({
error,
componentStack: errorInfo.componentStack,
page: this.props.currentPage
});
}
// 重置錯(cuò)誤狀態(tài)(支持重試)
resetError = () => {
this.setState({ hasError: false, error: null, errorInfo: null });
};
render() {
const { hasError, error, errorInfo } = this.state;
const { children, fallback } = this.props;
// 有錯(cuò)誤:顯示降級(jí)UI(優(yōu)先用props傳入的fallback,否則用默認(rèn))
if (hasError) {
return fallback ? (
React.cloneElement(fallback, {
error,
resetError: this.resetError
})
) : (
<ErrorFallback
error={error}
errorInfo={errorInfo}
onReset={this.resetError}
/>
);
}
// 無錯(cuò)誤:渲染子組件
return children;
}
}
export default ErrorBoundary;
2. 如何使用 ErrorBoundary
ErrorBoundary 是 “容器組件”,只需包裹需要監(jiān)控的子組件樹即可,推薦在路由級(jí)別或核心功能模塊使用:
(1)全局路由級(jí)包裹
// App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './components/ErrorBoundary';
import Home from './pages/Home';
import Order from './pages/Order';
import NotFound from './pages/NotFound';
import GlobalErrorFallback from './components/GlobalErrorFallback';
function App() {
return (
<Router>
{/* 全局ErrorBoundary:捕獲所有路由頁面的錯(cuò)誤 */}
<ErrorBoundary
currentPage="全局"
fallback={<GlobalErrorFallback />}
>
<Routes>
{/* 頁面級(jí)ErrorBoundary:針對(duì)特定頁面單獨(dú)處理 */}
<Route
path="/order"
element={
<ErrorBoundary
currentPage="訂單頁"
fallback={<div>訂單頁加載失敗,<button onClick={(e) => e.target.onReset()}>重試</button></div>}
>
<Order />
</ErrorBoundary>
}
/>
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</ErrorBoundary>
</Router>
);
}
export default App;
(2)組件級(jí)包裹(高危組件)
// pages/Home.jsx
import ErrorBoundary from '../components/ErrorBoundary';
import PaymentForm from '../components/PaymentForm'; // 高危組件:支付表單
import ProductList from '../components/ProductList';
function Home() {
return (
<div className="home-page">
<h1>首頁</h1>
{/* 只包裹高危組件,不影響其他部分 */}
<ErrorBoundary
currentPage="首頁-支付表單"
fallback={<div>支付表單加載失敗,請(qǐng)稍后再試</div>}
>
<PaymentForm />
</ErrorBoundary>
<ProductList /> {/* 普通組件,不包裹 */}
</div>
);
}
3. ErrorBoundary 的“盲區(qū)”
ErrorBoundary 只監(jiān)聽組件渲染時(shí)報(bào)錯(cuò),不監(jiān)聽dom事件、異步錯(cuò)誤。
原因:
- 事件處理器中的代碼并不在 React 的渲染階段執(zhí)行。在
onClick, onChange等回調(diào)中拋出錯(cuò)誤時(shí),React 的渲染流程已經(jīng)結(jié)束,錯(cuò)誤發(fā)生在瀏覽器正常的事件調(diào)用棧中,ErrorBoundary無從干預(yù)。這個(gè)時(shí)候可以通過try...catch手動(dòng)捕獲錯(cuò)誤。 setTimeoutsetIntervalPromise的.then()或async/await中的異步操作,其回調(diào)執(zhí)行時(shí)已經(jīng)脫離了最初的 React 渲染上下文和調(diào)用棧。ErrorBoundary監(jiān)聽的是渲染期間的同步錯(cuò)誤。
注意:開發(fā)環(huán)境下,React 會(huì)通過 iframe 的方式把錯(cuò)誤的 stack 直接覆蓋在頁面上(如要調(diào)試 UI ,可以通過控制臺(tái)刪掉這個(gè) iframe),便于調(diào)試,生產(chǎn)環(huán)境才會(huì)直接顯示 UI。
4. 避免創(chuàng)建類式組件
上述可以看出,ErrorBoundary 是通過 getDerivedStateFromError 和 componentDidCatch 去更新錯(cuò)誤狀態(tài)及執(zhí)行副作用的,而這兩個(gè)生命周期都是類才具有的方法。
函數(shù)式組件中目前還沒有與 static getDerivedStateFromError 直接等同的東西。如果你想避免創(chuàng)建類式組件,請(qǐng)像上面那樣編寫一個(gè) ErrorBoundary 組件,并在整個(gè)應(yīng)用程序中使用它。或者使用 react-error-boundary 包來執(zhí)行此操作。
總結(jié)
到此這篇關(guān)于從原生到框架之前端的錯(cuò)誤監(jiān)聽的文章就介紹到這了,更多相關(guān)前端的錯(cuò)誤監(jiān)聽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)圖片輪播組件代碼示例
本篇文章主要介紹了JavaScript實(shí)現(xiàn)圖片輪播組件代碼示例,對(duì)圖片輪播效果感興趣的小伙伴們可以參考一下。2016-11-11
Three.js光照技術(shù)為3D場(chǎng)景注入靈魂示例詳解
在Three.js中,提供了不同類型的光源,每種光源的不同之處在于其照射場(chǎng)景的方式和效果,這篇文章主要介紹了Three.js光照技術(shù)為3D場(chǎng)景注入靈魂的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-06-06
JavaScript this調(diào)用規(guī)則說明
我希望通過這些來使你們理解各種函數(shù)調(diào)用方式的不同,讓你的JavaScript代碼遠(yuǎn)離bugs。2010-03-03
JS實(shí)現(xiàn)獲取進(jìn)今年第幾天是周幾的方法分析
這篇文章主要介紹了JS實(shí)現(xiàn)獲取進(jìn)今年第幾天是周幾的方法,結(jié)合實(shí)例形式對(duì)比分析了JavaScript進(jìn)行日期與天數(shù)運(yùn)算相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-06-06
js獲取當(dāng)前年月日-YYYYmmDD格式的實(shí)現(xiàn)代碼
下面小編就為大家?guī)硪黄猨s獲取當(dāng)前年月日-YYYYmmDD格式的實(shí)現(xiàn)代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06

