VUE 3.0 學習探索入門系列 - Vue3.x 令人期待的新特性(7)

村口蹲一郎發表於2020-04-04

在前面的文章中我們也聊了許多 Vue3.x 相比 Vue2.x 有哪些變化,也介紹了一些它的特點,今天就重點介紹下它新增的這些特性。

本文主要參考:vueschool.io/articles/vu…

總覽

  1. Compostion API 合成API
  2. 取消 Vue 全域性變數
  3. 自定義指令 Directives API調整
  4. Component 元件支援 v-model 指令
  5. Fragments Template 支援有多個根節點
  6. Suspense Template Fallback 元件
  7. Teleport Template Dom佔位傳遞元件

1 Compostion API 合成API

上一篇文章已經重點介紹過了。

檢視:juejin.im/post/5e8010…

2 取消 Vue 全域性變數

Vue2.x 中的程式碼片段:

import Vue from 'vue'
import App from './App.vue'

Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)

new Vue({
  render: h => h(App)
}).$mount('#app')
複製程式碼

Vue3.x 中取消了全域性變數 Vue,改為例項函式 createApp() 建立例項物件。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.ignoredElements = [/^app-/]
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

app.mount('#app')
複製程式碼

RFC檢視網友討論:github.com/vuejs/rfcs/…

這個變化很大,將會給我們從 Vue2.x 升級到 Vue3.x 帶來不小的工作量。

為什麼這麼改變?其實也好理解,Vue3.x 基於函數語言程式設計,所以:一切皆函式。 為了保證每個函式都有自己的小 圈子 能獨立執行,所以從源頭上就開始 開刀

3 自定義指令 Directives API調整

Vue2.x 中自定義一個指令:

const MyDirective = {
  bind(el, binding, vnode, prevVnode) {},
  inserted() {},
  update() {},
  componentUpdated() {},
  unbind() {}
}
複製程式碼

Vue3.x 中變成了:

const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // new
  unmounted() {}
}
複製程式碼

這可以算上是一個 breaking change 了,主要是在 Vue3.x 中生命週期函式的變化導致的。

檢視上一篇文章,瞭解 Vue3.x 的生命週期有哪些變化:juejin.im/post/5e8010…

RFC看這裡:github.com/vuejs/rfcs/…

4 Component 元件支援 v-model 指令

Vue2.x 中我們會把 v-model 用在一些表單元素上,用於資料的雙向繫結。

<input v-model="property />
複製程式碼

但是,如果我們希望父子元件也能雙向繫結時,Vue2.x 是不建議的,因為這會給父元件的維護帶來災難!

所以在 Vue2.x 中建議使用 this.$emit() 事件回傳機制明確通知父元件,真正的更新還是父元件自己實現。

後來為了簡化上述操作,在 Vue2.3.0 新增了 .sync 修飾符。

參考:cn.vuejs.org/v2/guide/co…

比如:父元件呼叫子元件 text-document 時,子元件就可以修改父元件的 doc.title

<text-document v-bind:title.sync="doc.title"></text-document>
複製程式碼

好了,通過以上描述我們可以得出結論:

  • v-model 可以實現表單元素的資料雙向繫結
  • v-bind:xxx.sync 或者簡寫為 :xxx.sync 可以實現父子元件的雙向繫結

那麼在 Vue3.x 中得到了統一:

:xxx.sync 將被 v-model:xxx 取代

如果你希望跟子元件直接雙向繫結,則:

<text-document v-model="doc"></text-document>
複製程式碼

或者多個屬性之間一一繫結:

<text-document 
    v-model:title="doc.title"
    v-model:content="doc.content"
></text-document>
複製程式碼

RFC檢視網友討論:github.com/vuejs/rfcs/…

5 Fragments Template 支援有多個根節點

Vue2.x 中 Template 模板你通常是這麼寫的:

<template>
    <div>
        <p>Hello</p>
        <p>Vue2.x</p>
    </div>
</template>
複製程式碼

template 中只能有唯一一個根節點。原因就是每個 Vue 的例項只允許繫結到唯一的Dom樹上。

如果你希望繫結兩個Dom,那麼你就只能在新建一個 Vue 示例,但是這樣就跟當前系統脫節了,沒啥意義了。

或者使用這個外掛:vue-fragments,類似於 React 中的 <React.Fragment>

