如果说业务开发中最重要的能力,那定位代码的能力肯定是其中之一。
业务项目一般代码都很多,你拿到一个需求之后,可能改起来不难,但是要定位在哪里改比较难。
特别是接手别人写的代码的时候。
大家都是怎么在不熟悉的项目里定位的代码呢?
很多都学都是搜文案,搜 className。
这样没问题,但如果你用了 styled-component 之类的方案之后,className 都是动态生成的:
而且不少项目都做了国际化,你搜文案会搜到资源包里,而不是组件代码里:
当然,你可以进一步根据国际化的 key 来搜索源码的对应组件。
但这样总归比较麻烦,而且还不一定能搜到准确的位置。
那有什么好的办法可以快速定位代码么?
有,就是 click-to-react-component。
我们创建个项目:
npx create-vte
改下 main.tsx:
安装 antd,我们随便写几个页面:
npm install
npm install --save antd
App.tsx:
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:
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:
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="#">Morea>} style={{ width: 300 }}>
<p>Card contentp>
<p>Card contentp>
<p>Card contentp>
Card>
<Card size="small" title="Small size card" extra={<a href="#">Morea>} style={{ width: 300 }}>
<p>Card contentp>
<p>Card contentp>
<p>Card contentp>
Card>
Space>
);
export default Bbb;
这些都是从 antd 官网复制的 demo 代码。
不用管具体的代码内容,我们只需要看下怎么定位代码。
把开发服务跑起来:
npm run dev
渲染出来是这样的:
如果我们想定位下面卡片的代码,就可以通过搜索文案或者 className:
但复杂项目就不行了。
这时候可以引入 click-to-react-component:
npm install --save-dev click-to-react-component
在 main.tsx 引入下:
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 取出来: