Blazor和Vue對比學習(基礎1.5):雙向繫結

functionMC發表於2022-05-10

這章我們來學習,現代前端框架中最精彩的一部分,雙向繫結。除了掌握原生HTML標籤的雙向繫結使用,我們還要在一個自定義的元件上,手擼實現雙向繫結。雙向繫結,是前兩章知識點的一個綜合運用(父傳子、子傳父),但因為又多了一層抽象,有一點小難度,需要一點時間和練習來熟悉套路,但必須要越過去。此後,基礎部分就是一馬平川了。此章我們分為以下幾個部分來一起學習:

  • 補充:HTML標籤和元件
  • HTML標籤的雙向繫結
  • 自定義元件的雙向繫結
  • 再次認識指令屬性/修飾符

 

一、補充:HTML標籤和元件

在學習雙向繫結前,我們需要進一步理解元件的本質,可以從下面兩個角度:

角度1、我們定義一個元件,相當於定義一個類。在模板中使用一個元件時,比如這樣<MyComponent></MyComponent>,相當於new一個例項物件,並通過標籤屬性進行資料賦值,實列化了,元件就具有了檢視、狀態、生命週期等特徵。

角度2、無論Vue,還是Blazor。元件的檢視模板,均由HTML和其它元件構成。其中元件成分還可以繼續拆,拆到底,都是HTML。HTML是元件的根零件,到它就不可以再拆了,你只能使用它,無法窺探它的內部結構。你可以認為,元件在檢視這個層面上,就是HTML的再組合。同時,元件還包括了資料和邏輯,通過繫結技術實現檢視與資料的互動,再通過虛擬DOM技術實現了差量更新,提升了互動的效能。

理解了HTML標籤和元件,將有助於我們學習後面的知識點。

 

 

二、HTML標籤的雙向繫結

我們之前使用{{}}或者@時,都只是實現了邏輯層>檢視層的單向繫結,即邏輯層的資料改變時,檢視層自動更新,這是不夠的。比如輸入框input,當我們在檢視層輸入值時,邏輯層的資料也應該響應新輸入的值,實現自動更新,實現檢視層>邏輯層的自動更新。

比如input(type為text),我們將value屬性與邏輯層繫結,實現邏輯層到檢視層的單向繫結。而檢視層到邏輯層,則需要藉助DOM事件的event物件,前面我們說過,DOM事件的回撥裡,會自動匯入e物件。我們在回撥裡,直接通過e物件獲得新輸入的value值,然後賦值給邏輯層資料。這個過程很簡單,因為不需要涉及跨元件通訊。

//Vue=====================================

//通過屬性繫結和事件回撥,實現雙向繫結,不需要在父子元件之間傳值
<template>
  <h1>邏輯層資料:{{inputValue}}</h1>
  <input  :value = "inputValue" @input="(e)=>{inputValue = e.target.value}">
</template>

<script setup>
import { ref } from 'vue'
const inputValue = ref('輸入框初始值')
</script>

//v-model可以認為是以上方式的語法糖
<input type="text" v-model="inputValue"/>

 

//Blazor===================================

//通過屬性繫結和事件回撥,實現雙向繫結,不需要在父子元件之間傳值。獲取event物件,需要指定型別,獲取value值也和Vue不太一樣,同時注意型別轉換
<h1>邏輯層資料:@inputValue</h1>
<input value = "@inputValue" @oninput = "(ChangeEventArgs e)=>{inputValue = Convert.ToString(e.Value);}" />

@code {
    private string inputValue = "輸入框初始值";
}

//@bind可以認為是以上方式的語法糖
//bind指令有三個屬性,culture、format和event,其中enent用來指定更新時機,預設為onchang失去焦點時發生,此例修改為oninput輸入時發生
<input @bind = "inputValue" @bind:event = "oninput" />

 

 

 三、自定義元件的雙向繫結

如前所述HTML標籤和元件的區別。在HTML標籤中,我們可以使用原生的屬性、事件及事件物件event,實現雙向繫結。而元件,是一個再組合的東西,並沒有原生的屬性、事件及事件物件,都需要我們自己來實現。參照HTML標籤的雙向繫結,我們來嘗試一下,思路和HTML標籤是一樣的,只是我們需要掰開元件,到內層去操作,所以需要進行資料的父傳子和子傳父。

下面的案例,我們在子元件裡定義一個HTML標籤:input文字框,實現子元件和父元件的邏輯資料實現雙向繫結。雙向繫結可以定義多個

//Vue=====================================
//1、父元件
<template>
 <div class="Parent">
      <h1>灰色框是父元件</h1>
      <h1>父元件邏輯資料值:{{parentValue}}</h1>
      <Child :inputValue = "parentValue" 
             @update:inputValue = "(newValue)=>{parentValue = newValue}">
      </Child>
</div>
</template>

<script setup>
import Child from './components/Child.vue'
import { ref } from 'vue'
const parentValue = ref('父元件初始值')
</script>


//2、子元件
<template>
    <div class="Child">
        <h1>紅色框是子元件</h1>
        <input :value = "inputValue" @input="childEmit"/>
    </div>
</template>

<script setup>
import {ref} from 'vue'
const props = defineProps(['inputValue'])
const emits = defineEmits(['update:inputValue'])
const childEmit = (e)=>{
    emits('update:inputValue',e.target.value)
}
</script>


