什麼是Civet:一個更好的TypeScript?

什麼是Civet:一個更好的TypeScript?

通過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中進行迭代

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<For each={props.items}>
{(item) => {
return (
<li class="item" style={props.style}>
<Item item={item} />
</li>
);
}}
</For>
<For each={props.items}> {(item) => { return ( <li class="item" style={props.style}> <Item item={item} /> </li> ); }} </For>
<For each={props.items}>
{(item) => {
return (
<li class="item" style={props.style}>
<Item item={item} />
</li>
);
}}
</For>

對我來說,這似乎是不必要的笨拙。一旦你習慣了它,就可以忍受了,而且還有其他的方法來進行迭代(儘管用JavaScript寫標記也同樣笨拙)。但是,當你需要做的只是對一個集合進行迭代時,如果能簡單到只需即時輸入就可以了,那就太好了。

清單2顯示了使用Civet編寫的清單1中的迴圈。

清單2. 在Civet中進行迭代

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<For each=props.items>
(item) =>
<li .item {props.style}><Item {item}>
<For each=props.items> (item) => <li .item {props.style}><Item {item}>
<For each=props.items>
(item) =>
<li .item {props.style}><Item {item}>

清單2中的程式碼更容易記憶,也更容易打出來,不用查。

Civet管道操作符

Civet在TypeScript管道操作符成為正式檔案之前,給了你一個建議。這個功能的基本想法是允許在沒有巢狀或流暢的方法鏈的情況下進行組合操作。就像CoffeeScript曾經為JavaScript所做的那樣,Civet讓你在它正式進入TypeScript之前就能使用這個功能。

使用管道操作符 (|>)使得以一種易於閱讀的方式編寫連鎖操作成為可能。

比方說,你已經定義了幾個操作(例如,foobar, 和 baz),現在你想把它們結合起來使用來修改一個變數。在直接的JavaScript中,你可能會出現巢狀的函式呼叫,如 foo(bar(baz(myVar))) 或可能是 baz(myNum).bar().foo()。兩者都很笨重,而且隨著事情變得越來越複雜,它們變得越來越難解讀。你可以用管道操作符在Civet中執行同樣的邏輯,如清單3所示。

清單3. 使用Civet管道操作符

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let foo = function(){}
let bar = function(){}
let baz = function(x){}
let myVar = “some value”;
myVar |> baz |> bar |> foo;
let foo = function(){ … } let bar = function(){ … } let baz = function(x){ … } let myVar = “some value”; myVar |> baz |> bar |> foo;
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中用模式進行切換

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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”
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”
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. 在陣列上迴圈操作

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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 }));
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 }));
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. 單引數函式速記

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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" ]
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" ]
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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));
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));
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規範釋出之前採用並實驗新的功能,它是一個很好的工具。它是進入這個領域的前沿開發的一個切入點。

評論留言