通过TypeScript的这个现代超集,可以提前获得ECMAScript的建议和灵活的附加功能。
有传言说Civet是新的CoffeeScript,但也许这是件好事。CoffeeScript给官方的JavaScript规范带来了类、去结构化、async/await、箭头函数、休息参数等内容。也许Civet会让管道操作符、模式匹配等进入ES2025。
-Civet的创建者Daniel Moore
Civet被描述为一种用于TypeScript的现代CoffeeScript,如果你像我一样记得CoffeeScript,这听起来可能并不乐观。不过,在你把它注销之前,请考虑一下Civet所能提供的东西。这是一种紧凑的现代语言,旨在为你提供你所喜欢的TypeScript的一切,并具有更多的功能和简单性,包括早期访问拟议的ECMAScript语言功能。你可能会对Civet的一些功能感到惊讶,因为它只需很少的努力就能让你掌握。
什么是Civet?
由于Civet经常被拿来与CoffeeScript进行比较,因此,首先考虑它们的共同点以及不同点是很有帮助的。
与TypeScript一样,CoffeeScript是JavaScript的超集。它是在JavaScript以其许多缺点而闻名的时代发布的,而CoffeeScript是解决这些问题的一个权宜之计。JavaScript很快就向更高的表现力和能力发展,而CoffeeScript被视为一个不必要的附加层。
另一方面,Civet被设计成一个增值层,它不断成长和发展,为TypeScript(和JavaScript)代码提供最先进的功能。如果你想尝试它的额外语法,你可以简单地将Civet添加到你的构建管道中,例如使用Vite或esbuild。
由于Civet直接转译为TypeScript,它在IDE中拥有强大的开发时间支持。正如Civet开发者Erik Demaine所说:”一个巨大的优势是VS Code LSP[语言服务器协议扩展],所以当你编辑文件时,它会自动转译和运行TypeScript,对错误进行下划线,并提供悬停帮助。”
在接下来的章节中,我们将看一下Civet语法的主要特点。这是一个相当小的块状物,可以容纳在大脑中。请记住,有效的TypeScript也是有效的Civet。
使用Civet在JSX中进行迭代
Civet可以处理JSX。它可以让你跳过标记中的关闭标签,如果你缩进标签,它就会替你关闭。它还能自动将多个同级元素或片段包装成一个父片段。我个人在使用JSX时最大的失望之一是处理列表的迭代问题。典型的方法是将JavaScript直接交织在标记中,如清单1所示。
清单1. 在JSX中进行迭代
<For each={props.items}>
{(item) => {
return (
<li class="item" style={props.style}>
<Item item={item} />
</li>
);
}}
</For>
对我来说,这似乎是不必要的笨拙。一旦你习惯了它,就可以忍受了,而且还有其他的方法来进行迭代(尽管用JavaScript写标记也同样笨拙)。但是,当你需要做的只是对一个集合进行迭代时,如果能简单到只需即时输入就可以了,那就太好了。
清单2显示了使用Civet编写的清单1中的循环。
清单2. 在Civet中进行迭代
<For each=props.items>
(item) =>
<li .item {props.style}><Item {item}>
清单2中的代码更容易记忆,也更容易打出来,不用查。
Civet管道操作符
Civet在TypeScript管道操作符成为正式文件之前,给了你一个建议。这个功能的基本想法是允许在没有嵌套或流畅的方法链的情况下进行组合操作。就像CoffeeScript曾经为JavaScript所做的那样,Civet让你在它正式进入TypeScript之前就能使用这个功能。
使用管道操作符 (|>
)使得以一种易于阅读的方式编写连锁操作成为可能。
比方说,你已经定义了几个操作(例如,foo
, bar
, 和 baz
),现在你想把它们结合起来使用来修改一个变量。在直接的JavaScript中,你可能会出现嵌套的函数调用,如 foo(bar(baz(myVar)))
或可能是 baz(myNum).bar().foo()
。两者都很笨重,而且随着事情变得越来越复杂,它们变得越来越难解读。你可以用管道操作符在Civet中执行同样的逻辑,如清单3所示。
清单3. 使用Civet管道操作符
let foo = function(){ … }
let bar = function(){ … }
let baz = function(x){ … }
let myVar = “some value”;
myVar |> baz |> bar |> foo;
管道操作符使几个操作一起进行更加清晰明了。
更强大的开关
另一个提议的ECMAScript功能是模式匹配,你可以用Civet提前采用。TC39的提案有很多内容,旨在解决ECMAScript switch
语句的缺陷。目前,正如该提案所指出的, switch
“包含了大量的footguns,如意外的case fallthrough和模棱两可的范围”。它还严重缺乏匹配能力。
虽然提案中引入了一个新的关键字 match
,但Civet将许多建议的匹配改进直接应用到 switch
语句中。例如,在Civet中,你可以如清单4所示,使用更高级的匹配来切换一个字符串。
清单4. 在Civet中用模式进行切换
let s = [{type:"text", content:"foobar"},'test2'];
switch s
""
console.log "nothing"
/\s+/
console.log "whitespace"
"hi"
console.log "greeting"
[{type: "text", content}, ...rest]
console.log("leading text", content)
// outputs “leading text foobar”
Civet的 switch
语句相当强大,不仅仅是添加了regex模式。它实际上能够(在上面的第四个案例中)将参数作为一个数组进行类型检查,将第一个元素作为一个对象进行检查,然后使用对象的属性来执行其工作。相当复杂。
还要注意的是, switch
语句已经取消了 break
语句,这是TC39提案中的建议。默认情况下,执行不会落到下一个案例。
Loops
让我们回到循环的话题上,Civet有能力在某些情况下简化循环的语法。作为快速浏览,请看清单5,该清单在一个整数数组上循环,创建一个新的整数数组。该循环是以三种方式执行的。Civet,程序化的JavaScript,和功能化的JavaScript。
清单5. 在数组上循环操作
const list =[1,2,3,4,5,6,7];
// Civet
squares :=
for item of list
item * item
// programmatic JS
const squares = (() => {
const results = [];
for (const item of list) {
results.push(item * item);
}
return results;
})();
// functional JS
squares = list.map((x) => { return x*x }));
单参数函数的速记
Civet包括一个 “单参数函数速记” 来代替 (x) => { }
。清单6显示了几个例子和它们的普通JavaScript等价物。
清单6. 单参数函数速记
let x = [{name:'test123'},{name:'Another name'}];
console.log(x.map .name);
console.log(x.map &.name?.slice(0,4));
console.log(x.map((x) => x.name));
console.log(x.map((x) => { return x.name?.slice(0, 4)}));
// output is the same in both groups:
[ "test123", "Another name" ]
[ "test", "Anot" ]
清单6向你展示了如何声明一个没有括号和箭头的单个函数参数。第二个例子中的安培尔字符允许引用该参数。
切片数组和字符串
Civet有少量的快捷方式用于切片数组和字符串。它允许你在数组上像使用函数参数一样使用方括号,而参数会像你所期望的那样传递给 slice()
。作为一个例子,在清单7中,我们对一个字符串进行了几次切片操作。请注意方括号带来的便利。
清单7. Slice() shortcuts
let s = "Words are flowing out like endless rain";
console.log(s[10..16]); // outputs “flowing”
// equivalent to console.log(s.slice(10, 1 + 16 || 1 / 0));
console.log(s[-4..]); // output “rain”
// equivalent to console.log(s.slice(-4));
console.log(s[...5]); // outputs “Words”
// equivalent to console.log(s.slice(0, 5));
在这里,你可以看到该语法如何使 slice()
的使用更简单一些。注意,两点和三点的语法分别意味着对最后一个元素的排斥和包容。
配置
Civet包括相当多的配置来微调它的工作方式,这在迁移项目,特别是使用CoffeeScript的项目时可以起到帮助。关于配置选项的更多细节可以在这里找到。
小结
Civet还有很多,但这是一个很好的样本。请参阅Civet Cheatsheet,了解Civet的语法与TypeScript语法的比较。请参阅GitHub上的Civet项目,以了解对Civet的更深入了解。
作为一个思想实验室,Civet可能是最有意义的。如果你想在ECMAScript规范发布之前采用并实验新的功能,它是一个很好的工具。它是进入这个领域的前沿开发的一个切入点。