//3、下面來分析一下這個過程:
//從案例中,我們可以看到,<Child>這個元件,實際上僅是起到搬運工的作用,①派出屬性,將父元件的資料狀態parentValue交給子元件內部的input消費;②拍出事件,將子元件內部的input新值,傳遞給父元件的資料狀態parentValue消費。
//除去Child這個外殼,實際上還是邏輯層的資料狀態,在和HTML標籤做互動,只是因為邏輯層資料和檢視層HTML標籤在不同的位置,所以需要<Child>這個橋樑!


//4、自定義元件的雙向繫結,可以使用v-model語法糖嗎?
//可以的,但是要遵守屬性和事件的命名約定,如下:
//屬性名:任意名稱。但如果使用【v-model="parentValue】的格式,就必須命名為modelValue
//事件名:update:屬性名。但如果使用【v-model="parentValue"】的格式,就必須命名為update:modelValue
//上面的案例,我們可以使用v-model來簡化一下,但只能簡化父元件哦,子元件保持不變【注意如果使用v-model的最簡形式,案例中子元件的屬性和事件名稱改一下】
<Child v-model = "parentValue"></Child>
<Child v-model:firstValue = "parentValue1" v-model:secondValue = "parentValue2"></Child>
//Blazor====================================

//1、父元件
<div class = "Parent">
    <h1>灰色框裡是父元件</h1>
    <h1>父元件邏輯資料值:@parentValue</h1>
    <Child InputValue="@parentValue" InputValueChanged="@((newValue)=>{parentValue = newValue;})"></Child>
</div>

@code {
    private string parentValue = "輸入框初始值";
}

//2、子元件
<div class="Child">
    <h1>紅色框裡是子元件</h1>
    <input value = "@InputValue" @oninput = "ChildEmit" />
</div>

@code {

    [Parameter]
    public EventCallback<string> InputValueChanged { get; set; }

    [Parameter]
    public string? InputValue{ get; set; }

    private async Task ChildEmit(ChangeEventArgs e)
    {
        await InputValueChanged.InvokeAsync(Convert.ToString(e.Value));
    }
}


//3、分析:雙向繫結的實現邏輯,和Vue基本是一樣的。通過屬性引數,將父元件的邏輯層資料parentValue交給子元件的input,通過EventCallback事件,將子元件input的新值,交給父元件的parentValue。本質上,還是邏輯層資料和HTML原生標籤的互動,自定義元件只是玩個了寂寞。


//4、可以使用@bind的語法糖嗎?肯定是可以的
//一樣要遵守屬性和事件的命名約定,如下:
//屬性名稱:任意名稱
//EventCallback事件名稱:屬性名稱Changed
//上面案例子元件不做改動,父元件可以簡寫為:
<Child @bind-InputValue="parentValue"></Child>

 

 

 

四、再次認識指令屬性/修飾符

雖然是再次認識,但仍舊不打算深入,對於Blazor的指令屬性,或Vue的事件修飾符,我認為即使已經開始進行生產應用的開發,都僅需要掌握用法即可,不需要深入內部的實現甚或自己來實現一個。所以,下面僅是一個使用方法的彙總

1、Vue的修飾符,常用的主要有v-on類修飾符和v-model類修改符

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(1)v-on類修飾符

①prevent:阻止預設行為。HTML標籤a的預設行為“開啟連結”,被阻止執行

<a href=”http://www.hiwork.xyz” @click.prevent=”show”>

 

②stop:阻止事件冒泡。如果沒有stop,點選div2時,div1也會被執行。

<div id = "div1"@click = "show1">
    <div id= "div2" @click.stop = "show2"></div>
</div>

 

③capture:事件從外層開始執行。事件正常從內層開始執行(即冒泡),show2>show1。在外層使用capture後,從外層開始執行,show1>show2

<div @click.capture = "show1">
    <div @click = "show2"></div>
</div>

 

④once:事件只觸發一次。無論點選按鈕多少次,show1都只在第一次點選時執行。

<button @click = "show1"></button>

 

⑤self:只有事件target是自己時才觸發事件。點選div2時,按冒泡順序,show1在show2之後執行,但因為div1使用了self,所以點選div2時,show1不會執行。

<div id="div1" @click.self = "show1" style="height: 100px;width: 100px;background-color: aquamarine;">
    <div id="div2" @click = "show2" style="height: 50px;width: 50px;background-color:blue;"></div>
</div>

 

⑥⑦passive/native:這兩個極少用,但文件吧

 

(2)v-model類修飾符,如<input v-model.number = "count" />

①number:將輸入值轉為數值

②trim:去除輸入值首尾空格

③lazy:輸入框失去焦點時再更新

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

2、Blazor的指令/事件屬性,即少又難用,不知道未來是否會改進。主要有DOM事件的屬性,Bind指令屬性。另外,指令屬性的使用也比較奇葩,需要重複申請,見案例

 

(1)DOM事件屬性

①preventDefault,阻止HTML標籤的預設行為,同Vue的prevent

<a href = "http://www.hiwork.xyz" @onclick = "Show1" @onclick:preventDefault></a>

 

②stopPropagation,組止冒泡,同Vue的stop

<div id = "div1" @onclick = "show1">
    <div id= "div2" @onclick = "show2" @onclick:stopPropagation></div>
</div>

 

(2)Bind指令屬性

①event,指定雙向繫結檢視更新邏輯的時機,是失去焦點時更新onchange(預設),還是實時更新oninput

<input @bind ="parentValue" @bind:event="oninput"/>

②format,指定雙向繫結的日期格式

<input @bind="StartDate" @bind:format="yyyy-MM-dd" />

③culture,指定語言,沒有用過,查文件吧

 

相關文章