老是写重复代码?用一个实例教你如何使用 VSCode Snippets 解放生产力
theme: smartblue
VSCode 是微软旗下的一款开源的代码编辑器,深受广大开发者的喜爱。通过安装插件的方式,你可以在上面进行不同语言项目的开发,说是瑞士军刀其实也不为过,是不少开发者(指 Web 前端开发者)的主力开发工具。
其中,VSCode 的 Code Snippets 可以让你很方便地写出重复代码模式的模板,比如循环和条件语句。这里的 Snippet 是片段的意思。如果你在工作中总是写一些和项目相关的重复代码片段,这篇文章一定要看完。
VS Code 内置的 snippets
VS Code 内置了一些语言的 snippets。比如在 JavaScript 文件中,输入 for
后选择要应用的 snippet,按下回车键即可引入代码模板,然后再通过 tab
键跳转依次选中一些变量名,手动修改为自己想要使用的变量名。
当然对于一些 VSCode 不支持的语言或框架,比如 React 和 Vue,我们可以在 VSCode 的插件市场找到一些高质量的 snippet。
一般来说,VSCode 内置和第三方市场的 snippets 对于日常开发已经够用了,但我们常用的一些代码片段往往是项目特有的代码,这时候就要自己去写自定义的 snippets 了。
编写自己的 VSCode Snippets
下面通过我遇到的一个实际需求的实现来抛砖引玉,来教大家怎么写个 snippets。
我的需求是,在 tsx 文件下输入 rfc
(react-fun-component,React 函数组件) 时,可以生成下面的代码片段。这里还有个小要求,这里的函数名需要转换为 大驼峰风格。
```tsx import React, { FC } from 'react';
interface IProps {}
const LinkButton: FC
</div>
); };
export default LinkButton; ```
第三方插件其实也有 React 函数组件的 snippets,比如 ES7 React/Redux/GraphQL/React-Native snippets 。但是有几个问题:
- 代码风格和项目不统一。比如双引号还是单引号,是否加分号等,其实这点还好,我们可以通过 ESLint 在文件保存时修正代码风格。
- 需要识别的前缀过于冗长。因为风格太多,同样的效果会有多种相似的写法,在 ES7 React/Redux/GraphQL/React-Native snippets 中,
tsrfce
(ts-react-fun-component-export) 代表声明一个普通函数组件类,并将其作为模块的默认导出。而tsrafc
(ts-react-arraw-fun-component) 代表声明一个箭头函数组件类,并作为命名导出(非默认导出)。此外还有一堆其他的前缀,用起来很容易选错,而通常我们只会用其中的一种。如果用自己写的 snippet,我们可以把前缀写的更短,且用户自定义的优先级比插件的要高,能更快找到你要是用 snippet。 - 不够灵活。开发时的使用的操作系统通常为对文件名大小写不敏感的 macOS 或 Windows 系统,如果你将一个 App.js 改名为 app.js,git 是识别不了的,为了解决这个问题,有些项目可能会规定组件文件名用下划线的命名风格,但代码中的组件函数要保持推荐的大驼峰风格。第三方插件只会傻乎乎地原封不动使用文件名的风格,每次都要手动改成大驼峰风格我实在是受够了,事已至此,唯有自己写个可以转换风格的 snippet 了。
下面我们开搞。
创建一个配置文件
我们可以点击编辑器的左下角设置按钮,然后选择 User Snippets
,选择你要创建的 Snippets 类型。
这里我们可以选择 snippets 的应用范围。
- 全局。全局的话会对每一个项目都应用。VSCode 会在全局的配置路径下创建配置文件,可以是不限定语言的
.code-snippets
后缀,也可以通过文件名 指定语言,如c.json
表示该配置文件下的模板代码只会在 c 语言中生效。 - 项目。只在项目内有效。会在根目录下创建一个 .vscode 目录,并创建一个后缀名为
.code-snippets
的文件,不能像全局一样创建指定语言的文件。
字段说明
VSCode 会帮我们创建一个默认的 snippet 文件,使用的是支持注释的 JSON 语法。这里面会提供一个做了注释的例子,我们将其取消注释再改改就能用了。
json
{
// Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"Print to console": {
"scope": "javascript,typescript",
"prefix": "log",
"body": [
"console.log('$1');",
"$2"
],
"description": "Log output to console"
}
}
先通过这个官方给出的简单例子,简单说说各种字段的意义。
- 最外层的 key ("Print to console")代表的是 snippet 的名字,起标识作用。
- scope 用于指定应用的语言。如果有多种语言,用逗号分隔。如果想要支持所有语言,直接将这个字段省略即可。
- prefix 则是触发智能提示的输入内容,可以看到,出现同名的前缀 log 时,用户自定义的 snippet 在更前面的位置。
- body 则是模板代码内容,是个字符串数组,每个字符串代表一行代码。支持 tab 键切换光标定位($0, $1 等)、插入变量的写法(如 $TM_FILENAME_BASE 代表当前移除掉后缀的文件名)。
- description:snippet 的详细描述。body 里就是我们的代码片段,每一个字符串代表一行。前面几行很简单,就是引入包(import),声明组件传参的类型。到后面第五行,就出现一个比较复杂的结构了。
实现
这里我们直接给出需要需实现的最终代码片段。
jsx
{
// Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"Custom React functional component": {
"prefix": "rfc",
"scope": "typescriptreact",
"body": [
"import React, { FC } from 'react';",
"",
"interface IProps {}",
"",
"const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}: FC<IProps> = (props) => {",
"\treturn (",
"\t\t<div>",
"\t\t\t$0",
"\t\t</div>",
"\t);",
"};",
"",
"export default ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/};"
],
"description": "React Functional Component"
}
}
body 里就是我们的代码片段,每一个字符串代表一行。前面几行很简单,就是引入包(import),声明组件传参的类型。到后面第五行,就出现一个比较复杂的结构了。
变量和对变量做正则转换
这里出现了一个名为 变量 的概念。变量大致可以分为几个大类:
- 文件信息变量:从我们所在文件的上下文得到的。比如 TM_SELECTED_TEXT 表示选中的内容。
- 时间变量。比如 CURRENT_YEAR 代表当前时间。
- 随机数。比如 RANDOM 表示长度为 6 的 10 进制随机数字符串
- 注释块。比如 LINE_COMMENT 表示行注释。
下面我们对 ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}
结构进行分析。这是对特定的变量做正则处理,格式为:
${变量/正则表达式/替换内容}
TM_FILENAME_BASE
是我们正在编辑的文件的文件名,不包含后缀名。(.*)
。这是一个正则表达式,表示匹配整个变量字符串,且用圆括号指定了捕获组,于是在这里,$1 表示整一个完整的字符串。${1:/pascalcase}
对 $1 应用pascalcase
方法,将其转换为大驼峰风格。
如果你熟悉 JavaScript 的话,大概等价于:
jsx
TM_FILENAME_BASE.replace(/(.*)/, (match, $1) => {
return pascalcase($1); // pascalcase 方法由 VSCode 提供
})
我们继续。\t
则代表制表符。实际会转换为什么,就看你对编辑器做的设置了,比如它可能是 4 个空格或真的制表符。
$0
则是光标最后停留的位置,官方称之为 Tabstops。VSCode 会在代码片段中,通过按下 tab 键,我们可以不断地从 $1
递增定位光标位置。当都找完了,如果还设置了 $0
,光标就会跑到 $0
的位置上。
至此,我们的需求便完成了,并不复杂。我们看看最终效果:
结语
VSCode Snippet 可以有效地解决我们重复写相同项目代码的效率问题,希望本文可以起抛砖引玉的作用,带大家简单入门,能够写一些简单的 snippet。如果想要实现更复杂的需求,可以前往查阅 VSCode 更详尽的官方文档。
如果你感兴趣的话,可以尝试实现文章开头的 for 语句 snippet 来练手,并在留言区进行讨论。
欢迎关注我的公众号:前端西瓜哥。第一时间了解前沿行业消息、分享深度技术干货、获取优质学习资源
- WebSocket 入门:简易聊天室
- JavaScript写一个 once 函数,让传入函数只执行一次
- 需要学习哪些东西,才能找到前端开发工作?
- ahooks 的 useClickAway 在 React 17 中不工作了!
- 在 React 中使用 Redux 的 4 种写法
- 如何使用 CSS 自定义无序列表样式
- 稀土掘金这个平台有什么问题?
- JS 的异步遍历,你真的会写吗?
- 一道关于 box-sizing 的字节面试题
- 为什么产品国际化看似简单,实际落地却困难重重?
- Web 前端陈年烂题:['1', '2', '3'].map(parseInt) 结果是什么?
- 老是写重复代码?用一个实例教你如何使用 VSCode Snippets 解放生产力
- 前端国际化,该如何实现按需加载语言包?