内容简介:持续更新中对
持续更新中 react相关库源码浅析
解决的两个问题
- 收集错误
- 自定义事件实现
try...catch
错误捕获功能同时增加Pause on exceptions
功能
react\packages\shared\ReactErrorUtils.js
用于存储错误以及是否发生错误的状态
// Used by Fiber to simulate a try-catch. let hasError: boolean = false; let caughtError: mixed = null; 复制代码
需要重新抛出的错误以及重新抛出的状态
// Used by event system to capture/rethrow the first error. let hasRethrowError: boolean = false; let rethrowError: mixed = null; 复制代码
reporter
对象上的 onError
方法提供一个借口改变错误的状态以及存储捕获到的错误对象
const reporter = { onError(error: mixed) { hasError = true; caughtError = error; }, }; 复制代码
错误系统入口函数,用于捕获第一个错误
对 invokeGuardedCallback
的封装,当执行完 invokeGuardedCallback
之后,通过 hasError
判断是否发生了错误,并调用 clearCaughtError
返回错误对象,并清空 hasError、caughtError
。然后将返回的错误存储到 rethrowError
,并改变 hasRethrowError
的状态。
function invokeGuardedCallbackAndCatchFirstError(name,func,context,a,b,c,d,e,f) { invokeGuardedCallback.apply(this, arguments); if (hasError) { const error = clearCaughtError(); if (!hasRethrowError) { hasRethrowError = true; rethrowError = error; } } } export function clearCaughtError() { if (hasError) { const error = caughtError; hasError = false; caughtError = null; return error; } else { invariant( false, 'clearCaughtError was called but no error was captured. This error ' + 'is likely caused by a bug in React. Please file an issue.', ); } } 复制代码
调用传入函数 func
的准备
对 invokeGuardedCallbackImpl
的封装,利用 apply
将 this
指向 reporter
,用于当 invokeGuardedCallbackImpl
函数发生错误的时候,调用 reporter
上的接口 onError
,将错误对象存储到最外层 ReactErrorUtils.js
文件模块的 hasError、caughtError
变量上。
export function invokeGuardedCallback(name,func,context,a,b,c,d,e,f) { hasError = false; caughtError = null; invokeGuardedCallbackImpl.apply(reporter, arguments); } 复制代码
直接执行的 func
的 invokeGuardedCallbackImpl
invokeGuardedCallbackImpl
在不同的环境下有不同的实现:
- 在生产环境下利用
try ... catch
捕获错误,并存储错误到hasError、caughtError
上。 - 在开发环境下利用自定义事件模拟了
try ... catch
功能,同时具备在开发 工具 中在所有异常发生的地方自动断点的功能。如果用try ... catch
,那么try
块语句中出现的错误不会断点暂停,因为这个异常被catch
捕获了,除非在chrome下将Pause on exceptions
打开。
Because React wraps all user-provided functions in invokeGuardedCallback, and the production version of invokeGuardedCallback uses a try-catch, all user exceptions are treated like caught exceptions, and the DevTools won't pause unless the developer takes the extra step of enabling pause on caught exceptions.
下面主要分析 invokeGuardedCallbackImpl
react\packages\shared\invokeGuardedCallbackImpl.js
生产环境:利用try-catch实现错误捕获
let invokeGuardedCallbackImpl = function(name,func,context,a,b,c,d,e,f){ const funcArgs = Array.prototype.slice.call(arguments, 3); try { func.apply(context, funcArgs); } catch (error) { this.onError(error); } }; 复制代码
将 arguments
转成数组,然后执行传入的函数 func
,执行上下文指定为传入的 context
。如果 func
执行过程中发生错误,抛出的错误被捕获并通过 this.onError
传入最外层的变量上。这里的 this
指向的是 reporter
。
开发环境:模拟try-catch
const fakeNode = document.createElement('react'); const invokeGuardedCallbackDev = function(name,func,context,a,b,c,d,e,f){ let windowEvent = window.event; const windowEventDescriptor = Object.getOwnPropertyDescriptor( window, 'event', ); const funcArgs = Array.prototype.slice.call(arguments, 3); /** * 给window添加error事件监听函数,未捕获的错误都会经过这里 */ let error; let didSetError = false; let isCrossOriginError = false; function handleWindowError(event) { error = event.error; didSetError = true; if (error === null && event.colno === 0 && event.lineno === 0) { // 当加载自不同域的脚本中发生语法错误时,为避免信息泄露,语法错误的细节将不会报告,而代之简单的"Script error." // event: // message:错误信息(字符串)。可用于HTML onerror=""处理程序中的event。 // source:发生错误的脚本URL(字符串) // lineno:发生错误的行号(数字) // colno:发生错误的列号(数字) // error:Error对象(对象) isCrossOriginError = true; } if (event.defaultPrevented) { if (error != null && typeof error === 'object') { try { error._suppressLogging = true; } catch (inner) { // Ignore. } } } } window.addEventListener('error', handleWindowError); /** * 利用createEvent创建自定义事件event,并利用initEvent指定名称,然后给DOM fakeNode添加evtType事件监听函数 */ let didError = true; const evtType = `react-${name ? name : 'invokeguardedcallback'}`; const evt = document.createEvent('Event'); evt.initEvent(evtType, false/*false表示阻止该事件向上冒泡*/, false/*false表示该事件默认动作不可取消*/); function callCallback() { fakeNode.removeEventListener(evtType, callCallback, false); if ( typeof window.event !== 'undefined' && window.hasOwnProperty('event') ) { window.event = windowEvent; } func.apply(context, funcArgs); didError = false; } fakeNode.addEventListener(evtType, callCallback, false); /** * 手动触发fakeNode上的evt事件,此时会先执行传入的func函数,如果发生错误会触发window上的error事件 */ fakeNode.dispatchEvent(evt); /** * 还原window.event */ if (windowEventDescriptor) { Object.defineProperty(window, 'event', windowEventDescriptor); } /** * 调用onError记录error */ if (didError) { if (!didSetError) { error = new Error( 'An error was thrown inside one of your components' ); } else if (isCrossOriginError) { error = new Error( "A cross-origin error was thrown. React doesn't have access to ", ); } this.onError(error); } // Remove our event listeners window.removeEventListener('error', handleWindowError); }; invokeGuardedCallbackImpl = invokeGuardedCallbackDev; 复制代码
chrome
下 Pause on exceptions
功能
操作: f12 -> Source Tab -> 点击 Pause on exceptions 暂停图标 -> 图标变成蓝色,表明启用了在未捕获到的异常出现的时候断点的功能。
勾选 Pause On Caught Exceptions
, 能够在捕获到异常的情况下也断点。
try{ throw'a exception'; }catch(e){ console.log(e); } 复制代码
上面 try
里面的代码会遇到异常,但是后面的 catch
代码能够捕获该异常。如果是所有异常都中断(勾选了 Pause On Caught Exceptions
),那么代码执行到会产生异常的 throw 语句时就会自动中断;而如果是仅遇到未捕获异常才中断,那么这里就不会中断。一般我们会更关心遇到未捕获异常的情况。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
高性能网站建设进阶指南
Steve Souders / 口碑网前端团队 / 电子工业出版社 / 2010年4月 / 49.80元
性能是任何一个网站成功的关键,然而,如今日益丰富的内容和大量使用Ajax的Web应用程序已迫使浏览器达到其处理能力的极限。Steve Souders是Google Web性能布道者和前Yahoo!首席性能工程师,他在本书中提供了宝贵的技术来帮助你优化网站性能。 Souders的上一本畅销书《高性能网站建设指南》(High Performance Web Sites)震惊了Web开发界,它揭示......一起来看看 《高性能网站建设进阶指南》 这本书的介绍吧!