自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue 3.4 重磅升級:defineModel 宏如何徹底改變前端狀態(tài)管理!

開發(fā) 前端
隨著 3.4 的發(fā)布,由于它使狀態(tài)管理變得更加簡潔和直觀,這種愿景現(xiàn)在感覺更加完整。在某種程度上,它有助于澄清狀態(tài)放置位置的復雜決策過程,使簡單而明顯的選擇成為正確的選擇。

理解狀態(tài)的位置和組件邊界仍然是現(xiàn)代前端開發(fā)中主要挑戰(zhàn)之一,也是團隊在應用規(guī)模增加時做出的最重要的決定之一,可能會加速開發(fā),也可能成為最大的摩擦源。

如果做得好,構建、組合、重構和測試前端組件會變得輕而易舉;如果做得不好,則會成為難以追蹤的幽靈錯誤的無盡源泉,使代碼庫變得脆弱。

Vue 3.4 版本中從實驗狀態(tài)發(fā)布的 defineModel 宏,可能是關于組件間復雜狀態(tài)交互的實現(xiàn)方式最具變革性的特性之一。

描述看似很無害:

defineModel 是一個新的 <script setup> 宏,旨在簡化支持 v-model 的組件的實現(xiàn)

表面上看,這個宏的實用性似乎微不足道,但它對團隊如何處理狀態(tài)和管理組件邊界有深遠影響。我們看看 defineModel 是做什么的,為什么它的添加在 Vue 3.4 中感覺像是一種范式轉變——盡管它只是一個簡單的宏。

狀態(tài)的概念模型

通常,現(xiàn)代前端應用程序有三種狀態(tài)范圍(不包括全局窗口級別的狀態(tài))。

圖片圖片

  1. 全局共享狀態(tài)。這是整個層次結構中不同組件可訪問的狀態(tài),例如登錄用戶的賬戶信息和路由間共享的信息。
  2. 分層組件狀態(tài)。這是在層次結構的子樹中不同組件可訪問的狀態(tài),例如列表-詳情編輯視圖。
  3. 單個組件級別的狀態(tài)。這是僅在層次結構中的單個組件中可訪問的狀態(tài),狀態(tài)交互不需要在樹中上下傳遞。

在全局級別,有許多庫和解決方案可以解決這個問題。例如,React 的 Zustand、Jotai、Recoil、Redux 等,Vue 的 Pinia 可以將狀態(tài)從組件樹中提取出來放入全局范圍,以跨越樹。它旨在保存真正的全局狀態(tài),如淺色/深色模式或租戶 ID。

第二層狀態(tài)是團隊遇到“屬性傳遞”摩擦的地方——無論是在 React、Vue 還是其他庫或框架中。部分原因是管理狀態(tài)在組件之間的上下移動是繁重的。

在這種情況下,團隊的自然決定是將狀態(tài)移動到全局存儲中,或者進入第三個組件狀態(tài)范圍,僅僅是為了避免這種摩擦,而不斷堆積到一個巨大的組件中——這會產生另一種痛苦。

如果能在保持 Vue 的雙向綁定的同時,輕松地將狀態(tài)分離開來,而不需要屬性傳遞的摩擦,那該多好。這正是 defineModel 的作用所在,它大大減少了在樹中移動狀態(tài)的摩擦,同時保持 Vue 的雙向綁定。

defineModel 是什么?

重要的是首先了解它是什么以及它的功能。對于那些不熟悉 Vue 的人來說,組件間上下移動狀態(tài)的慣用模式一直是使用 props 和 emits。

在 defineModel 之前 - props 和 emits

例如,考慮這個父子組件:

圖片圖片

外部組件定義了 ref 并將其作為 prop 傳遞給子組件。更新通過子組件向父組件的 emit 事件來完成。

為了獲得雙向綁定,我們需要內部的 NameInput.vue 組件如下:

<!-- NameInput.vue -->
<template>
  <LabeledContainer label="NameInput.vue">
    <input v-model="name"/>
  </LabeledContainer>
</template>

<script setup lang="ts">
const props = defineProps<{
  modelValue: string
}>()

const emits = defineEmits<{
  'update:modelValue': [string]
}>()

const name = computed({
  get() {
    return props.modelValue
  },
  set(val) {
    emits('update:modelValue', val)
  }
})
</script>

外部的 Example1.vue 組件如下:

