React 项目里,如何快速定位你的组件源码?

如果说业务开发中最重要的能力,那定位代码的能力肯定是其中之一。

业务项目一般代码都很多,你拿到一个需求之后,可能改起来不难,但是要定位在哪里改比较难。

特别是接手别人写的代码的时候。

大家都是怎么在不熟悉的项目里定位的代码呢?

很多都学都是搜文案,搜 className。

这样没问题,但如果你用了 styled-component 之类的方案之后,className 都是动态生成的:

而且不少项目都做了国际化,你搜文案会搜到资源包里,而不是组件代码里:

当然,你可以进一步根据国际化的 key 来搜索源码的对应组件。

但这样总归比较麻烦,而且还不一定能搜到准确的位置。

那有什么好的办法可以快速定位代码么?

有,就是 click-to-react-component。

我们创建个项目:

lua 复制代码
npx create-vte

改下 main.tsx:

安装 antd,我们随便写几个页面:

css 复制代码
npm install
npm install --save antd

App.tsx:

javascript 复制代码
import React from 'react';
import { ColorPicker, Space } from 'antd';
import Aaa from './Aaa';

const Demo = () => (
  <div>
    <Space>
      <Space direction="vertical">
        <ColorPicker defaultValue="#1677ff" size="small" />
        <ColorPicker defaultValue="#1677ff" />
        <ColorPicker defaultValue="#1677ff" size="large" />
      </Space>
      <Space direction="vertical">
        <ColorPicker defaultValue="#1677ff" size="small" showText />
        <ColorPicker defaultValue="#1677ff" showText />
        <ColorPicker defaultValue="#1677ff" size="large" showText />
      </Space>
    </Space>
    <Aaa></Aaa>
  </div>
);

export default Demo;

Aaa.tsx:

javascript 复制代码
import React, { useState } from 'react';
import { Slider, Switch } from 'antd';
import Bbb from './Bbb';

const Aaa: React.FC = () => {
  const [disabled, setDisabled] = useState(false);

  const onChange = (checked: boolean) => {
    setDisabled(checked);
  };

  return (
    <>
        <div>
            <Slider defaultValue={30} disabled={disabled} />
            <Slider range defaultValue={[20, 50]} disabled={disabled} />
            Disabled: <Switch size="small" checked={disabled} onChange={onChange} />
        </div> 
        <Bbb></Bbb>
    </>
  );
};

export default Aaa;

Bbb.tsx:

javascript 复制代码
import React from 'react';
import { Card, Space } from 'antd';

const Bbb: React.FC = () => (
  <Space direction="vertical" size={16}>
    <Card title="Default size card" extra={<a href="#">More</a>} style={{ width: 300 }}>
      <p>Card content</p>
      <p>Card content</p>
      <p>Card content</p>
    </Card>
    <Card size="small" title="Small size card" extra={<a href="#">More</a>} style={{ width: 300 }}>
      <p>Card content</p>
      <p>Card content</p>
      <p>Card content</p>
    </Card>
  </Space>
);

export default Bbb;

这些都是从 antd 官网复制的 demo 代码。

不用管具体的代码内容,我们只需要看下怎么定位代码。

把开发服务跑起来:

arduino 复制代码
npm run dev

渲染出来是这样的:

如果我们想定位下面卡片的代码,就可以通过搜索文案或者 className:

但复杂项目就不行了。

这时候可以引入 click-to-react-component:

css 复制代码
npm install --save-dev click-to-react-component

在 main.tsx 引入下:

javascript 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
// @ts-ignore
import { ClickToComponent } from 'click-to-react-component';

ReactDOM.createRoot(document.getElementById('root')!).render(
    <>
        <ClickToComponent />
        <App />
    </>
)

可能有类型的报错,我们直接 @ts-ignore 忽略好了。

然后打开页面试一下:

可以看到,现在按住 option + 单击,就会直接打开它的对应的组件的源码。

如果按住 option + 右键单击,可以看到它的所有父级组件,然后选择一个组件打开:

这样在页面上看到了啥东西就可以直接打开它的组件代码来改,特别高效。

当然,我们的 demo 比较简单,来看个真实项目里的使用效果:

比如我想改这个登录弹窗的表单,就可以直接定位到对应组件的 Input。

对于大项目的维护来说真的超级方便。

知道了怎么用之后,我们再来探究下它的原理。

