Vue使用模板語法,Blazor使用祖傳的Razor語法,從邏輯和方向上看,兩者極為相似,比如:
- 都基於HTML
- 都通過宣告式地將元件例項的狀態(資料/方法)繫結到呈現的DOM上
- 都通過指令實現更加豐富的檢視/HTML與邏輯/JS和C#的互動應用
- 底層機制都是通過虛擬DOM,實現差量更新
- 工程組建方式都基於元件樹
- 都具有單檔案元件特徵
但在具體實現和語法上,兩者有比較大的差異。給人的總體感覺就是,都很熟悉,但就是不太一樣。以下僅對語法基礎進行逐點比較,內容較多,目錄如下:
- 標籤內容繫結(單向)
- 標籤屬性繫結(單向)
- 控制結構(判斷/迴圈等)
- 指令體系概述
- 補充:Vue的響應式約束
1、標籤內容繫結(單向)
這是最基本的資料繫結形式,可以實現HTML標籤體內容和邏輯程式碼的動態繫結。【單向】更新邏輯程式碼時,標籤內容會自動更新。Vue和Blazor的標籤體內容繫結語法如下所示:
- Vue使用雙大括號,{{ /*這裡是JS表示式*/ }},如{{ number+1 }}
- Blazor使用@符,@/*這裡是C#表示式*/,如@(number+1),這裡需要使用()顯示標註表示式
- 注:表示式可以使用常量、變數、方法呼叫、API呼叫、運算子的任意合法組合
(1)最簡單的變數
//Vue===================================== <template> <span>Message: {{ msg }}</span> </template> <script setup> import { ref } form ‘vue’; const msg = ref(‘你好,我是functonMC’); </script> //Blazor==================================== <span>Message: @msg</span> @code { private string msg = “你好,我是functonMC”; }
(2)運算表示式
//Vue===================================== <template> <span>結果是:{{ number + 1 }}</span> </template> <script setup> import { ref } from "vue"; const number = ref(10); </script> //Blazor==================================== <span>@(number+1)</span> @code { private int number = 10; }
(3)呼叫API的表示式
//Vue===================================== <template> <span>{{ message.split("").reverse().join("") }}</span> </template> <script setup> import { ref } from "vue"; const message = ref("Hello,functionMC"); </script> //Blazor==================================== <span>@(string.Join("",message.ToCharArray().Reverse().ToArray()))</span> @code { private string message = "Hello,functionMC"; }
(4)三元表示式
//Vue===================================== <template> <span>{{ ok ? "好的" : "不行" }}</span> </template> <script setup> import { ref } from "vue"; const ok = ref(true); </script> //Blazor==================================== <span>@(ok?"好的":"不行")</span> @code { private string message = "Hello,functionMC"; }
(5)方法呼叫
//Vue===================================== <template> <span>{{ addResult(1, 2) }}</span> </template> <script setup> function addResult(a, b) { return a + b; } </script> //Blazor=================================== <span>@AddResult(1,2)</span> @code { private int AddResult(int a,int b) { return a + b; } }
2、標籤屬性繫結(單向)
和標籤體內容繫結一樣,標籤屬性也可以繫結邏輯程式碼,可以使用常量、變數、方法呼叫、API呼叫、運算子的任意合法組合的表示式。Vue需要使用v-bind指令(可以簡寫為冒號),Blazor仍然使用@符,表示式的使用“標籤內容”繫結基本一致。兩者的語法如下所示:
- Vue:<span v-bind:title="newsTitle"></span>,簡寫為<span :title="newsTitle"></span>
- Blazor:<span title="@newsTitle"></span>,可以省略引號為<span title=@newsTitle></span>
可以注意到Vue的冒號和Blazor@符所在位置的區別。所以在Blazor中,你是很容易區別指令、標籤屬性繫結和標籤內容繫結三者的區別的,比如style="@TitleStyle"是標籤屬性繫結,@key="people.Id"是指令key。而在Vue中,這並不好區別,如:style="titleStyle"是標籤屬性繫結,:key="people.id"是指令呢?還是屬性繫結呢?雖然這種區分,然並卵,但你能感覺到Blazor有著C#的嚴謹支撐,整個元件體系,也是基於C#的語法體系,Razor和C#之間是很容易打通的,原始碼比較容易看懂。而Vue在靈活的JS之上又做了一層抽象,總讓人感到失去了語言的一慣性,現在的組合式API會好點,Vue2時代的選項式API,這種感覺更甚,甚至有人說,學了Vue後,都快忘了JS了。
言歸正傳,標籤屬性繫結的應用,主要涉及到樣式繫結、表單元件的雙向繫結、父子元件的資料傳遞、以及指令體系,每個在後續都會另起章節詳述,所以暫且略過!
3、控制結構(條件/迴圈)
這裡的控制結構,主要指DOM結構的條件渲染和迴圈渲染。Vue需要使用指令來完成,用於條件的v-if/v-else-if/v-else/v-show,用於迴圈的v-for;而Blazor則靈活很多,因為在Blazor中,html和C#可以混寫,所以你就感覺是在寫C#一樣,這和react倒是很像。
(1)條件渲染
//Vue中使用v-if=============================== //根據繫結的type值,只渲染判斷為true的DOM節點,其它全部幹掉。如果type值在執行時頻繁改變,開銷將比較大,這種情況推薦使用v-show <template> <div v-if="type === 'A'">A</div> <div v-else-if="type === 'B'">B</div> <div v-else-if="type === 'C'">C</div> <div v-else>Not A/B/C</div> </template> <script setup> import { ref } from "vue"; const type = ref("B"); </script> //Vue中使用v-show============================= //使用v-show時,所有節點都有渲染,只是改變的style的display屬性,所以執行時頻繁改變type值時,開銷會少很多。 <template> <div v-show="type === 'A'">A</div> <div v-show="type === 'B'">B</div> <div v-show="type === 'C'">C</div> <div v-show="!(type==='A'||type==='B'||type==='C')">Not A/B/C</div> </template> <script setup> import { ref } from "vue"; const type = ref("B"); </script> //Blazor中使用if=============================== @if (type == "A") { <div>A</div> } else if (type == "B") { <div>B</div> } else if (type == "C") { <div>C</div> } else { <div>not A/B/C</div> } @code { private string type = "g"; } //Blazor中使用switch============================ @switch (type) { case "A": <div>A</div> break; case "B": <div>B</div> break; case "C": <div>C</div> break; default: <div>not A/B/C</div> break; } @code { private string type = "g"; } //Blazor中實現類似Vue的v-show===================== <div style="display:@((type=="A")?"inline":"none")">A</div> <div style="display:@((type=="B")?"inline":"none")">B</div> <div style="display:@((type=="C")?"inline":"none")">C</div> <div style="display:@(!(type=="A"||type=="B"||type=="C")?"inline":"none")">not A/B/C</div> @code { private string type = "A"; }
(2)迴圈渲染
//Vue使用v-for指令============================= //可以迴圈渲染陣列和類陣列,類陣列包括了物件、字串、整數等 <template> //迴圈物件陣列,同時拿到索引。也可以v-for=“item in items1" <li v-for="(item, index) in items1" :key="item.id"> {{ index + 1 }}-{{ item.name }} </li> //迴圈物件,按順序拿到value,key和index <li v-for="(value, key, index) in items2" :key="key"> {{ key }}-{{ value }}-{{ index }} </li> //迴圈一個整數 <li v-for="n in 10" :key="n"> {{ n }} </li> //迴圈一個字串 <li v-for="n in 'hello,functionMC'" :key="n"> {{ n }} </li> </template> <script setup> import { ref } from "vue"; const items1 = ref([ { id: 1, name: "ZhangSan", age: 18 }, { id: 2, name: "LiSi", age: 18 }, { id: 3, name: "WangWu", age: 18 }, ]); const items2 = ref({ type: "上衣", number: "KY2022001", price: 200, }); </script> //Blazor中可以使用所有C#的控制語句=================== //使用foreach迴圈 @foreach (var item in peoples) { <li> @($"{item.Id}-{item.Name}-{item.Age}") </li> } //使用for迴圈 @for (var i = 0; i < peoples.Count; i++) { <li> @peoples[i].Name </li> } //使用while迴圈。不是閒得蛋蛋疼,應該不會用它 @{ var j = 0; } @while (j < peoples.Count) { <li> @peoples[j].Name </li> j++; } //迴圈整數 @for (var i = 0; i < 10; i++) { <li>@i</li> } //迴圈字串 @foreach (var item in "Hello,functionMC") { <li>@item</li> } //是否可以像Vue一樣迴圈物件?p1物件身上並沒有迭代器,可以嘗試定義一個迭代器來實現,或者通過遍歷Json來實現。太費腦,先留著吧 @code { private List<People> peoples = new List<People> { new People{Id=1,Name="Zhangsan",Age=18}, new People{Id=1,Name="LiSi",Age=19}, new People{Id=1,Name="WangWu",Age=20} }; public class People { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } }
4、指令體系概覽
Vue和Blazor都有指令,指令本質上是內建巨集(即一組命令),從這個角度理解,Vue在邏輯程式碼中使用的defineProps、defineEmits、defineExpose,算不算指令?對比Vue,Blazor的指令的劃分、語法格式和使用,更加規範,也更加廣泛。但目前為止,Blazor還不能夠自定義指令,而Vue可以(單獨章節來說)。下面對兩個框架的指令,進行概述,後續章節再和其它知識點做更深入的總結
(1)Blazor的指令:可以劃分為三類,Razor檔案/元件級別指令、元件/標籤級別指令、DOM事件指令
//1、Razor檔案/元件級別的指令。Razor檔案/元件,本質上是一個類,這些指令主要作用於類。除了@code外,其它指令所在位置都在檔案頭。 @code //c#程式碼塊,可以定義多個 @page //路徑,程式碼層面是作用於類的特性 @layout //母板,程式碼層面是作用於類的特性 @inject //依賴注入 @implements //實現介面 @inherits //繼承基類 @attribute //標註特性 @typeparam //類的泛型 @namespace //所屬名稱空間 //2、元件或HTML標籤級別的指令。這些指令都定義在標籤的屬性名位置,以@符開頭,如<span @ref="title"></span> @ref //引用元件或HTML標籤 @key //作為元件或HTML標籤的唯一標識,主要在迴圈渲染時使用 @attributes //將字典擴充套件為元件或HTML標籤屬性 @bind //實現雙向繫結 //3、HTML的DOM事件指令。這部分比較多,主要分為焦點、滑鼠、拖動、鍵盤、輸入、剪下板、觸控、指標、多媒體、進度、其它。需要注意幾個點: //①如果我們把指令視為類,那麼就還可以通過指令屬性來定義指令的特殊形為。Blazor中稱為指令屬性(事件指令特有),Vue中叫事件修飾符。在Blazor中,使用麻煩點,需要重複一次指令。 <div @onclick = "SomeFunction"> <div @onclick = "AddCount" @onclick:stopPropagation></div> </div> //②事件回撥可以預設獲得DOM事件引數,但需要注意,不同事件類別,事件引數型別不一樣,比如滑鼠事件為MouseEventArgs,輸入事件為ChangeEventArgs等。如果要傳入事件引數以外的引數,需要使用以下形式: <button @onclick = "@((e)=>AddCount(e,1,2))"></button> @code{ private int AddCount(MouseEventArgs e,int a,int b){return a+b;} }
(2)Vue的指令特指v-開頭的那十幾個
//1、條件迴圈渲染 //①迴圈渲染 <li v-for="item in items">{{item.id}}</li> //②條件渲染 <div v-if="Math.random() > 0.5">大於0.5</div> <div v-else-if="Math.random() < 0.5">小於0.5</div> <div v-else>Now you don't</div> //③條件渲染通過更新display屬性 <div v-show="true"></div> //2、資料事件繫結 //①屬性繫結,簡寫用冒號 <span v-bind:title="title"></span> <a :href="href"></a> //②事件繫結,簡寫用@符 <button v-on:click = "addCount"></button> <button @click = "addCount"></button> <button @click.stop="doThis"></button> //③雙向繫結,表單標籤 <input v-model="count" /> <input v-model.number="count" /> <input v-model.trim="name" /> <input v-model.lazy="name" /> //3、控制元件渲染 //①只在第一次載入時渲染 <div v-once> {{ message }}</div> //②不編譯,包括子元件,保持原始輸出(此例中輸出{{ message }}) <div v-pre> {{ message }}</div> //③直至編譯完才出現 <div v-cloak> {{ message }}</div> <style> [v-cloak] { display:none !important; </style> //4、檔案html繫結 ①可以等價於{{}} <div v-text = "text"></div> ... const text = ref('hello') ②繫結html原始標籤,有風險,甚用 <div v-text = "html"></div> ... const html = ref('<h1>aaa</h1>')
5、補充:Vue的響應式約束
Vue通過reactive和ref,這兩個API來建立響應式資料,實現html檢視和js邏輯的資料繫結。實際應用中,有幾個點需要特別注意:
(1)reactive只能用來建立物件型別(如物件、陣列、Map、Set),不能建立原始型別(如string、number、boolean等)。而ref可以建立任何型別。
const a = reactive({name:'MC',age:18}) //正確 const b = reactive(18) //錯誤 const a = ref({name:'MC',age:18}) //正確 const b = ref(18) //正確
(2)reactive建立的響應式物件,預設是深層次的,所以裡面巢狀的資料都是響應式的。
const a = reactive({}) a.people = {name:'MC',age:18} //增加的people屬性也是響應式
(3)當使用ref時,值儲存在ref物件的value屬性上。如果是在邏輯程式碼裡讀取或修改,需要通過訪問value屬性,如b.value+=2;如果在模板中讀取或修改,會自動解包,不需要.value
//邏輯程式碼中,需要通過.value來訪問。模板中,自動解包,不需要.value <script setup> import {ref} from 'vue' const a = ref({name:”MC”,age:18}) a.value.name </script> <template> <h1>{{a.name}}</h1> </template>
(4)當使用ref建立物件型別時,會呼叫reactive來建立value屬性,類似於這種感覺ref(reactive(value)),所以替換value值時,新值仍然是響應式的,而reactive如果替換新值,則會失去響應性。所以在實際應用中,建立物件陣列型別時,需要使用ref,因為ref建立的物件,使用陣列的map、filter、reduce等返回新陣列的方法時,新陣列仍然可以保持響應性。除此之外,建立分離的組合式API時,暴露出來的資料,也應該使用ref,這樣在引用這個API時,解構出來的資料仍然具有響應性。
//這樣是行不通的 const a = reactive({name:”MC”,age:18}) a = reactive{name:”Fun”,age:16} //ref才可以實現響應式替換 const b = ref({name:”MC”,age:18}) b.value = {name:”Fun”,age:16} //ref實現響應式解構 const obj1 = {foo: ref(1),bar: ref(2)} const {foo,bar} = obj1 //響應式解構 //使用ref,b還是響應式 const b = ref([ {name:”MC1”,age:18}, {name:”MC2”,age:19}, {name:”MC3”,age:20}]) b.value = b.value.filter((e)=>{return e.age >18}) //換成reactive,b失去了響應式 const b = reactive([ {name:”MC1”,age:18}, {name:”MC2”,age:19}, {name:”MC3”,age:20}]) b = b.filter((e)=>{return e.age >18})
總結:官方文件有一句話“為了解決reactive帶來的限制,Vue 也提供了一個ref方法來允許我們建立可以使用任何值型別的響應式ref”。建立響應式資料時,我推薦儘量使用ref,雖然.value麻煩點。