<!-- Example1.vue -->
<template>
  <LabeledContainer label="Example1.vue">
    <h1>Example 1</h1>
    <p>Hello, {{ name.length === 0 ? "(enter your name below)" : name }}</p>
    <NameInput v-model="name"/>
  </LabeledContainer>
</template>

<script setup lang="ts">
const name = ref('')
</script>

現(xiàn)在,當我們在文本框中輸入值時,它會自動更新 prop 的值:

圖片圖片

我們的非常簡單的組件具有父子組件之間的雙向綁定。

可以很容易地看到,對于如此簡單的事情,這種樣板代碼會變得多么繁瑣!

在 defineModel 之后 ?

隨著 Vue 3.4 中 defineModel 的發(fā)布,我們看看它如何簡化 NameInput.vue:

<!-- NameInput.vue -->
<template>
  <LabeledContainer label="NameInput.vue">
    <input v-model="name"/>
  </LabeledContainer>
</template>

<script setup lang="ts">
const name = defineModel<string>({ required: true })
</script>

父組件保持不變,但大量的樣板代碼被刪除了!這個小小的宏完全改變了管理狀態(tài)的體驗。

一個實際的例子

表面上看,這似乎是一個微不足道的變化。當然,獲得了一些便利,但這對開發(fā)人員管理狀態(tài)有多大影響?僅僅是一個簡單的宏,聲稱會有這么大的影響豈不是荒謬?

事實上,開發(fā)人員往往會選擇阻力最小的路徑,如果阻力最小的路徑是壞習慣,那么,開發(fā)人員將創(chuàng)建一個充滿許多壞習慣的代碼庫——即“技術債務”。如果你見過 1000 多行的 React 或 Vue 組件(我們中誰沒見過?),那么很可能的原因是將狀態(tài)以可管理的方式分散出來的摩擦太大;隨著組件的有機增長,將狀態(tài)保持在一個巨大的組件中比分解出新組件更容易。

defineModel 的實現(xiàn)是,它創(chuàng)建了一條最小阻力路徑,同時有助于改善團隊對狀態(tài)的思考方式。突然之間,管理分層組件狀態(tài)變得微不足道,并消除了將狀態(tài)移入全局范圍或在大型組件中進行松散操作的誘惑(通常是 1000 多行組件的來源)。

使用 defineModel 簡化分層狀態(tài)

考慮以下簡單的聯(lián)系人管理應用程序:

圖片圖片

注意這個示例中的層次結構。當用戶從 Listing.vue 中選擇聯(lián)系人時,應用程序應在 Details.vue 中顯示詳細信息。當用戶編輯詳細信息并在 Details.vue 中保存更改時,應用程序應更新 Listing.vue 中的條目。

如果我們想在 Listing.vue 和 Details.vue 之間共享狀態(tài),它必須是全局狀態(tài)或從公共父級 Example3.vue 開始的分層狀態(tài)——否則,很容易看到將所有內容放入一個巨大的組件中的誘惑!

在這種情況下,這就是我們的分層狀態(tài)的樣子:

圖片圖片

狀態(tài)通過 prop 從列表組件傳遞到聯(lián)系人組件。

我們從外到內檢查代碼。

這是我們的父 Example3.vue 組件:

<template>
  <LabeledContainer label="Example3.vue">
    <h1>Example 3</h1>

    <p v-if="!!selectedContact">
      Selected: {{ selectedContact.name }} ({{ selectedContact.handle  }})
    </p>

    <div class="parent">
      <Listing
        v-model="contacts"
        v-model:selected="selectedContact"/>

      <Details v-model="selectedContact"/>
    </div>
  </LabeledContainer>
</template>

<script setup lang="ts">
const selectedContact = ref<Contact>()

const contacts = ref<Contact[]>([{
  name: 'Charles',
  handle: '@chrlschn'
}])
</script>

這是我們的狀態(tài)所在根,并通過綁定將其傳遞給 Listing 和 Details 組件:

<!-- Snippet from Example3.vue-->
<Listing
  v-model="contacts"
  v-model:selected="selectedContact"/>

<Details v-model="selectedContact"/>

我們先看看 Details.vue :

