更多文章,参见大搜车技术博客:blog.souche.com/
大搜车无线开发中心持续招聘中,前端,Nodejs,android 均有 HC,简历直接发到:sunxinyu@souche.com
前言
大家都是出来写代码的,少不了要写上千万来行代码,其中重复性的代码占比又会很大。那么如何避免一次又一次写重复性的代码呢?除了代码自身的优雅、可复用,Jetbrains系如Intellij IDEA的Live Templates
或Visual Studio Code的Snippet
等内置工具都能很大程度上帮我们少写很多重复性的代码。下面就vsc的Snippet
,结合demo,讲讲它的用法。
创建Snippet
快捷键Cmd + Shift + P
或F1
打开命令窗口,输入snippet
,选中配置用户代码片段
,这时候会弹出
可以在现有的代码片段文件上新增snippet,也可以自己新建代码片段文件。
Snippets片段以JSON格式定义,官方提供的例子如下
{
"For_Loop": {
"prefix": "for",
/**
* 在全局代码片段文件新增snippet时,
* 通过该项来指定该snippet使用范围,
* 如下所示,在文件以`.js`或者`.ts`结尾时可使用该snippet
*/
"scope": "javascript,typescript",
"body": [
"for (const ${2:element} of ${1:array}) {",
"\t$0",
"}"
],
"description": "For Loop"
}
}
复制代码
For_Loop
is the snippet name(后续在指定快捷键绑定的时候会用到).prefix
defines how this snippet is selected from IntelliSense and tab completion. In this casefor
.(前缀支持N:1,比如prefix
值为["for","fof"]
,则for
和fof
对应同一条代码片段)body
is the content and either a single string or an array of strings of which each element will be inserted as separate line.description
is the description used in the IntelliSense drop down(非必填).
上述英文解释引自官方,怕翻译不到位影响读者理解,就不作翻译了。
下面用一张图展示在使用中各个属性对应的位置
prefix
,右侧圈中的为
description
,
description
下方的为
body
部分内容
Snippet语法
Snippet
的语法集中在body
上。
Tabstops
: 即$1
、$2
等。使用$1
、$2
等表示按下键盘tab
后光标将要指向的位置。根据数字大小表示先后顺序。$0
表示光标最后指向的位置。可以存在多个相同的Tabstops
,并会同步更新。
以下是多个相同的Tabstops同步更新的示例
{
"For_Loop": {
"prefix": "for",
"scope": "javascript,typescript",
"body": [
"for (const ${2:element} of ${1:array}) {",
"\tconst item = $1[$2];$0",
"}"
],
"description": "For Loop"
}
}
复制代码
以下是使用上述snippet的过程
Placeholders
:占位符。用户直接跳过Tabstops
不输入新值时,会使用占位符。它还能内嵌。
以下是占位符内嵌的示例
{
"placeholder": {
"prefix": "ph",
"body": "${1:hello ${2:world}}$0"
}
}
复制代码
以下是使用上述snippet的过程
Choice
: 可选的占位符
以如下snippet为例
{
"choice": {
"prefix": "ch",
"body": "${1|hello,hi,how are you|}"
}
}
复制代码
输入ch
按下tab键后,就会显示
Choice
中,,
、|
、$
、}
、\
可以使用\
转义;其他情况有且仅$
、}
、\
可以使用\
转义,其他字符无法转义, 详见https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar
Variables
: 官方定义的变量
TM_SELECTED_TEXT:当前选定的文本或空字符串;
TM_CURRENT_LINE:当前行的内容;
TM_CURRENT_WORD:光标所处单词或空字符串
TM_LINE_INDEX:行号(从零开始);
TM_LINE_NUMBER:行号(从一开始);
TM_FILENAME:当前文档的文件名;
TM_FILENAME_BASE:当前文档的文件名(不含后缀名);
TM_DIRECTORY:当前文档所在目录;
TM_FILEPATH:当前文档的完整文件路径;
CLIPBOARD:当前剪贴板中内容。
时间相关
CURRENT_YEAR: 当前年份;
CURRENT_YEAR_SHORT: 当前年份的后两位;
CURRENT_MONTH: 格式化为两位数字的当前月份,如 02;
CURRENT_MONTH_NAME: 当前月份的全称,如 July;
CURRENT_MONTH_NAME_SHORT: 当前月份的简称,如 Jul;
CURRENT_DATE: 当天月份第几天;
CURRENT_DAY_NAME: 当天周几,如 Monday;
CURRENT_DAY_NAME_SHORT: 当天周几的简称,如 Mon;
CURRENT_HOUR: 当前小时(24 小时制);
CURRENT_MINUTE: 当前分钟;
CURRENT_SECOND: 当前秒数。
注释相关
BLOCK_COMMENT_START: 在PHP中输出 /* ,在HTML中输出 <!--
BLOCK_COMMENT_END: 在PHP中输出 */ ,在HTML中输出 -->
LINE_COMMENT: 在PHP中输出 // ,在HTML中输出 <!-- -->
复制代码
Variable transforms
变量转换可将变量的值格式化处理后插入预定的位置。 它包括三个部分:
- 正则表达式
- 格式字符串
- 正则表达式匹配选项
正则表达式和正则表达式匹配选项不作解释。
格式字符串在官方文档的Grammar
中如下
format ::= '$' int | '${' int '}'
| '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
| '${' int ':+' if '}'
| '${' int ':?' if ':' else '}'
| '${' int ':-' else '}' | '${' int ':' else '}'
复制代码
假设某整体片段为${variable/regexp/(format|text)/options}
,再结合上述语法, 整体片段可变为
${var_name/regular_expression/$1/options}
${var_name/regular_expression/${1}/options}
${var_name/regular_expression/${1:/upcase}/options}
///downcase
和/capitalize
使用方式同/upcase
${var_name/regular_expression/${1:+if}/options}
${var_name/regular_expression/${1:?if:else}/options}
${var_name/regular_expression/${1:-else}/options}
${var_name/regular_expression/${1:else}/options}
${var_name/regular_expression/text/options}
上述format
中$
后的数字1表示分组捕获组(正则表达式中()
匹配到的数组)的第1项,同理$2表示分组捕获项第2项。分组捕获相关信息详见分组捕获
现有文件名global-js.json
,以demo说明上述片段含义。为了精简,输出值直接以body末尾注释表示,另外多个body直接放在一个snippet(实际操作不规范,仅仅为了精简)
注:使用transform末尾必须加/
,才能被识别。如${TM_FILENAME_BASE/(global).*/$1/
}
其中1
、2
意义相同
代码片段
"transform": {
"prefix": "tr",
"body": "${TM_FILENAME_BASE/(global).*/$1/}" // 输出: global
}
// 待匹配字符串 global-js
// 正则表达式 (global).*
// 全部匹配项 global-js
// 分组捕获组 ['global'],即$1=global
// $1替换global-js,即输出 global
复制代码
3
表示对匹配到的相应分组捕获组转化成大写,或小写,或首字母大写,以替换正则表达式匹配到的全部字符串
代码片段(该片段仅展示转化成大写)
"transform": {
"prefix": "tr",
"body": "${TM_FILENAME_BASE/(global).*/${1:/upcase}/}" // 输出: GLOBAL
}
// 待匹配字符串 global-js
// 正则表达式 (global).*
// 全部匹配项 global-js
// 分组捕获组 ['global'],即$1=global
// 经${1:/upcase}转换后,为GLOABl
// ${1:/upcase}替换global-js,即输出GLOABl
复制代码
4
表示匹配成功时,将if
所述语句完全替换正则表达式匹配项。
代码片段
"transform": {
"prefix": "tr",
"body": "${TM_FILENAME_BASE/(global)/${1:+if}/}", //输出: if-js
// 待匹配字符串 global-js
// 正则表达式 (global)
// 全部匹配项 global
// 分组捕获组 ['global'],即$1=global
// $1匹配成功, if替换global,即输出if-js
"body": "${TM_FILENAME_BASE/(global).*/${1:+if}/}" //输出: if
// 待匹配字符串 global-js
// 正则表达式 (global).*
// 全部匹配项 global-js
// 分组捕获组 ['global'],即$1=global
// $1匹配成功, if替换global-js,即输出if
}
复制代码
上述3、4两个示例匹配失败时,不做转换,即仍输出global.js
5
表示匹配成功,且相应的分组捕获成功时,将if
所述语句完全替换正则表达式匹配项;
匹配成功,且相应的分组捕获为空时,将else
所述语句完全替换正则表达式匹配项;
匹配失败时,else
所述语句即为处理后的变量
代码片段
"transform": {
"prefix": "tr",
"body": "${TM_FILENAME_BASE/(global)/${1:?if:else}/}", //输出: if-js
// 待匹配字符串 global-js
// 正则表达式 (global)
// 全部匹配项 global
// 分组捕获组 ['global'],即$1=global
// $1匹配成功, if替换global,即输出if-js
"body": "${TM_FILENAME_BASE/global/${1:?if:else}/}", //输出: else-js
// 待匹配字符串 global-js
// 正则表达式 global
// 全部匹配项 global
// 分组捕获组 [],$1对应的分组捕获为空
// else替换global,即输出else-js
"body": "${TM_FILENAME_BASE/(globald)/${1:?if:else}/}", //输出: else
// 待匹配字符串 global-js
// 正则表达式 (globald),匹配失败
// 全部匹配项 null
// 分组捕获组 无
// 输出 else
}
复制代码
6
同7
,表示匹配成功,且分组捕获(正则表达式中()
匹配到的项)成功时,不变;
匹配成功,且分组捕获为空时,将else
所述语句完全替换正则表达式匹配项;
匹配失败时,else
所述语句即为处理后的变量
代码片段
"transform": {
"prefix": "tr",
"body": "${TM_FILENAME_BASE/(global)/${1:else}/}", //输出: global-js
// 待匹配字符串 global-js
// 正则表达式 (global)
// 全部匹配项 global
// 分组捕获组 ['global'],即$1=global
// 不变,输出global-js
"body": "${TM_FILENAME_BASE/global/${1:else}/}", //输出: else-js
// 待匹配字符串 global-js
// 正则表达式 global
// 全部匹配项 global
// 分组捕获组 [],$1对应的分组捕获为空
// else替换global,输出else-js
"body": "${TM_FILENAME_BASE/(globald)/${1:else}/}", //输出: else
// 待匹配字符串 global-js
// 正则表达式 (globald),匹配失败
// 全部匹配项 null
// 分组捕获组 无
// 输出 else
}
复制代码
8
表示匹配成功时,将text
完全替换正则表达式匹配项; 匹配失败时,不变
代码片段
"transform": {
"prefix": "tr",
"body": "${TM_FILENAME_BASE/(global)/text/}", //输出: text-js
}
复制代码
Placeholder Transform
本质与Variable Transform
的第8条一致,只是正则表达式变为占位符常量
代码片段
"transform": {
"prefix": "tr",
"body": "$1 -> ${1/placeholder/text/}", //输入: placeholder 输出: placeholder -> text
}
复制代码
快捷键导入snippet
首先打开keybindings.json
然后可以添加如下
{
"key": "cmd+k 1",
"command": "editor.action.insertSnippet",
"when": "editorTextFocus",
"args": {
"snippet": "console.log($1)$0"
}
}
复制代码
亦或者导入已有的snippet
{
"key": "cmd+k 1",
"command": "editor.action.insertSnippet",
"when": "editorTextFocus",
"args": {
"langId": "csharp", // 语言包,如javascript,typescript
"name": "myFavSnippet" // 对应最开始提过的name,如下面使用场景的`import snippet`
}
}
复制代码
使用场景
- 用
snippet
写snippet
"import snipppet": {
"prefix": "sni",
"body": ["\"$1\": {", "\t\"prefix\": \"$2\",", "\t\"body\": [\"$3\"]", "}"]
},
复制代码
加个可选的description
"snipppet": {
"prefix": "sni",
"body": [
"\"$1\": {",
"\t\"prefix\": \"$2\",",
"\t\"body\": [\"$3\"]${4:,\n\t\"description\":\"${5}\"}",
"}"
]
}
复制代码
- 某些固定格式的数组对象
如某个数组长度较大的对象{title:'input',dataIndex:'input'}
,该数组中对象的每个title
或dataIndex
不尽相同,则可以写个临时snippet,直接生成该结构。
"templates": {
"prefix": "tem",
"body": [",{dataIndex:$1,", "title:$2}"],
"description": ""
}
复制代码
- 某些固定内容的文件或者某些常用的
import
组合
"muji store file": {
"prefix": "store",
"body": [
"import { createStore } from '@souche-f2e/muji'",
"",
"type IState = {",
"",
"}",
"const state: IState = {",
"",
"}",
"const store = createStore({",
" state,",
" reducers: {",
" ",
" },",
" effects: {",
" ",
" },",
"})",
"export default store"
]
},
复制代码
- muji react 下的pages文件
"index.tsx under pages": {
"prefix": "ind",
"scope": "typescriptreact",
"body": [
"import React, { Component } from 'react'",
"import { dispatch, IRootState } from '@@/store'",
"import { connect } from '@souche-f2e/muji'",
"const mapStateToProps = (state: IRootState) => state.${1/(.*)/${1:/downcase}/}",
"",
"interface I$1Props extends ReturnType<typeof mapStateToProps> {",
" ",
"}",
"interface I${1}State {",
"",
"}",
"class $1 extends Component<I${1}Props, I${1}State> {",
" render() {",
" return ${3:null}",
" }",
"}",
"export default connect(mapStateToProps)($1)"
]
}
复制代码
只需要输入组件名,就可以一次性生成必备的格式。
最后
安利一下prettier,配合snippet使用,简直绝配。