React16源码: React中commit阶段的invokeGuardedCallback的源码实现

本文主要是介绍React16源码: React中commit阶段的invokeGuardedCallback的源码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

invokeGuardedCallback


1 )概述

  • 在 commit 阶段中在 DEV 环境中调用了 invokeGuardedCallback 这个方法
  • 这个方法是在开发过程中被使用,用于捕获错误,协助开发调试的方法

2 )概述

定位到 packages/shared/ReactErrorUtils.js#L41

进入 invokeGuardedCallback

const reporter = {onError(error: mixed) {hasError = true;caughtError = error;},
};/*** Call a function while guarding against errors that happens within it.* Returns an error if it throws, otherwise null.** In production, this is implemented using a try-catch. The reason we don't* use a try-catch directly is so that we can swap out a different* implementation in DEV mode.** @param {String} name of the guard to use for logging or debugging* @param {Function} func The function to invoke* @param {*} context The context to use when calling the function* @param {...*} args Arguments for function*/
export function invokeGuardedCallback<A, B, C, D, E, F, Context>(name: string | null,func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,context: Context,a: A,b: B,c: C,d: D,e: E,f: F,
): void {hasError = false;caughtError = null;invokeGuardedCallbackImpl.apply(reporter, arguments);
}
  • 这个方法它实际调用了 invokeGuardedCallbackImpl.apply(reporter, arguments);
  • 这个 reporter,是定义的一个全局对象
  • 内部初始化了两个变量 hasErrorcaughtError
  • 看下 invokeGuardedCallbackImpl
    // https://github.com/facebook/react/blob/v16.6.3/packages/shared/invokeGuardedCallbackImpl.js
    let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(name: string | null,func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,context: Context,a: A,b: B,c: C,d: D,e: E,f: F,
    ) {const funcArgs = Array.prototype.slice.call(arguments, 3);try {func.apply(context, funcArgs);} catch (error) {this.onError(error);}
    };if (__DEV__) {// In DEV mode, we swap out invokeGuardedCallback for a special version// that plays more nicely with the browser's DevTools. The idea is to preserve// "Pause on exceptions" behavior. 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. This is// untintuitive, though, because even though React has caught the error, from// the developer's perspective, the error is uncaught.//// To preserve the expected "Pause on exceptions" behavior, we don't use a// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake// DOM node, and call the user-provided callback from inside an event handler// for that fake event. If the callback throws, the error is "captured" using// a global event handler. But because the error happens in a different// event loop context, it does not interrupt the normal program flow.// Effectively, this gives us try-catch behavior without actually using// try-catch. Neat!// Check that the browser supports the APIs we need to implement our special// DEV version of invokeGuardedCallbackif (typeof window !== 'undefined' &&typeof window.dispatchEvent === 'function' &&typeof document !== 'undefined' &&typeof document.createEvent === 'function') {const fakeNode = document.createElement('react');const invokeGuardedCallbackDev = function<A, B, C, D, E, F, Context>(name: string | null,func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,context: Context,a: A,b: B,c: C,d: D,e: E,f: F,) {// If document doesn't exist we know for sure we will crash in this method// when we call document.createEvent(). However this can cause confusing// errors: https://github.com/facebookincubator/create-react-app/issues/3482// So we preemptively throw with a better message instead.// 首先会有一大串长的提醒,说明当前这个环境是不支持的invariant(typeof document !== 'undefined','The `document` global was defined when React was initialized, but is not ' +'defined anymore. This can happen in a test environment if a component ' +'schedules an update from an asynchronous callback, but the test has already ' +'finished running. To solve this, you can either unmount the component at ' +'the end of your test (and ensure that any asynchronous operations get ' +'canceled in `componentWillUnmount`), or you can change the test itself ' +'to be asynchronous.',);// 它创建了一个 event 对象const evt = document.createEvent('Event');// Keeps track of whether the user-provided callback threw an error. We// set this to true at the beginning, then set it to false right after// calling the function. If the function errors, `didError` will never be// set to false. This strategy works even if the browser is flaky and// fails to call our global error handler, because it doesn't rely on// the error event at all.// 声明了 didError 为truelet didError = true;// Keeps track of the value of window.event so that we can reset it// during the callback to let user code access window.event in the// browsers that support it.let windowEvent = window.event;// Keeps track of the descriptor of window.event to restore it after event// dispatching: https://github.com/facebook/react/issues/13688// 来获取 window.event 的对象描述符const windowEventDescriptor = Object.getOwnPropertyDescriptor(window,'event',);// Create an event handler for our fake event. We will synchronously// dispatch our fake event using `dispatchEvent`. Inside the handler, we// call the user-provided callback.// 把前面3个参数给它拿出来,剩下的都是作为它的argumentsconst funcArgs = Array.prototype.slice.call(arguments, 3);// 它声明了一个叫做callcallback的一个方法。function callCallback() {// We immediately remove the callback from event listeners so that// nested `invokeGuardedCallback` calls do not clash. Otherwise, a// nested call would trigger the fake event handlers of any call higher// in the stack.fakeNode.removeEventListener(evtType, callCallback, false); // 移除事件监听// We check for window.hasOwnProperty('event') to prevent the// window.event assignment in both IE <= 10 as they throw an error// "Member not found" in strict mode, and in Firefox which does not// support window.event.// 对 window.event 进行兼容处理if (typeof window.event !== 'undefined' &&window.hasOwnProperty('event')) {window.event = windowEvent;}// 使用 context 调用 func 传入 funcArgs 后续的参数// 注意,在 dev 时,这个 context 可能是 null, 可以向上追溯func.apply(context, funcArgs);didError = false;}// Create a global error event handler. We use this to capture the value// that was thrown. It's possible that this error handler will fire more// than once; for example, if non-React code also calls `dispatchEvent`// and a handler for that event throws. We should be resilient to most of// those cases. Even if our error event handler fires more than once, the// last error event is always used. If the callback actually does error,// we know that the last error event is the correct one, because it's not// possible for anything else to have happened in between our callback// erroring and the code that follows the `dispatchEvent` call below. If// the callback doesn't error, but the error event was fired, we know to// ignore it because `didError` will be false, as described above.// 声明一堆和 error 相关的变量let error;// Use this to track whether the error event is ever called.let didSetError = false;let isCrossOriginError = false;function handleWindowError(event) {error = event.error;didSetError = true;// 这种情况下是 跨域错误if (error === null && event.colno === 0 && event.lineno === 0) {isCrossOriginError = true;}// 别的地方,有去处理 window.onerror,这里被阻塞了if (event.defaultPrevented) {// Some other error handler has prevented default.// Browsers silence the error report if this happens.// We'll remember this to later decide whether to log it or not.if (error != null && typeof error === 'object') {try {error._suppressLogging = true; // 设置当前标识} catch (inner) {// Ignore.}}}}// Create a fake event type.const evtType = `react-${name ? name : 'invokeguardedcallback'}`;// Attach our event handlers// 注册两个事件window.addEventListener('error', handleWindowError);fakeNode.addEventListener(evtType, callCallback, false);// Synchronously dispatch our fake event. If the user-provided function// errors, it will trigger our global error handler.// 初始化事件,并立即触发 dispatchEventevt.initEvent(evtType, false, false);fakeNode.dispatchEvent(evt); // 触发完,会立即调用 callbackif (windowEventDescriptor) {Object.defineProperty(window, 'event', windowEventDescriptor);}if (didError) {// 出错了,但是 handleWindowError 没有被调用的处理if (!didSetError) {// The callback errored, but the error event never fired.error = new Error('An error was thrown inside one of your components, but React ' +"doesn't know what it was. This is likely due to browser " +'flakiness. React does its best to preserve the "Pause on ' +'exceptions" behavior of the DevTools, which requires some ' +"DEV-mode only tricks. It's possible that these don't work in " +'your browser. Try triggering the error in production mode, ' +'or switching to a modern browser. If you suspect that this is ' +'actually an issue with React, please file an issue.',);} else if (isCrossOriginError) {error = new Error("A cross-origin error was thrown. React doesn't have access to " +'the actual error object in development. ' +'See https://fb.me/react-crossorigin-error for more information.',);}this.onError(error);}// Remove our event listeners// 最终移除事件监听window.removeEventListener('error', handleWindowError);};invokeGuardedCallbackImpl = invokeGuardedCallbackDev;}
    }
    
    • 它接收一系列的参数, 后续几个参数,它通过a,b,c,d,e,f来进行了一个命名
    • 看着前期的定义好像很简单,但是下面有一个 DEV 的判断
    • 这个方法 invokeGuardedCallbackDev 是最终 export 出去的
    • 注意,在上述 handleWindowError 中,
      • 被触发相关事件时,就会执行这个回调函数
      • 1 )当遇到跨域错误时,会做相关检测,并设置相关标识
      • 2 )如果遇到 onError被阻塞,也是做一些标识处理
    • 上述 evtType 的事件,一经调用,就会执行 上述的 callCallback 方法
      • callCallback 方法一旦被执行,就会执行参数中的 func 方法
      • 当后续 func 执行中出现错误,didError = false 就不会被执行
      • 同时,出现错误,会调用 handleWindowError 这个方法,里面的变量也会发生变化
    • 所以,里面的变量在整体流程中,非常有用
    • 还有,就是 在最后的两个嵌套 if中 if (didError) { if (!didSetError) {} else if(isCrossOriginError) }
      • 这时候会抛出错误, 分成两种情况,
      • 参考:https://legacy.reactjs.org/docs/cross-origin-errors.html
    • 为何不直接用try catch,而要用这种事件的方式来触发呢?
      • 因为会存在这么一个问题,在浏览器的调试工具中
      • 比方说,Sources面板,里面有个 Pause on exceptions 功能
      • 如果有try catch会自行处理,但开发者本意不希望它停在那边,选中 Pause on caught exceptions
      • 这样,即便通过trycatch捕获了这个错误,一旦代码报错了,我勾中了这个之后,代码也会因为报错而停止
      • 开启该功能,使代码在捕获的错误发生的位置暂停,这样更方便定位问题
      • 对于要去调试像 componentDidCatch 这种在组件内去处理错误的这种情况
      • 它就会因为代码停在这里,而导致我们没有办法去调试这部分相关的功能
      • 所以react为了防止这种情况的出现了,实现了这么一种方式

这篇关于React16源码: React中commit阶段的invokeGuardedCallback的源码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/646101

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

Vue和React受控组件的区别小结

《Vue和React受控组件的区别小结》本文主要介绍了Vue和React受控组件的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录背景React 的实现vue3 的实现写法一:直接修改事件参数写法二:通过ref引用 DOMVu