<!-- Details.vue, the right side form inputs -->
<template>
  <LabeledContainer label="Details.vue">
    <div v-if="!!selected">
      <label>
        Name
        <input v-model="name"/>
      </label>

      <label>
        Handle
        <input v-model="handle"/>
      </label>

      <div>
        <button @click="handleCancel">Done</button>
        <button @click="handleDone">Save</button>
      </div>
    </div>
    <p v-else>
      Select a contact
    </p>
  </LabeledContainer>
</template>

<script setup lang="ts">
const selected = defineModel<Contact|undefined>({
  required: true
})

const name = ref('')

const handle = ref('')

watch (selected, (contact) => {
  if (!contact) {
    return
  }

  name.value = contact.name,
  handle.value = contact.handle
})

function handleCancel() {
  selected.value = undefined
}

function handleDone() {
  if (!selected.value) {
    return
  }

  selected.value.name = name.value;
  selected.value.handle = handle.value;
}
</script>

這個組件的目的是擁有一組狀態(tài)副本,當選中的聯(lián)系人更改時,組件將值復制到本地狀態(tài),以便在不影響原始狀態(tài)的情況下(直到用戶保存),更改名稱和 handle。這也允許用戶取消任何編輯。

對于更大的屬性集,可以考慮創(chuàng)建對象的完整響應式副本并直接綁定到它。

在左側,Listing.vue 組件包含聯(lián)系人列表,并有添加新聯(lián)系人的選項。

<!-- Listing.vue -->
<template>
  <LabeledContainer label="Listing.vue">
    <div class="container">
      <ContactItem
        v-for="contact in contacts"
        :cnotallow="contact"
        :selected="selected == contact"
        @click="selected = contact">
      </ContactItem>
    </div>

    <div>
      <button @click="handleAddContact"> Add contact </button>
    </div>
  </LabeledContainer>
</template>

<script setup lang="ts">
const contacts = defineModel<Contact[]>({
  required: true
})

const selected = defineModel<Contact|undefined>('selected', {
  required: true
})

function handleAddContact() {
  contacts.value.push({
    name: 'Name',
    handle: 'Handle'
  })
}
</script>

然后在 ContactItem.vue 中,Listing.vue 通過普通的 props 傳遞顯示值,因為這里不需要變更(也不需要雙向綁定):

<template>
  <LabeledContainer
    label="Contact.vue"
    class="contact"
    :class="{
      'selected': !!selected
    }">
    <p class="name">{{ contact.name }}</p>
    <p class="handle">{{ contact.handle }}</p>
  </LabeledContainer>
</template>

<script setup lang="ts">
defineProps<{
  contact: Contact,
  selected?: boolean
}>()
</script>

我們看看這整個事情是如何結合在一起的:

圖片圖片

我們的組件在示例組件樹的層次結構中共享狀態(tài)。

如果沒有 defineModel 來幫助簡化這種交互,很容易看到本能是采取捷徑或將狀態(tài)移入全局狀態(tài),因為編寫各種 emits 和 computed 會產生相當大的摩擦,即使在這個小示例中也是如此!

正如 Billy Mays 可能會說:“但等一下!還有更多!”讓我們看看如何通過使用可組合組件進一步簡化代碼。

使用 defineModel 和可組合組件

利用可組合組件可以將其提升到一個新的水平,并通過將狀態(tài)從組件中提取出來進一步簡化我們的代碼。當組件變得更大時,這特別有用。

在 Vue 中,這很容易實現(xiàn),并使重構和重新組織復雜性變得輕而易舉。

我們只需將我們的狀態(tài)和函數(shù)向上提取到另一個函數(shù)中:

// useContacts composable
export function useContacts() {
  const selectedContact = ref<Contact>()

  const contacts = ref<Contact[]>([{
    name: 'Charles',
    handle: '@chrlschn'
  }])

  function addContact() {
    contacts.value.push({
      name: 'Name',
      handle: 'Handle'
    })
  }

  return {
    selectedContact,
    contacts,
    addContact
  }
}

很容易看出,如果我們想從 Details.vue 中移動更多的邏輯和狀態(tài),將 name 和 handle refs 以及 handleCancel() 和 handleDone() 函數(shù)移動到另一個可組合組件中并共享它們是非常低摩擦的:

// useDetailsEditor.ts