点击页面标签,就可以直接用 vscode 打开对应组件源码的行列号,是怎么实现的呢?

首先,怎么通过标签拿到对应组件的?

react 在标签上添加了 __reactFiber$ 开头的属性,可以拿到对应的 fiber 节点。

我们复制某个 dom 元素的选择器:

用 document.querySelector 取出来放到 el 变量。

然后你输入 el.__react 的时候会提示出一些属性:

__reactFiber$ 属性就是 dom 元素对应的 Fiber 节点。

__reactProps$ 属性就是这个组件的 props。

而且,拿到 fiber 节点后还可以通过 _debugOwner 拿到 fiber 节点的父节点。

一层层向上找,直到为 null,

就是这个的实现原理:

当然,fiber 节点还要根据 tag 来转为具体的类型。

比如 tag 为 10 是 Provider,tag 为 11 是 forwardRef 等。

这样,怎么从标签拿到对应的 fiber 节点我们就知道了。

那如何拿到组件在源码的文件和行列号呢?

这个通过 fiber 节点的 _debugSource 属性。

这个只有组件类型的 fiber 节点才有。

知乎就是用 react 开发的,因为你可以用 __reactFiber$ 属性拿到标签的 fiber 节点:

但是拿不到 __debugSource 属性,这个只有开发时才会有。

这个 _debugSource 属性是怎么加上的呢?react 并不知道组件在哪个文件定义的啊。

是 babel 插件做的:

@babel/plugin-transform-react-jsx-source 这个插件内置在 @babel/preset-env 里,不用手动引入。

它会在编译 jsx 的时候添加 _source 属性,然后 react 源码里再把 _source 属性的值添加到 fiber._debugSource 上。

那如何打开 vscode 呢?

只要在浏览器打开 vscode://file/文件绝对路径:行号:列号 的地址,就可以自动在 vscode 打开对应文件,并把光标定位到目标行列号。

这样,整个流程我们都理清了,点击标签的时候怎么拿到对应的 fiber 节点,拿到所有父组件,拿到组件的行列号,然后打开 vscode。

此外,还有一些 ui 上的实现原理:

hover 的时候会框选对应组件。

它定义了 data-xxx 的样式。

然后通过 useState 创建了状态来保存当前 target。

mousemove 的时候修改 target。

当 target 改变,就会给它设置 dataset.xxx 属性。

这个 dataset 大家可能没用过:

如果你给一个 dom 元素设置 dataset.aaaBbbCccDdd = 1

那它就会有一个 data-aaa-bbb-ccc-ddd="1" 的属性。

然后同样可以通过 dataset 取出来:

然后我们前面定义的 [data-xx] 的样式就生效了,就加上了框选的样式。

至于这个 popover,是用 @floating-ui 做的,所有浮动元素都可以用这个来做。

此外,这个 click-to-react-component 需要在生产环境去掉么?

不用。

它内部做了处理:

只有开发环境才会渲染。

还有,我们是这个组件放在 main.tsx 里的,其实放哪都行。

因为它的事件都是绑定在 window上的:

总结

对于业务代码来说,快速定位源码是很重要的。

因为改动可能很简单,但是项目大了定位在哪里改就比较麻烦了。

我们也可以通过搜索文案、className 的方式,但对于用了 styled-component、做了国际化的项目来说,这种方式也不行。

所以更推荐用 click-to-react-component 来快速定位源码。

只要在页面上 option + 单击,或者 option + 右键单击然后选一个组件,就可以直接打开对应组件源码的行列。

我们看了下它的原理,dom 元素有 __reactFiber$ 属性可以拿到对应 fiber 节点,然后 _debugOwner 拿到父节点 fiber。_debugSource 拿到源码文件路径和行列号。

然后通过 vscode://file/xxx 的方式直接 vscode 打开对应文件行列号。

这样就完成了点击页面元素,打开对应源码的功能。

这里的 _debugSource 是 babel 插件做的,在 @babel/preset-env 里,每个项目会都自动引入这个插件。

然后加上用 dataset.xx 定义属性和对应的 className,用 @floating-ui 实现 popover。

这就是 click-to-react-component 的实现原理了。

这个小组件还是很有用的,感觉是每个 react 项目必备,可以在项目里引入下试试。

更多内容可以看我的小册《React 通关秘籍》

相关推荐
编程猪猪侠18 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞22 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路44 分钟前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架