老是写重复代码?用一个实例教你如何使用 VSCode Snippets 解放生产力

1,549 阅读6分钟

VSCode 是微软旗下的一款开源的代码编辑器,深受广大开发者的喜爱。通过安装插件的方式,你可以在上面进行不同语言项目的开发,说是瑞士军刀其实也不为过,是不少开发者(指 Web 前端开发者)的主力开发工具。

其中,VSCode 的 Code Snippets 可以让你很方便地写出重复代码模式的模板,比如循环和条件语句。这里的 Snippet 是片段的意思。如果你在工作中总是写一些和项目相关的重复代码片段,这篇文章一定要看完。

VS Code 内置的 snippets

VS Code 内置了一些语言的 snippets。比如在 JavaScript 文件中,输入 for 后选择要应用的 snippet,按下回车键即可引入代码模板,然后再通过 tab 键跳转依次选中一些变量名,手动修改为自己想要使用的变量名。

1641217383-vscode-snippet-for.gif

当然对于一些 VSCode 不支持的语言或框架,比如 React 和 Vue,我们可以在 VSCode 的插件市场找到一些高质量的 snippet。

1641217575-market-snippet.png

一般来说,VSCode 内置和第三方市场的 snippets 对于日常开发已经够用了,但我们常用的一些代码片段往往是项目特有的代码,这时候就要自己去写自定义的 snippets 了。

编写自己的 VSCode Snippets

下面通过我遇到的一个实际需求的实现来抛砖引玉,来教大家怎么写个 snippets。

我的需求是,在 tsx 文件下输入 rfc (react-fun-component,React 函数组件) 时,可以生成下面的代码片段。这里还有个小要求,这里的函数名需要转换为 大驼峰风格。

import React, { FC } from 'react';

interface IProps {}

const LinkButton: FC<IProps> = (props) => {
  return (
    <div>
      
    </div>
  );
};

export default LinkButton;

第三方插件其实也有 React 函数组件的 snippets,比如 ES7 React/Redux/GraphQL/React-Native snippets 。但是有几个问题:

  1. 代码风格和项目不统一。比如双引号还是单引号,是否加分号等,其实这点还好,我们可以通过 ESLint 在文件保存时修正代码风格。
  2. 需要识别的前缀过于冗长。因为风格太多,同样的效果会有多种相似的写法,在 ES7 React/Redux/GraphQL/React-Native snippets 中, tsrfce (ts-react-fun-component-export) 代表声明一个普通函数组件类,并将其作为模块的默认导出。而 tsrafc (ts-react-arraw-fun-component) 代表声明一个箭头函数组件类,并作为命名导出(非默认导出)。此外还有一堆其他的前缀,用起来很容易选错,而通常我们只会用其中的一种。如果用自己写的 snippet,我们可以把前缀写的更短,且用户自定义的优先级比插件的要高,能更快找到你要是用 snippet。
  3. 不够灵活。开发时的使用的操作系统通常为对文件名大小写不敏感的 macOS 或 Windows 系统,如果你将一个 App.js 改名为 app.js,git 是识别不了的,为了解决这个问题,有些项目可能会规定组件文件名用下划线的命名风格,但代码中的组件函数要保持推荐的大驼峰风格。第三方插件只会傻乎乎地原封不动使用文件名的风格,每次都要手动改成大驼峰风格我实在是受够了,事已至此,唯有自己写个可以转换风格的 snippet 了。

下面我们开搞。

创建一个配置文件

我们可以点击编辑器的左下角设置按钮,然后选择 User Snippets ,选择你要创建的 Snippets 类型。

1641217554-snippet-entry.png

这里我们可以选择 snippets 的应用范围。

  • 全局。全局的话会对每一个项目都应用。VSCode 会在全局的配置路径下创建配置文件,可以是不限定语言的 .code-snippets 后缀,也可以通过文件名 指定语言,如 c.json 表示该配置文件下的模板代码只会在 c 语言中生效。
  • 项目。只在项目内有效。会在根目录下创建一个 .vscode 目录,并创建一个后缀名为 .code-snippets 的文件,不能像全局一样创建指定语言的文件。

字段说明

VSCode 会帮我们创建一个默认的 snippet 文件,使用的是支持注释的 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,0, 1 等)、插入变量的写法(如 $TM_FILENAME_BASE 代表当前移除掉后缀的文件名)。
  • description:snippet 的详细描述。body 里就是我们的代码片段,每一个字符串代表一行。前面几行很简单,就是引入包(import),声明组件传参的类型。到后面第五行,就出现一个比较复杂的结构了。

1641217641-snippet-select-introdution.png

实现

这里我们直接给出需要需实现的最终代码片段。

{
	// 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 的话,大概等价于:

TM_FILENAME_BASE.replace(/(.*)/, (match, $1) => {
	return pascalcase($1); // pascalcase 方法由 VSCode 提供
})

我们继续。\t 则代表制表符。实际会转换为什么,就看你对编辑器做的设置了,比如它可能是 4 个空格或真的制表符。

$0 则是光标最后停留的位置,官方称之为 Tabstops。VSCode 会在代码片段中,通过按下 tab 键,我们可以不断地从 $1 递增定位光标位置。当都找完了,如果还设置了 $0,光标就会跑到 $0 的位置上。

至此,我们的需求便完成了,并不复杂。我们看看最终效果:

1641217379-rfc2.gif

结语

VSCode Snippet 可以有效地解决我们重复写相同项目代码的效率问题,希望本文可以起抛砖引玉的作用,带大家简单入门,能够写一些简单的 snippet。如果想要实现更复杂的需求,可以前往查阅 VSCode 更详尽的官方文档。

如果你感兴趣的话,可以尝试实现文章开头的 for 语句 snippet 来练手,并在留言区进行讨论。

欢迎关注我的公众号:前端西瓜哥。第一时间了解前沿行业消息、分享深度技术干货、获取优质学习资源