// ?? 注意我們在這里接收響應式 selectedContact 
export function useDetailsEditor(
  selectedContact: Ref<Contact|undefined>
) {
  const name = ref('') 

  const handle = ref('') 

  // ?? 我們在 selectedContact 上添加一個監(jiān)聽器
  // 以便更新封裝的狀態(tài)。
  watch (selectedContact, (contact) => {
    if (!contact) {
      return
    }

    name.value = contact.name,
    handle.value = contact.handle
  })

  function cancel() {
    selectedContact.value = undefined
  }

  function done() {
    if (!selectedContact.value) {
      return
    }

    selectedContact.value.name = name.value;
    selectedContact.value.handle = handle.value;
  }

  return {
    name,
    handle,
    cancel,
    done
  }
}

然后我們更新 Details.vue:

<template>
  <LabeledContainer label="Details.vue">
    <div v-if="!!selected">
      <div>
        <label>
          Name
          <input v-model="name"/>
        </label>
      </div>

      <div>
        <label>
          Handle
          <input v-model="handle"/>
        </label>
      </div>

      <div>
        <button @click="cancel">Done</button>
        <button @click="done">Save</button>
      </div>
    </div>
    <p v-else>
      Select a contact
    </p>
  </LabeledContainer>
</template>

<script setup lang="ts">
const selected = defineModel<Contact|undefined>({
  required: true
})

const {
  name,
  handle,
  cancel,
  done
} = useDetailsEditor(selected)
// ?? 我們將選中的聯(lián)系人傳遞給可組合組件以便監(jiān)聽
</script>

使用 Vue 3 可組合組件和 Vue 3.4 的 defineModel 宏,這種方式可以很好地將相關狀態(tài)和邏輯干凈地分離和封裝。我希望清楚的是,通過簡單地復制狀態(tài)、函數(shù)、監(jiān)聽器和計算值并將它們粘貼到可組合組件中,可以輕松地重構代碼。

這種模式可以使即使是大型子組件樹也變得易于管理、重構和測試。

結論

Vue 3.4 中引入的 defineModel 實際上是一個深遠的變化,它將幫助團隊遵循最佳實踐并構建更好、更易管理的組件。通過消除構建分層狀態(tài)時的許多摩擦,它使得團隊不太可能立即求助于全局狀態(tài)或倒退到松散的做法。

通過將 defineModel 與 Vue 可組合組件結合使用,團隊可以通過組織和封裝相關狀態(tài)和邏輯來創(chuàng)建更干凈的組件,這些組件易于閱讀和理解。

當尤雨溪首次提出 Vue 3 的 Composition API 時,社區(qū)中有很多反對聲音,認為它應該保留 Options API 的簡單性和易用性。事后看來,很明顯,尤雨溪幫助 Vue 更好地擴展以應對構建大型項目的團隊的路徑是正確的選擇。

隨著 3.4 的發(fā)布,由于它使狀態(tài)管理變得更加簡潔和直觀,這種愿景現(xiàn)在感覺更加完整。在某種程度上,它有助于澄清狀態(tài)放置位置的復雜決策過程,使簡單而明顯的選擇成為正確的選擇。defineModel 就像是一顆投向海洋的小石子,其漣漪可能有一天會成為洶涌的波浪!

責任編輯:武曉燕 來源: 大遷世界
相關推薦

2023-10-17 13:50:04

RPAAI

2025-03-07 07:00:00

AI人工智能

2023-04-17 16:02:00

2023-04-17 16:13:16

2021-06-24 14:20:31

物聯(lián)網(wǎng)智能建筑IoT

2020-05-25 20:48:06

物聯(lián)網(wǎng)供應鏈技術

2022-10-26 11:06:16

機器學習農業(yè)

2013-04-28 10:31:18

物聯(lián)網(wǎng)改變搜索

2023-03-14 14:43:40

邊緣計算物聯(lián)網(wǎng)

2023-10-10 13:16:16

云優(yōu)先數(shù)字孿生

2022-07-11 18:49:19

虛擬Web3元宇宙

2023-07-20 10:44:46

2024-03-05 14:43:02

2023-05-04 09:48:54

機器學習人工智能

2020-06-02 16:33:11

物聯(lián)網(wǎng)IoT施管理服務模式

2023-12-29 10:49:49

2022-06-27 14:59:11

酒店業(yè)物聯(lián)網(wǎng)

2024-02-20 10:27:49

2024-01-24 11:24:39

VR虛擬現(xiàn)實電影制作

2024-03-22 14:22:08

rSIM移動網(wǎng)絡物聯(lián)網(wǎng)
點贊
收藏

51CTO技術棧公眾號