<template>
  <v-fragment>
    <div>Fragment 1</div>
    <div>Fragment 2</div>
  </v-fragment>
</template>
複製程式碼

但是在 Vue3.x 中,就可以不用唯一根節點,也不用外掛了,變得簡單了許多:

<template>
    <p>Hello</p>
    <p>Vue3.x</p>
</template>
複製程式碼

6 Suspense Template Fallback 元件

Vue2.x 中你應該會經常遇到這種場景:

<template>
    <div>
        <div v-if="!loading">
            ...
        </div>
        <div v-if="loading">Loading...</div>
    </div>
</template>
複製程式碼

或者安裝這個外掛:vue-async-manager

然後,就變成了:

<template>
    <div>
        <Suspense>
            <div>
                ...
            </div>
            <div slot="fallback">Loading...</div>
        </Suspense>
    </div>
</template>
複製程式碼

Vue3.x 感覺就是參考了上面這個元件的做法,現在可以這麼寫:

<Suspense>
  <template #default>
    ...
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>
複製程式碼

#fallback 其實在 Vue3.x 中就是 slot 的簡寫。所以,#default 可以省略。

當然 React 也有 Suspense 元件解決類似的問題。

其實,這個全域性元件可能更多的會配合非同步元件使用。順便說下,在 Vue3.x 中,定義一個非同步元件使用:defineAsyncComponent

7 Teleport Template Dom佔位傳遞元件

注意: teleport3.0.0-alpha.11 剛改的名字,之前叫:portal, 檢視 CHANGELOG

Vue2.x 中你應該會經常遇到這種場景:

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <b> {{ user.name }} </b>  
    <button @click="isPopUpOpen = true">刪除使用者</button>
    
    <!-- 注意這一塊程式碼 -->
    <div v-show="isPopUpOpen">
      <p>確定刪除?</p>
      <button @click="removeUser">確定</button>
      <button @click="isPopUpOpen = false">取消</button>
    </div>
    
  </div>
</template>
複製程式碼

以上程式碼就是當我們需要做一個彈窗的時候,按照業務邏輯,彈窗和其他程式碼在一塊寫著。 但是這麼寫往往會出現問題,就是一旦我們點選 “刪除使用者”,本希望彈窗顯示,但是往往這個彈出框被外邊的元素擋住!由於 z-index 的原因。

那我們就想方設法讓這個彈出框直接掛在到 body 節點上,這樣就沒問題了。比如:可以通過 Javascript 追加這個彈出框到 body 中等,處理起來比較麻煩。

關鍵的問題是:業務邏輯被打斷了,程式碼也不連貫了

所以為了解決這個煩惱,Vue3新增了這個元件,現在你就可以這麼寫了。

在最外層的 App.vue 中:

<!-- 預留一塊空地,專門用來顯示這個容易被遮擋的層 -->
<div id="modal-container"></div>

<!-- app -->
<div id="app">
複製程式碼

在你自己的元件中:

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <b> {{ user.name }} </b>  
    <button @click="isPopUpOpen = true">刪除使用者</button>
    
    <!-- 注意這一塊程式碼 -->
    <Teleport to="#modal-container">
        <div v-show="isPopUpOpen">
          <p>確定刪除?</p>
          <button @click="removeUser">確定</button>
          <button @click="isPopUpOpen = false">取消</button>
        </div>
    </Teleport>
    
  </div>
</template>
複製程式碼

這樣技能保證你程式碼的完整性、業務的連貫性,又能解決彈視窗被遮擋的問題。

另外,今天在 3.0.0-alpha.11 CHANGELOG 中又看到一條更新記錄 16cd8ee

portal: portal should always remove its children when unmounted (16cd8ee)

就是說一旦元件被銷燬 unmounted,Teleport 裡面的元素應該被清空。如果不自動清空掉,隨著你頁面的切換,前一次頁面遺留的彈窗可能一直存在的bug。

最後

本文也是 VUE 3.0 學習探索入門系列 裡面的最後一篇,希望能對大家入門 Vue3 有所幫助。

接下來就等 Vue3.0 正式 Release 以後,再帶給大家 VUE 3.0 實戰上手篇 系列,歡迎大家關注我,及時瞭解動態。

本系列歷時20天完成,再次感謝大家。

(全劇終)

相關文章