背景
之前调试一直是在项目里写 console.log() 和 debugger调试,简单快捷,但其实也不算方便,而且还经常忘记删,虽然打包时有插件可以移除,但总归不算优雅;
一直想把 vscode 的调试器用起来,但就是太懒了?,这么多配置懒得看;终于痛定思痛花重金购入神光的调试小册,学会之后发现使用起来还是很简单的;这里对学习的内容做一下总结梳理。
文末有简单的示例,带有注释,如果你也很懒就可以直接拿来先用起来;
优势
使用调试器的优势如下:
-
保持业务代码干净不用加了调试代码又忘了删除
-
可以随时中止流程
-
能查看所有可用的变量,不用写一堆console,漏写又要重跑流程等等等等
基本配置
我们先创建一个demo项目
// 环境 node v18.16.0
yarn create react-app react-debug-demo
进入 vscode 的 debugger 面板,点击 create a launch file
,输入Web App
,选择 Web App(Chrome)
,vscode 会在 .vscode
文件夹下自动创建配置文件 launch.json
,里面是调试器的基本配置
当然如果项目下已经有 .vscode/launch.json
文件,左侧 debugger 面板就是这样的
version
是配置文件的版本,用来确保和调试器的兼容性,默认即可;
configurations
数组里每个对象对应一个调试器启动项,通过name
字段的值选择,可以配置多个;可以看到除了我们自定义的,还有一些默认选项;
我们先来过一下这几个配置项
type
type
表示调试器的运行环境,比如我们想通过 chrome 调试 react 应用,type
值就用 chrome
;如果我们想调试项目的打包过程或者是node应用,type
值就是 node
,当然还有很多其它可选项,注意不同的 type
值会影响其它配置项的可用性,以下配置都以 chrome 或 node 为例
request
request
有两个取值,attach
和 launch
launch
可以帮我们以调试模式启动 chrome 或 node (具体看你的type
配置),并与 vscode 调试器建立连接
attach
用于与已经处于调试模式的 chrome 或 node 建立连接
启动 chrome 或 node 的调试模式需要用到命令行,可以自行了解,一般用 launch 就好
name
上面讲过,就是当前配置项的名称,作为启动器的选项
url
调试器要访问的地址,本地调试的话就是 npm run start
启动项目时的地址,只有 request
为 launch
时才生效
对应的,如果 request
为 attach
,则url
字段无效, 需要设置的字段就是 port
,即以调试模式启动应用时的端口
webRoot
设置项目代码所在的根目录,${workspaceFolder}
是 vscode 的内置变量,表示 vscode 当前打开的文件夹的路径
最基本的字段就这些,现在就可以用起来啦
调试
我们先改造下 App.js
模拟一个非常常见的场景,加工接口返回的列表,然后展示(为了展示清晰,我用了两次循环?)
import './App.css';
import { useEffect, useState } from 'react';
const fetchData = () => {
return new Promise((res) => {
setTimeout(() => {
res([
{
id: 'top',
title: 'title',
content: 'content',
},
{
id: 'middle',
title: 'title',
content: 'content',
},
{
id: 'bottom',
title: 'title',
content: 'content',
},
]);
}, 1000);
});
};
function App() {
const [list, setList] = useState([]);
useEffect(() => {
const promiseResCall = (res) => {
const result = res.map(i => {
return {
...i,
title: `${i.id} ${i.title}`,
content: `${i.id} ${i.content}`
}
})
setList(result);
}
fetchData().then(promiseResCall);
}, []);
return (
<div className='App'>
<ul>
{list.map((item) => (
<li key={item.id}>
<h4>{item.title}</h4>
<p>{item.content}</p>
</li>
))}
</ul>
</div>
);
}
export default App;
接下来我们给需要调试的位置打上断点,在代码行数旁点击一下出现小红点即可
npm run start
项目启动!
再选择我们的配置,点击启动按钮
调试器启动!
可以看到浏览器被自动打开,之后又自动切到了vscode界面我们断点的位置;
接下来我们看下界面右上角的操作按钮
恢复运行
可以理解为每次执行”一行”,遇到函数时不进入函数内部
每次执行”一行”,遇到函数时进入函数内部
跳出当前函数
重启调试器
停止调试器
界面左侧的面板,最常用到的就是
VARIABLES
:变量,依据作用域划分
CALL STACK
:调用栈,对于屎山?项目,调用栈可以大大提高调试效率;需要注意的是,匿名函数会被展示为 anonymous
;
调用栈里一堆 commitxxxx
的函数是 react
的源码调用,我们调试业务代码的时候,可能并不需要关注第三方库如何运作,可以在 launch.json
里加上配置
"skipFiles": ["${workspaceFolder}/node_modules/**/*.js"]
这样可以折叠 node_modules 里js文件的调用,停止调试模式后重新启动(直接用刷新按钮配置可能不会生效)
可以看到刚刚 react 的调用栈已经被折叠了,这个字段有个默认配置
"<node_internals>/**"
,它会折叠 node 的调用栈
BREAKPOINTS
:就是我们打的所有断点,最右上角的两个按钮分别是所有断点的启禁用切换和清空断点; 勾选 Caught Exceptions
和 Uncaught Exceptions
可以分别在捕获到错误和抛出未被捕获的错误时断住
以上就是使用调试器最基本的内容啦,除此之外我们还需要一些技巧来提高我们的调试效率
右键点击断点,可以看到断点是能编辑的
里面有三个选项,用法其实已经写明了,主要用到的是
Expression
:表达式返回true时断点生效;这个在循环里很有用,输入条件,敲回车即可;程序会在达成条件时断住
Log Message
:在控制台打印输出,用{}
包裹变量即可,调试器的默认控制台是 DEBUG CONSOLE
userDataDir
userDataDir
这个字段用来设置调试器启动的chrome的用户数据储存目录;
可以看到,调试器打开的chrome是没有任何书签,插件和cookie等本地储存的,这是因为 userDataDir
的默认值是 true,即每次启动都使用临时目录;这样每次调试插件和cookie都会被清空,很不方便;
将 userDataDir
设置为 false
的话可以使用默认的用户数据目录;重启调试器,可以看到各种插件都是在的;
但是这样有个问题,同一个数据目录同一时间只能被一个 chrome 实例使用,也就是说,当我们用chrome打开网页之后,调试器就不能用这个chrome调试了
可以看到,因为我已经开了其它网页,所以地址栏并没有成功访问我们的调试地址 http://localhost:3000
,而是about:blank
, vscode 也报错了;需要退出 chrome ,重启调试器才行;这样我们调试的时候就不能正常使用 chrome 了,也很不方便
有两个解决办法
一个是设置一个目录专门用来储存调试时的用户数据 ,比如
"userDataDir": "${workspaceFolder}/data"
还有一个办法是下载另一个版本的 chrome,比如 canary
,这是chrome的每日构建版,需要先到官网去下载,下载安装完成,设置 runtimeExecutable
字段为 canary
;
"runtimeExecutable": "canary",
"userDataDir": false,
在 type
为 chrome 时,runtimeExecutable
可以设置为可执行文件的绝对路径 或者 浏览器的版本,比如 stable
(默认值),canary
,dev
等,当然你要先下载安装这些版本浏览器才可用;
这样我们就可以将一个版本的浏览器用来开发,一个用来日用了;
当然如果你想同时调试多个项目还是需要给每个项目的userDataDir 设置为不同的值的;
还有几个常用的配置
runtimeArgs
调试器启动时携带的额外参数
env
设置环境变量
console
设置输出的控制台,internalConsole
为默认值,输出在 DEBUG CONSOLE
,integratedTerminal
输出在 TERMINAL
,需要注意这个字段是受 type
影响的
理解了每个字段的含义其实我们就能组装出各种调试配置了,比如下面是一个简单的node调试器的配置,可以用来调试项目打包
{
"type": "node",
"request": "launch",
"name": "Launch via NPM",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "build"],
"env": {
"NODE_ENV": "production"
},
"console": "integratedTerminal"
},
很好理解,其实就是调试器帮我们运行了 npm run build
,并设置了一个环境变量 NODE_ENV
, 将日志打印在 TERMINAL
;如果你想运行别的版本的node,可以直接替换掉 runtimeExecutable
换成别的版本的node绝对路径,注意,换成执行 node 后参数也要从 npm 的 run build
改成实际执行的脚本,比如
{
"type": "node",
"request": "launch",
"name": "Launch via node",
"runtimeExecutable": "/Users/dqqbl/.nvm/versions/node/v18.16.0/bin/node",
"runtimeArgs": ["scripts/build.js"],
"env": {
"NODE_ENV": "production"
},
"console": "integratedTerminal"
},
打断点和调试的方式都是一样的,配置好,选择配置,加上断点,启动即可;当然调试到的可能是编译后的代码,如何调试源码就要学习 sourcemap
的知识了,这个可以去看看小册;
示例
{
"version": "0.2.0",
"configurations": [
{
"name": "debug react",
"request": "launch",
"type": "chrome", // 调试器运行环境
"runtimeExecutable": "canary", // 可执行文件的绝对路径或浏览器版本,如果是浏览器版本需要先下载安装
"userDataDir": false, // chrome 保存的用户数据目录
"url": "http://localhost:8080", // 浏览器访问的url
"runtimeArgs": [
"--auto-open-devtools-for-tabs" // 启动时自动打开开发者工具
], // 启动时携带的参数
"webRoot": "${workspaceFolder}" // 项目根目录
},
{
"name": "Launch via NPM",
"request": "launch",
"type": "node",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "build"],
"console": "integratedTerminal"
},
{
"name": "Launch via Node",
"request": "launch",
"type": "node",
"runtimeExecutable": "/Users/dqqbl/.nvm/versions/node/v18.16.0/bin/node",
"runtimeArgs": ["scripts/build.js"],
"console": "integratedTerminal"
},
]
}
本文专注于先用起来,还有很多其它的可配置项,学会了基本用法其它就是选项的灵活搭配组合而已,如果碰到不认识的可以直接上 vscode 的官网上搜,注意区分 type
不同时可用配置不同