Blazor和Vue對比學習(基礎1.2):模板語法和Razor語法

functionMC發表於2022-05-06

Vue使用模板語法,Blazor使用祖傳的Razor語法,從邏輯和方向上看,兩者極為相似,比如:

  • 都基於HTML
  • 都通過宣告式地將元件例項的狀態(資料/方法)繫結到呈現的DOM上
  • 都通過指令實現更加豐富的檢視/HTML與邏輯/JS和C#的互動應用
  • 底層機制都是通過虛擬DOM,實現差量更新
  • 工程組建方式都基於元件樹
  • 都具有單檔案元件特徵

但在具體實現和語法上,兩者有比較大的差異。給人的總體感覺就是,都很熟悉,但就是不太一樣。以下僅對語法基礎進行逐點比較,內容較多,目錄如下:

  1. 標籤內容繫結(單向)
  2. 標籤屬性繫結(單向)
  3. 控制結構(判斷/迴圈等)
  4. 指令體系概述
  5. 補充: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通過reactiveref,這兩個API來建立響應式資料,實現html檢視和js邏輯的資料繫結。實際應用中,有幾個點需要特別注意:

1reactive只能用來建立物件型別(如物件、陣列、MapSet),不能建立原始型別(如stringnumberboolean等)。而ref可以建立任何型別。

const a = reactive({name:'MC',age:18}) //正確
const b = reactive(18) //錯誤
const a = ref({name:'MC',age:18}) //正確
const b = ref(18) //正確

 

2reactive建立的響應式物件,預設是深層次的,所以裡面巢狀的資料都是響應式的。

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建立的物件,使用陣列的mapfilterreduce等返回新陣列的方法時,新陣列仍然可以保持響應性。除此之外,建立分離的組合式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麻煩點。

相關文章