如何快速定位页面对应的代码位置?click-to-component 源码解析
介绍和使用
click-to-component 是一个强大的 React 开发工具,它能让开发者在浏览器中通过 Option+Click(或 Alt+Click)直接定位到 React 组件的源代码。
如图所示 Option+Click,直接从浏览器中点击的 DOM 元素跳转到对应的 React 组件源代码位置。
- 安装
npm install click-to-react-component
- 将 ClickToComponent 添加到你的应用中,组件有两个参数
- editor: 指定点击后用哪个编辑器打开源码,默认值 vscode
- pathModifier: 用于修改跳转到对应的源码文件路径
// 导入 ClickToComponent
import { ClickToComponent } from 'click-to-react-component';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
{/* 添加 ClickToComponent 到应用中 */}
<ClickToComponent
editor={'cursor'}
pathModifier={(path) => path}
/>
<App />
<>
);
源码解析
click-to-component 的核心能力,是从浏览器中点击的 DOM 元素跳转到对应的 React 组件源代码位置。
阅读源码前先思考两个问题
-
onClick 获取的是 DOM 元素,它是如何对应上 React 组件的?
-
如何获取 React 组件的源代码位置?
DOM 元素与 React 组件的映射
click-to-component 通过两种方式实现映射:
- 优先使用 react-devtools 提供的能力
- 降级方案:通过 DOM 元素上的特殊属性(_reactFiber)获取
react-devtools 获取 DOM 元素对应的 React 组件
通过 DOM 元素上的特殊属性(_reactFiber)获取对应 React 组件
_reactFiber 这属性是怎么来的那?
React DOM 内部用来建立 DOM 节点和 Fiber 节点之间联系的关键函数
如何获取 React 组件的源代码位置?
/**
* @typedef {import('react-reconciler').Fiber} Fiber
* @typedef {import('react-reconciler').Source} Source
*/
/**
* @param {Fiber} instance
*/
export function getSourceForInstance(instance) {
// 检查实例是否包含调试源码信息
// _debugSource 只在开发环境下存在
if (!instance._debugSource) {
return
}
// 从 _debugSource 中解构需要的信息
const {
// It _does_ exist!
// @ts-ignore Property 'columnNumber' does not exist on type 'Source'.ts(2339)
columnNumber = 1, // 源码列号,默认值为 1
fileName, // 源文件路径
lineNumber = 1, // 源码行号,默认值为 1
} = instance._debugSource
return { columnNumber, fileName, lineNumber }
}
通过 getSourceForInstance.js,可以看出是通过 React Fiber 实例的 _debugSource 属性获取这个组件的源代码位置
这个 _debugSource 属性的值是怎么来的?
babel-plugin-transform-react-jsx-source 转义的时候加上的
什么情况下不具有 _debugSource
在 ClickToComponent onClick事件处理流程中,
- 先调用 getReactInstancesForElement 获取点击元素对应的组件实例链(包含当前组件及其所有父级组件),
- 后使用 getSourceForInstance 从组件实例链中筛选出第一个包含源代码位置信息(即具有 _debugSource 属性)的组件实例
在 getReactInstancesForElement(多了个 s) 中
-
首先调用了 getReactInstanceForElement 获取 DOM 元素对应的直接 React 组件实例
-
然后沿着组件树向上遍历,通过 _debugOwner 收集所有父级组件实例
-
最终返回包含当前组件及其所有父级组件实例的数组
上面的操作都是为了找到具有 _debugSource 属性的组件实例,那什么情况下当前不具有 _debugSource,递归的父组件实例却有 _debugSource ?
node_modules 下引入组件