React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案
前言
導(dǎo)致白屏的原因大概有兩種,一為資源的加載,二為 JS 執(zhí)行出錯(cuò)
本文就 JS 中執(zhí)行的報(bào)錯(cuò),會(huì)比較容易造成"白屏"場(chǎng)景,和能解決這些問(wèn)題的一些方法,作出一個(gè)匯總
常見(jiàn)的錯(cuò)誤
SyntaxError
SyntaxError(語(yǔ)法錯(cuò)誤)對(duì)象代表嘗試解析不符合語(yǔ)法的代碼的錯(cuò)誤。當(dāng) Javascript 引擎解析代碼時(shí),遇到了不符合語(yǔ)法規(guī)范的標(biāo)記(token)或標(biāo)記順序,則會(huì)拋出 SyntaxError
。
這里陳列下 SyntaxError
的常見(jiàn)錯(cuò)誤
保留字錯(cuò)誤
SyntaxError: "x" is a reserved identifier (Firefox)
SyntaxError: Unexpected reserved word (Chrome)
如在控制臺(tái)執(zhí)行下方代碼,則會(huì)上述錯(cuò)誤出現(xiàn)
const enum = 1
enum
在嚴(yán)格模式和非嚴(yán)格模式下都是保留字。
而以下標(biāo)記符只會(huì)在嚴(yán)格模式下才作為保留字:
implements
interface
let
package
private
protected
public
static
例如:
const implements = 1 // ? "use strict"; const implements = 1; // caught SyntaxError: Unexpected strict mode reserved word
命名錯(cuò)誤
一個(gè) JavaScript 標(biāo)識(shí)符必須以字母開(kāi)頭,下劃線(_)或美元符號(hào)($)。他們不能以數(shù)字開(kāi)頭。只有后續(xù)字符可以是數(shù)字(0-9)。
var 1life = 'foo'; // SyntaxError: identifier starts immediately after numeric literal var foo = 1life; // SyntaxError: identifier starts immediately after numeric literal
錯(cuò)誤的標(biāo)點(diǎn)
在代碼中有非法的或者不期望出現(xiàn)的標(biāo)記符號(hào)出現(xiàn)在不該出現(xiàn)的位置。
“This looks like a string”; // SyntaxError: illegal character 42 – 13; // SyntaxError: illegal character
代碼里使用了中文的引號(hào)和橫杠,造成了解析錯(cuò)誤,這里就體現(xiàn)了編輯器的重要性
JSON 解析
JSON.parse('[1, 2, 3, 4, ]'); JSON.parse('{"foo" : 1, }'); // SyntaxError JSON.parse: unexpected character // at line 1 column 14 of the JSON data
json
解析失敗的類(lèi)型有很多,這里就不贅述了,我們?cè)谶M(jìn)行 json
解析的時(shí)候,一定要加上 try...catch
語(yǔ)句來(lái)避免錯(cuò)誤
分號(hào)問(wèn)題
通常情況下,這個(gè)錯(cuò)誤只是另一個(gè)錯(cuò)誤一個(gè)導(dǎo)致的,如不正確轉(zhuǎn)義字符串,使用 var 的錯(cuò)誤
const foo = 'Tom's bar'; // SyntaxError: missing ; before statement
通過(guò)其他方案聲明:
var foo = "Tom's bar"; var foo = 'Tom\'s bar'; var foo = `Tom's bar`; // 推薦這種方案
使用 var
錯(cuò)誤
var array = []; var array[0] = "there"; // SyntaxError missing ; before
類(lèi)似當(dāng)前錯(cuò)誤的還有很多,比如:
SyntaxError: missing ) after argument list
SyntaxError: missing ) after condition
SyntaxError: missing } after function body
SyntaxError: missing } after property list
這些都是語(yǔ)法的錯(cuò)誤,在編輯器/IDE使用時(shí)期都能解析,但是在某些比較古老的框架下,
編輯器可能并不能識(shí)別出來(lái)他的語(yǔ)法,這便是此錯(cuò)誤經(jīng)常出現(xiàn)的場(chǎng)景
SyntaxError
屬于運(yùn)行時(shí)代碼錯(cuò)誤,通常也是新手開(kāi)發(fā)者容易犯得的錯(cuò)誤 ,在 dev
時(shí)期就可以發(fā)現(xiàn),不然無(wú)法通過(guò)編譯,是屬于比較容易發(fā)現(xiàn)的問(wèn)題
TypeError
TypeError(類(lèi)型錯(cuò)誤)對(duì)象通常(但并不只是)用來(lái)表示值的類(lèi)型非預(yù)期類(lèi)型時(shí)發(fā)生的錯(cuò)誤。
以下情況會(huì)拋出 TypeError
:
- 傳遞給運(yùn)算符的操作數(shù)或傳遞給函數(shù)的參數(shù)與預(yù)期的類(lèi)型不兼容;
- 嘗試修改無(wú)法更改的值;
- 嘗試以不適當(dāng)?shù)姆椒ㄊ褂靡粋€(gè)值。
不可迭代屬性
當(dāng)使用 for...of
,右側(cè)的值不是一個(gè)可迭代值時(shí),或者作為數(shù)組解構(gòu)賦值時(shí),會(huì)報(bào)此問(wèn)題
例如:
const myobj = { arrayOrObjProp1: {}, arrayOrObjProp2: [42] }; const { arrayOrObjProp1: [value1], arrayOrObjProp2: [value2], } = myobj; // TypeError: object is not iterable const obj = { France: "Paris", England: "London" }; for (const p of obj) { // … } // TypeError: obj is not iterable
JS 中有內(nèi)置的可迭代對(duì)象,如: String
、Array
、TypedArray
、Map
、Set
以及 Intl.Segments (en-US)
,因?yàn)樗鼈兊拿總€(gè) prototype
對(duì)象都實(shí)現(xiàn)了 @@iterator
方法。
Object
是不可迭代的,除非它們實(shí)現(xiàn)了迭代協(xié)議。
簡(jiǎn)單來(lái)說(shuō),對(duì)象中缺少一個(gè)可迭代屬性: next
函數(shù)
將上述 obj
改造:
const obj = { France: "Paris", England: "London", [Symbol.iterator]() { // 用原生的空數(shù)組迭代器來(lái)兼容 return [][Symbol.iterator](); }, }; for (const p of obj) { // … }
如此可不報(bào)錯(cuò),但是也不會(huì)進(jìn)入循環(huán)中
空值問(wèn)題
null.foo; // 錯(cuò)誤類(lèi)型:null 沒(méi)有這個(gè)屬性 undefined.bar; // 錯(cuò)誤類(lèi)型:undefined 沒(méi)有這個(gè)屬性 const foo = undefined; foo.substring(1); // TypeError: foo is undefined
雖然看起來(lái)簡(jiǎn)單,但是他是出現(xiàn)白屏最為頻繁的報(bào)錯(cuò)原因之一
在以前我們通常這樣解決問(wèn)題:
var value = null; value && value.foo;
現(xiàn)在我們可以使用 可選鏈 Optional chaining 來(lái)解決這個(gè)問(wèn)題
var value = null; value?.foo; // 但是他也不能用來(lái)賦值: value?.foo = 1
可選鏈語(yǔ)法:
obj.val?.prop obj.val?.[expr] obj.func?.(args)
錯(cuò)誤的函數(shù)執(zhí)行
錯(cuò)誤的函數(shù)名稱(chēng):
var x = document.getElementByID("foo"); // TypeError: document.getElementByID is not a function var x = document.getElementById("foo"); // 正確的函數(shù)
不存在的函數(shù):
var obj = { a: 13, b: 37, c: 42 }; obj.map(function(num) { return num * 2; }); // TypeError: obj.map is not a function
in 的錯(cuò)誤場(chǎng)景
在判斷一個(gè)對(duì)象中是否存在某個(gè)值時(shí),比較常用的是一種方法是使用 in
來(lái)判斷:
var foo = { baz: "bar" }; if('baz' in foo){ // operation }
因?yàn)椴荒艽_定 foo['baz'] 的具體值,所以這種方案也是不錯(cuò)的,但是當(dāng) foo
的類(lèi)型也不能確認(rèn)的時(shí)候就會(huì)容易出現(xiàn)報(bào)錯(cuò)了
var foo = null; "bar" in foo; // TypeError: invalid 'in' operand "foo" "Hello" in "Hello World"; // TypeError: invalid 'in' operand "Hello World"
字符串和空值不適合使用此語(yǔ)法
_另外需要注意的是_,在數(shù)組中需要小心使用
const number = [2, 3, 4, 5]; 3 in number // 返回 true. 2 in number // 返回 true. 5 in number // 返回 false,因?yàn)?5 不是數(shù)組上現(xiàn)有的索引,而是一個(gè)值;
因?yàn)殄e(cuò)誤是跟隨著不同的值類(lèi)型,而數(shù)據(jù)的接收/轉(zhuǎn)變我們并不能做到 100% 的把控。
它是我們平時(shí)線上報(bào)錯(cuò)最頻繁的一種類(lèi)型,也是最容易造成頁(yè)面白屏的。需要保持 120% 的小心。
RangeError
RangeError
對(duì)象表示一個(gè)特定值不在所允許的范圍或者集合中的錯(cuò)誤。
在以下的情況中,可能會(huì)遇到這個(gè)問(wèn)題:
- 將不允許的字符串值傳遞給 String.prototype.normalize(),或
- 嘗試使用 Array 構(gòu)造函數(shù)創(chuàng)建一個(gè)具有不合法的長(zhǎng)度的字符串,或
- 傳遞錯(cuò)誤值到數(shù)值計(jì)算方法(Number.toExponential()、Number.toFixed() 或 Number.toPrecision())。
這里舉幾個(gè)例子:
String.fromCodePoint("_"); // RangeError new Array(-1); // RangeError new Date("2014-25-23").toISOString(); // RangeError (2.34).toFixed(-100); // RangeError (42).toString(1); const b = BigInt(NaN); // RangeError: NaN cannot be converted to a BigInt because it is not an integer
總的來(lái)說(shuō) RangeError 都是因?yàn)閭魅肓瞬徽_的值而導(dǎo)致的,這種情況發(fā)生的概率較小,部分?jǐn)?shù)字都是自己可以手動(dòng)控制或者寫(xiě)死在代碼里的
除非是定制化很高的情況,比如低代碼,讓用戶隨意輸入的時(shí)候,在使用的時(shí)候,最好先做出判斷,或者加上 try...catch
ReferenceError
ReferenceError
(引用錯(cuò)誤)對(duì)象代表當(dāng)一個(gè)不存在(或尚未初始化)的變量被引用時(shí)發(fā)生的錯(cuò)誤。
這種報(bào)錯(cuò)的場(chǎng)景大多處于嚴(yán)格模式下,在正常情況下 "變量未定義" 這種報(bào)錯(cuò)出現(xiàn)的情況較多
foo.substring(1); // ReferenceError: foo is not defined
如上,foo
未定義即直接使用,則就會(huì)出現(xiàn)報(bào)錯(cuò)
還有一類(lèi)報(bào)錯(cuò)是賦值的問(wèn)題,比如上方講過(guò)的可選鏈功能,他是不能賦值的:
foo?.bar = 123
這一類(lèi)在編碼因?yàn)槿菀追治觯话阍诰庉嬈髦芯湍苋菀装l(fā)現(xiàn),所以并不會(huì)帶來(lái)很多困擾。
其他
InternalError
對(duì)象表示出現(xiàn)在 JavaScript 引擎內(nèi)部的錯(cuò)誤。尚未成為任何規(guī)范的一部分,所以我們可以忽略。
EvalError
代表了一個(gè)關(guān)于 eval()
全局函數(shù)的錯(cuò)誤。
他不在當(dāng)前的 ECMAScript
規(guī)范中使用,因此不會(huì)被運(yùn)行時(shí)拋出。但是對(duì)象本身仍然與規(guī)范的早期版本向后兼容。
URIError
對(duì)象用來(lái)表示以一種錯(cuò)誤的方式使用全局 URI 處理函數(shù)而產(chǎn)生的錯(cuò)誤。
例如:
decodeURIComponent('%') // caught URIError: URI malformed decodeURI("%") // Uncaught URIError: URI malformed at decodeURI
所以使用 decodeURIComponent
函數(shù)時(shí),需要加上 try...catch
來(lái)保持正確性
另類(lèi)錯(cuò)誤
unhandledrejection
當(dāng) Promise 被 reject 且沒(méi)有 reject 處理器的時(shí)候,會(huì)觸發(fā) unhandledrejection
事件;
這個(gè)時(shí)候,就會(huì)報(bào)一個(gè)錯(cuò)誤:unhanled rejection;
沒(méi)有堆棧信息,只能依靠行為軌跡來(lái)定位錯(cuò)誤發(fā)生的時(shí)機(jī)。
window.addEventListener('unhandledrejection', event => { console.log('unhandledrejection: ', event.reason); // 打印 }); let p = Promise.reject("oops"); // 打印 unhandledrejection: oops // caught (in promise) oops
手動(dòng)拋出錯(cuò)誤
我們?cè)跁?shū)第三方庫(kù)的時(shí)候,可以手動(dòng)拋出錯(cuò)誤。但是throw error會(huì)阻斷程序運(yùn)行,請(qǐng)謹(jǐn)慎使用。
throw new Error("出錯(cuò)了!"); // caught Error: 出錯(cuò)了! throw new RangeError("出錯(cuò)了,變量超出有效范圍!"); throw new TypeError("出錯(cuò)了,變量類(lèi)型無(wú)效!");
同樣的,此種方案我們可以使用在 Promise
的 then
中:
// 模擬一個(gè)接口的返回 Promise.resolve({code: 3000, message: '這是一個(gè)報(bào)錯(cuò)!'}).then(res => { if (res.code !== 200) { throw new Error(`code 3000: ${res.message}`) } console.log(res); // 這里可以看做是執(zhí)行正常操作, 拋出錯(cuò)誤時(shí), 此處就不會(huì)執(zhí)行了 }).catch(err => { alert(err.message) });
在 catch
中我們可以通過(guò) name
來(lái)判斷不同的 Error
:
try { throw new TypeError(`This is an Error`) } catch (e) { console.log(e.name); // TypeError }
再加上自定義的 Error
,我們就可以制作更加自由的報(bào)錯(cuò)信息:
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } try { throw new ValidationError(`This is an Error`) } catch (e) { console.log(e.name); // 'ValidationError' if (e instanceof ValidationError) { alert("Invalid data: " + e.message); // Invalid data: This is an Error } }
在 Error
的基礎(chǔ)上我們還可以做更深入的繼承,來(lái)制作更多的自定義 Error
報(bào)錯(cuò)在 react 中的影響
react
報(bào)錯(cuò)按照位置,我將他分成兩類(lèi),一類(lèi)是渲染報(bào)錯(cuò),另一類(lèi)是執(zhí)行報(bào)錯(cuò);
渲染即 render
函數(shù)中的視圖渲染報(bào)錯(cuò),另一個(gè)則是執(zhí)行函數(shù)報(bào)錯(cuò);
函數(shù)的執(zhí)行報(bào)錯(cuò),是不會(huì)影響視圖的渲染的,即白屏,但是他會(huì)有一些不良影響,如
- 代碼執(zhí)行暫停,部分邏輯未執(zhí)行,未能閉環(huán)整體邏輯,如點(diǎn)擊按鈕一直卡在
loading
中 - 數(shù)據(jù)的渲染出現(xiàn)異常,兩邊數(shù)據(jù)對(duì)不上
在視圖渲染中(包括函數(shù)的 return
) ,觸發(fā) JS 錯(cuò)誤,都會(huì)渲染問(wèn)題
那為什么整個(gè)頁(yè)面都會(huì)白屏呢 ?
原因是自 React 16
起,任何未被錯(cuò)誤邊界捕獲的錯(cuò)誤將會(huì)導(dǎo)致整個(gè) React
組件樹(shù)被卸載。
錯(cuò)誤邊界
在 react 中存在此生命周期 componentDidCatch
,他會(huì)在一個(gè)子組件拋出錯(cuò)誤后被調(diào)用。
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } // 最新的官方推薦, 通過(guò)此 api 獲取是否觸發(fā)錯(cuò)誤 static getDerivedStateFromError(error) { return { hasError: true }; } // 舊方案是在此處 setState componentDidCatch(error, info) { // Example "componentStack": // in ComponentThatThrows (created by App) // in ErrorBoundary (created by App) // in div (created by App) // in App logComponentStackToMyService(info.componentStack); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } }
<ErrorBoundary fallback={<p>Something went wrong</p>}> <Profile /> </ErrorBoundary>
這是來(lái)自官網(wǎng)的一個(gè)簡(jiǎn)單例子,可以覆蓋子組件出錯(cuò)的情況,避免本身組件或兄弟組件收到波及,而錯(cuò)誤邊界組件的粒度需要開(kāi)發(fā)者本身來(lái)界定
降級(jí)和熔斷
在官方的文檔中他更加推薦此組件 react-error-boundary,它有著更加豐富的使用:
他可以簡(jiǎn)單的顯示錯(cuò)誤:
function Fallback({ error }) { return ( <div role="alert"> <p>Something went wrong:</p> <pre style={{ color: "red" }}>{error.message}</pre> </div> ); } <ErrorBoundary FallbackComponent={Fallback} > <ExampleApplication /> </ErrorBoundary>;
也可以使用重置方案:
function Fallback({ error, resetErrorBoundary }) { return ( <div role="alert"> <p>Something went wrong:</p> <pre style={{ color: "red" }}>{error.message}</pre> <button onclick={resetErrorBoundary}></button> </div> ); }
通過(guò)此方法重置組件,避免了刷新頁(yè)面,對(duì)于用戶來(lái)說(shuō)更加友好
更多使用,可以查看此處
總結(jié)
JS中有很多報(bào)錯(cuò),但是編輯器/編譯,已經(jīng)幫助我們過(guò)濾了一大部分的錯(cuò)誤,但是仍然會(huì)有部分報(bào)錯(cuò)會(huì)在特殊條件下出現(xiàn)
所以一方面需要充分的測(cè)試,如最大值/最小值/特殊值等等,另一方面就是需要積累經(jīng)驗(yàn),一些寫(xiě)法就是容易出現(xiàn)問(wèn)題,可以通過(guò) codeReview
來(lái)預(yù)防部分問(wèn)題但最終要堅(jiān)守軟件開(kāi)發(fā)的 不信任原則,保持 overly pessimistic
(過(guò)于悲觀),把和程序有關(guān)的一切請(qǐng)求、服務(wù)、接口、返回值、機(jī)器、框架、中間件等等都當(dāng)做不可信的,步步為營(yíng)、處處設(shè)防。
引用
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
http://chabaoo.cn/article/268041.htm
http://chabaoo.cn/javascript/295123kgd.htm
以上就是React 中的 JS 報(bào)錯(cuò)及容錯(cuò)方案的詳細(xì)內(nèi)容,更多關(guān)于React JS 報(bào)錯(cuò)容錯(cuò)方案的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react之umi配置國(guó)際化語(yǔ)言locale的踩坑記錄
這篇文章主要介紹了react之umi配置國(guó)際化語(yǔ)言locale的踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02關(guān)于react的代理配置(可配置多個(gè)代理)
這篇文章主要介紹了關(guān)于react的代理配置(可配置多個(gè)代理),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12在React中實(shí)現(xiàn)子組件向父組件傳值的幾種方法
在React應(yīng)用中,組件間的通信是常見(jiàn)的需求,通常,父組件通過(guò)props向子組件傳遞數(shù)據(jù),但有時(shí)也需要子組件向父組件傳遞數(shù)據(jù),本文將探討如何在React中實(shí)現(xiàn)子組件向父組件傳值的幾種方法,需要的朋友可以參考下2025-04-04淺談箭頭函數(shù)寫(xiě)法在ReactJs中的使用
這篇文章主要介紹了淺談箭頭函數(shù)寫(xiě)法在ReactJs中的使用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08React中實(shí)現(xiàn)父組件調(diào)用子組件的三種方法
在React中,組件之間的通信是一個(gè)常見(jiàn)的需求,有時(shí),我們需要從父組件調(diào)用子組件的方法,這可以通過(guò)幾種不同的方式實(shí)現(xiàn),需要的朋友可以參考下2024-04-04使用React+SpringBoot開(kāi)發(fā)一個(gè)協(xié)同編輯的表格文檔實(shí)現(xiàn)步驟
隨著云計(jì)算和團(tuán)隊(duì)協(xié)作的興起,協(xié)同編輯成為了許多企業(yè)和組織中必不可少的需求,本文小編就將為大家介紹如何使用React+SpringBoot簡(jiǎn)單的開(kāi)發(fā)一個(gè)協(xié)同編輯的表格文檔,感興趣的朋友一起看看吧2023-11-11react組件中的constructor和super知識(shí)點(diǎn)整理
這篇文章主要介紹了react組件中的constructor和super知識(shí)點(diǎn)整理,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08