是時候從vue2 遷移到 vue3了 (系列一)

u=706351709,1488187264&fm=26&gp=0.jpeg

1.v-for 中的 Ref 數組 非兼容

在 Vue 2 中,在 v-for 裏使用的 ref attribute 會用 ref 數組填充相應的 $refs property。當存在嵌套的 v-for 時,這種行爲會變得不明確且效率低下。javascript

在 Vue 3 中,這樣的用法將再也不在 $ref 中自動建立數組。要從單個綁定獲取多個 ref,請將 ref 綁定到一個更靈活的函數上 (這是一個新特性):html

2.x 語法

<template>
  <div> <div v-for="item in 10" :key="item" ref="liRef"></div> </div>
</template>
<script>
export default {
  name: 'Vue2Demo',
  data() {
    return {}
  },
  mounted() {
    console.log('refs', this.$refs.liRef)
    // vue2時,輸出: refs (10) [div, div, div, div, div, div, div, div, div, div]
    // vue3時, 輸出  refs <div>10<div>  (不會建立數組)
  }
}
</script>
複製代碼

3.x 語法

  • 結合選項式 API:
<template>
  <div> <div v-for="item in 10" :key="item" :ref="setItemRef"> {{item}} </div> </div>
</template>
<script> export default { data() { return { itemRefs: [] } }, methods: { setItemRef(el) { if (el) { this.itemRefs.push(el) } } }, mounted() { console.log('結合選項式 API:',this.itemRefs) } } </script>
複製代碼
  • 結合組合式 API:
<template>
  <div v-for="item in 10" :ref="setItemRef"> {{item}} </div>
</template>

<script> import { defineComponent,onMounted} from 'vue' export default defineComponent({ setup() { let itemRefs = []; const setItemRef = el => { if (el) { itemRefs.push(el) } } onMounted(() => { console.log('結合組合式 API:',itemRefs); }) return { setItemRef } } }) </script>
複製代碼

注意:前端

  • itemRefs 沒必要是數組:它也能夠是一個對象,其 ref 會經過迭代的 key 被設置。
  • 若是須要,itemRef 也能夠是響應式的且能夠被監聽。

2. 異步組件 新增

變化概覽:vue

  • 新的 defineAsyncComponent 助手方法,用於顯式地定義異步組件
  • component 選項重命名爲 loader
  • Loader 函數自己再也不接收 resolve 和 reject 參數,且必須返回一個 Promise

2.x 語法

異步組件是經過將組件定義爲返回 Promise 的函數來建立的java

const AsyncComponent = () => import('@/components/AsyncComponent')

複製代碼

或者,對於帶有選項的更高階的組件語法:node

const AsyncComponent = () => ({
  component: () => import('@/components/AsyncComponent'),
  delay: 200,
  timeout: 300,
  error: ErrorComponent,
  loading: LoadingComponent
})
複製代碼

3.x 語法

在 Vue 3 中,因爲函數式組件被定義爲純函數,所以異步組件的定義須要經過將其包裹在新的 defineAsyncComponent 助手方法中來顯式地定義:react

import { defineAsyncComponent } from 'vue'
import ErrorComponent from '@/components/ErrorComponent';
import LoadingComponent from '@/components/LoadingComponent';

// 不帶選項的異步組件
const  AsyncComponent = defineAsyncComponent(() => import('@/components/AsyncComponent'))

// 帶選項的異步組件
const AsyncComponentWithOptions = defineAsyncComponent({
  // 2.x 裏的component 選項改成 loader,以便準確地傳達不能直接提供組件定義的信息。loader 函數再也不接收 resolve 和 reject 參數,且必須始終返回 Promise。 
  loader: () => import('@/components/AsyncComponent'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})
複製代碼

3.attribute 強制行爲 非兼容

前置知識:

  • HTML 屬性
  • 布爾屬性與枚舉屬性的區別
  • 內容屬性與IDL屬性的區別

布爾屬性

  • 布爾屬性有個特色:當聲明瞭這個屬性時,其值爲 true;而未聲明時,其值爲 false。
  • HTML5 定義了布爾值屬性容許的取值:若是屬性存在,其值必須是一個空字符串(即該屬性的值未分配),或者是一個大小寫無關的 ASCII 字符串,該字符串與屬性名嚴格相同,先後都沒有空格。——摘自《MDN: Web 開發技術>HTML(超文本標記語言)>HTML 屬性參考>布爾值屬性
<div itemscope> This is valid HTML but invalid XML. </div>
<div itemscope=itemscope> This is also valid HTML but invalid XML. </div>
<div itemscope=""> This is valid HTML and also valid XML. </div>
<div itemscope="itemscope"> This is also valid HTML and XML, but perhaps a bit verbose. </div>
複製代碼

上面四種寫法是等效的。因此,布爾值屬性不能取值爲 「true」 和 「false」。若是須要表示 false 值,布爾值屬性須要整個忽略不寫。webpack

枚舉屬性

  • 枚舉屬性,顧名思義,就是取值是一個由若干關鍵詞組成的枚舉集合。例如 input 元素的 autocomplete 屬性,這個屬性可取值爲 username、email、country、tel、url 等等。
  • 須要注意的是有些枚舉屬性只接受兩個枚舉值:true和false。並且,空字符串 或者 不給屬性賦值 都等於true。下面寫法都表明true
<div contenteditable>An editable item</div>
<div contenteditable="">An editable item</div>
<div contenteditable="true">An editable item</div>
複製代碼

下面寫法都表明falseweb

<div contenteditable="false">An editable item</div>
<div contenteditable="abcdefg">An editable item</div>
<div>An editable item</div>
複製代碼

其餘屬性

除開上面兩種屬性分類,其他的屬性能夠歸類於常規屬性了。面試

內容屬性 和 IDL(接口描述語言)屬性:

HTML 中,屬性還有 內容屬性 和 IDL屬性 說法。注意,這兩種屬性,並非對標籤屬性的劃分。他們只是屬性不一樣地方的不一樣描述和寫法而已。

內容屬性 接收的值都是字符串。編寫 HTML 時,直接寫在標籤中的就是內容屬性。此外,內容屬性還能夠經過 JS 的 setAttribute() 來設置。

<div contenteditable>An editable item</div>

input.setAttribute('type', 'text');
input.getAttribute('type');

而 IDL屬性 是一個 JavaScript 屬性(property),是 DOM 提供給 JS 的真正屬性。經過 . 運算符來設置,且只接收正確類型的值。若是接收值的類型不正確,會自動轉化成正確的類型。

input.type = 'password';
複製代碼

MDN: Web 開發技術>HTML(超文本標記語言)>HTML 屬性參考

變化概覽:

  • 刪除枚舉 attribute 的內部概念,並將這些 attribute 視爲普通的非布爾 attribute
  • 重大改變:若是值爲布爾值,則再也不刪除 attribute false。相反,它被設置爲 attr=「false」。移除 attribute,應該使用 null 或者 undefined。

2.x 語法

  • 對於某些屬性/元素對,Vue 採用 IDL 屬性 形式處理:如 value of <input>, <select>, <progress>, 等等.
  • 對於 布爾值屬性 和 xlinks,Vue 經過判斷 是不是falsy(undefined、 null、false)值來決定添加或是刪除屬性。
  • 對於 枚舉屬性, Vue 強制轉化爲字符串。
  • 對於其餘(普通非布爾)屬性,若是傳遞過來的值是 falsy 值則刪除,不然直接添加

下表描述了 Vue 如何使用普通非布爾 attribute 強制「枚舉 attribute」:

v-bind表達式 普通非布爾屬性:foo 枚舉屬性:draggable
:attr="null" / draggable="false"
:attr="undefined" / /
:attr="true" foo="true" draggable="true"
:attr="false" / draggable="false"
:attr="0" foo="0" draggable="true"
attr="" foo="" draggable="true"
attr="foo" foo="foo" draggable="true"
attr foo="" draggable="true"

/: 移除

從上面的對照表能夠看出,二者的表現是不一致。這樣會形成使用時的不便。

3.x 語法

在 Vue 3.x 中移除了枚舉屬性的概念,統一將他們視爲普通非布爾屬性。這樣作的好處:

  • 消除了普通非布爾屬性和枚舉屬性表現形式的不一致(換而言之,在 Vue 3.x 中,只存在非布爾屬性和布爾屬性)
  • 意味着能夠對 枚舉屬性 使用除 true 和 false 之外的值,甚至是未使用的關鍵字。

此外,對於非布爾屬性,若是傳遞的值是false,Vue 將再也不會刪除屬性了,而是強制轉化爲字符串'false'。

上面那張表格,在 Vue 3.x 中的表現則變成:

v-bind表達式 普通非布爾屬性:foo 枚舉屬性:draggable
:attr="null" / /
:attr="undefined" / /
:attr="true" foo="true" draggable="true"
:attr="false" foo="false" draggable="false"
:attr="0" foo="0" draggable="0"
attr="" foo="" draggable=""
attr="foo" foo="foo" draggable="foo"
attr foo="" draggable=""

能夠看到,普通非布爾屬性 和 枚舉屬性 結果是一致的。

對於 非布爾屬性,false 被強制轉化爲'false',再也不刪除屬性。因此,在 Vue 3.x 中,應該使用undefined和null來顯式刪除屬性。

注意,布爾屬性 表現並改變,和 Vue 2.x 保持一致。

Attribute v-bind value 2.x v-bind value 3.x HTML output
Vue 2.x 中的枚舉屬性,如: contenteditable, draggable and spellcheck. undefined, false undefined, null removed
true, 'true', '', 1, 'foo' true, 'true' "true"
null, 'false' false, 'false' "false"
Vue 2.x 中的普通非布爾屬性,如:aria-checked, tabindex, alt, etc. undefined, null, false undefined, null removed
'false' false, 'false' "false"
布爾屬性:required、disabled、readonly false、null、undefined false、null、undefined removed

實際代碼測試

<div style="width: 500px">
  非枚舉非布爾屬性:true:<input type="text" :foo="true" />
  非枚舉非布爾屬性:false:<input type="text" :foo="false" />
  非枚舉非布爾屬性:undefined:<input type="text" :foo="undefined" />
  非枚舉非布爾屬性:null:<input type="text" :foo="null" />
  非枚舉非布爾屬性:0:<input type="text" :foo="0" />

  <hr />
  枚舉屬性:true:<input type="text" :spellcheck="true" />
  枚舉屬性:false:<input type="text" :spellcheck="false" />
  枚舉屬性:undefined:<input type="text" :spellcheck="undefined" />
  枚舉屬性:null:<input type="text" :spellcheck="null" />
  枚舉屬性:0:<input type="text" :spellcheck="0" />

  <hr />
  布爾屬性required:true:<input type="text" :required="true" />
  布爾屬性required:false:<input type="text" :required="false" />
  布爾屬性required:undefined:<input type="text" :required="undefined" />
  布爾屬性required:null:<input type="text" :required="null" />
  布爾屬性required:0:<input type="text" :required="0" />
</div>
複製代碼

結果: image.png

4. $attrs包含class&style 非兼容

變化概覽:

  • 如今 $attrs 包含傳遞給組件的全部 attribute,包括 class 和 style。

2.x 語法

在 Vue 2 的虛擬 DOM 實現中對 class 和 style attribute 有一些特殊處理。所以,它們不包含在 $attrs 中,而其它全部 attribute 都包含在其中。

在使用 inheritAttrs: false 時會產生反作用:

  • $attrs 中的 attribute 再也不自動添加到根元素中,而是由開發者決定在哪添加。
  • 可是 class 和 style 不屬於 $attrs,仍然會應用到組件的根元素:
<template>
  <label> <input type="text" v-bind="$attrs" /> </label>
</template>
<script> export default { inheritAttrs: false } </script>
複製代碼

像這樣使用時:

<my-component id="my-id" class="my-class"></my-component>
複製代碼

……將生成如下 HTML:

<label class="my-class">
  <input type="text" id="my-id" />
</label>
複製代碼

3.x 語法

$attrs 包含全部的 attribute,這使得把它們所有應用到另外一個元素上更加容易。那麼上面的示例就會生成如下 HTML:

<label>
  <input type="text" id="my-id" class="my-class" />
</label>
複製代碼

5.$children 移除

變化概覽:

  • $children 實例 property 已從 Vue 3.0 中移除,再也不支持。

2.x 語法

在 2.x 中,開發者可使用 this.$children 直接訪問當前實例的子組件:

<template>
  <div> <img alt="Vue logo" src="./assets/logo.png"> <my-button>Change logo</my-button> </div> </template>

<script> import MyButton from './MyButton' export default { components: { MyButton }, mounted() { console.log(this.$children) // [VueComponent] } } </script>
複製代碼

3.x 語法

在 3.x 中,$children property 已移除,再也不支持。若是你須要訪問子組件實例,咱們建議使用 $refs。

6.自定義指令 非兼容

變化概覽:

  • 指令的鉤子函數已經被重命名,以更好地與組件的生命週期保持一致。

2.x 語法

在 Vue 2,自定義指令是經過使用下面列出的鉤子來建立的,這些鉤子都是可選的

  • bind - 指令綁定到元素後發生。只發生一次。
  • inserted - 元素插入父 DOM 後發生。
  • update - 當元素更新,但子元素還沒有更新時,將調用此鉤子。
  • componentUpdated - 一旦組件和子級被更新,就會調用這個鉤子。
  • unbind - 一旦指令被移除,就會調用這個鉤子。也只調用一次。

下面是一個例子:

<p v-highlight="'yellow'">高亮顯示此文本亮黃色</p>
複製代碼
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value
  }
})
複製代碼

在這裏,在這個元素的初始設置中,指令經過傳遞一個值來綁定樣式,該值能夠經過應用程序更新爲不一樣的值。

3.x 語法

然而,在 Vue 3 中,咱們爲自定義指令建立了一個更具凝聚力的 API。正如你所看到的,它們與咱們的組件生命週期方法有很大的不一樣,即便咱們正與相似的事件鉤子,咱們如今把它們統一塊兒來了:

  • created - 新的!在元素的 attribute 或事件偵聽器應用以前調用。
  • bind → beforeMount
  • inserted → mounted
  • beforeUpdate:新的!這是在元素自己更新以前調用的,很像組件生命週期鉤子。
  • update → 移除!有太多的類似之處要更新,因此這是多餘的,請改用 updated。
  • componentUpdated → updated
  • beforeUnmount:新的!與組件生命週期鉤子相似,它將在卸載元素以前調用。
  • unbind -> unmounted

最終 API 以下:

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

生成的 API 能夠這樣使用,與前面的示例相同:

<p v-highlight="'yellow'">高亮顯示此文本亮黃色</p>
複製代碼
const app = Vue.createApp({})

app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})
複製代碼

7.自定義元素交互 非兼容

變化概覽:

  • 非兼容:自定義元素白名單如今在模板編譯期間執行,應該經過編譯器選項而不是運行時配置來配置。
  • 非兼容:特定 is prop 用法僅限於保留的 <component> 標記。
  • 新增:有了新的 v-is 指令來支持 2.x 用例,其中在原生元素上使用了 v-is 來處理原生 HTML 解析限制。

自主定製元素

若是咱們想添加在 Vue 外部定義的自定義元素 (例如使用 Web 組件 API),咱們須要「指示」Vue 將其視爲自定義元素。讓咱們如下面的模板爲例。

<plastic-button></plastic-button>
複製代碼

2.x 語法

在 Vue 2.x 中,將標記做爲自定義元素白名單是經過 Vue.config.ignoredElements:

// 這將使Vue忽略在Vue外部定義的自定義元素
// (例如:使用 Web Components API)

Vue.config.ignoredElements = ['plastic-button']
複製代碼

3.x 語法

在 Vue 3.0 中,此檢查在模板編譯期間執行指示編譯器將 <plastic-button> 視爲自定義元素:

  • 若是使用生成步驟:將 isCustomElement 傳遞給 Vue 模板編譯器,若是使用 vue-loader,則應經過 vue-loader 的 compilerOptions 選項傳遞:
// webpack 中的配置
rules: [
  {
    test: /\.vue$/,
    use: 'vue-loader',
    options: {
      compilerOptions: {
        isCustomElement: tag => tag === 'plastic-button'
      }
    }
  }
  // ...
]
複製代碼
  • 若是使用動態模板編譯,請經過 app.config.isCustomElement 傳遞:
const app = Vue.createApp({})
app.config.isCustomElement = tag => tag === 'plastic-button'
複製代碼

須要注意的是,運行時配置只會影響運行時模板編譯——它不會影響預編譯的模板。

定製內置元素

自定義元素規範提供了一種將自定義元素用做自定義內置模板的方法,方法是向內置元素添加 is 屬性:

<button is="plastic-button">點擊我!</button>
複製代碼

Vue 對 is 特殊 prop 的使用是在模擬 native attribute 在瀏覽器中廣泛可用以前的做用。可是,在 2.x 中,它被解釋爲渲染一個名爲 plastic-button 的 Vue 組件,這將阻止上面提到的自定義內置元素的原生使用。

在 3.0 中,咱們僅將 Vue 對 is 屬性的特殊處理限制到 <component> tag。

  • 在保留的 <component> tag 上使用時,它的行爲將與 2.x 中徹底相同;

  • 在普通組件上使用時,它的行爲將相似於普通 prop:

    <foo is="bar" />
    複製代碼
    • 2.x 行爲:渲染 bar 組件。
    • 3.x 行爲:經過 is prop 渲染 foo 組件。
  • 在普通元素上使用時,它將做爲 is 選項傳遞給 createElement 調用,並做爲原生 attribute 渲染,這支持使用自定義的內置元素。

    <button is="plastic-button">點擊我!</button>
    複製代碼
    • 2.x 行爲:渲染 plastic-button 組件。

    • 3.x 行爲:經過回調渲染原生的 button。

      document.createElement('button', { is: 'plastic-button' })
      複製代碼

v-is 用於 DOM 內模板解析解決方案

提示:本節僅影響直接在頁面的 HTML 中寫入 Vue 模板的狀況。 在 DOM 模板中使用時,模板受原生 HTML 解析規則的約束。一些 HTML 元素,例如 <ul>,<ol>,<table> 和 <select> 對它們內部能夠出現的元素有限制,和一些像 <li>,<tr>,和 <option> 只能出如今某些其餘元素中。

2x 語法

在 Vue 2 中,咱們建議經過在原生 tag 上使用 is prop 來解決這些限制:

<table>
  <tr is="blog-post-row"></tr>
</table>
複製代碼

3.x 語法

隨着 is 的行爲變化,咱們引入了一個新的指令 v-is,用於解決這些狀況:

<table>
  <tr v-is="'blog-post-row'"></tr>
</table>
複製代碼

v-is 函數像一個動態的 2.x :is 綁定——所以,要按註冊名稱渲染組件,其值應爲 JavaScript 字符串文本:

<!-- 不正確,不會渲染任何內容 -->
<tr v-is="blog-post-row"></tr>

<!-- 正確 -->
<tr v-is="'blog-post-row'"></tr>
複製代碼

8.Data 選項 非兼容

變化概覽:

  • 非兼容:data 組件選項聲明再也不接收純 JavaScript object,而須要 function 聲明。

當合並來自 mixin 或 extend 的多個 data 返回值時,如今是淺層次合併的而不是深層次合併的(只合並根級屬性)。

2x 語法

在 2.x 中,開發者能夠定義 data 選項是 object 或者是 function。

<!-- Object 聲明 -->
<script> const app = new Vue({ data: { apiKey: 'a1b2c3' } }) </script>

<!-- Function 聲明 -->
<script> const app = new Vue({ data() { return { apiKey: 'a1b2c3' } } }) </script>
複製代碼

雖然這對於具備共享狀態的根實例提供了一些便利,可是因爲只有在根實例上纔有可能,這致使了混亂。

3x 語法

在 3.x,data 選項已標準化爲只接受返回 object 的 function。

使用上面的示例,代碼只有一個可能的實現:

<script>
  import { createApp } from 'vue'

  createApp({
    data() {
      return {
        apiKey: 'a1b2c3'
      }
    }
  }).mount('#app')
</script>
複製代碼

Mixin 合併行爲變動

此外,當來自組件的 data() 及其 mixin 或 extends 基類被合併時,如今將淺層次執行合併:

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}
const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}
複製代碼

在 Vue 2.x中,生成的 $data 是:

{
  user: {
    id: 2,
    name: 'Jack'
  }
}
複製代碼

在 3.0 中,其結果將會是:

{
  user: {
    id: 2
  }
}
複製代碼

9.emits Option 新增

變化概覽: Vue 3如今提供了一個 emits 選項,相似於現有的 props 選項。此選項可用於定義組件能夠發送給其父組件的事件。

2x 語法

在Vue 2中,你能夠定義組件接收的props,但你不能聲明它能夠發出哪些事件:

<template>
  <div> <p>{{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div>
</template>
<script> export default { props: ['text'] } </script>
複製代碼

3x 語法

與props相似,組件發出的事件如今能夠用emits選項來定義:

<template>
  <div> <p>{{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div>
</template>
<script> export default { props: ['text'], emits: ['accepted'] } </script>
複製代碼

該選項還接受一個對象,它容許開發人員爲隨觸發事件傳遞的參數定義驗證器,相似於props定義中的驗證器。詳情見

10.事件 API 非兼容

變化概覽:

  • $on,$off 和 $once 實例方法已被移除,應用實例再也不實現事件觸發接口。

2.x 語法

在 2.x 中,Vue 實例可用於觸發由事件觸發 API 經過指令式方式添加的處理函數 ( o n on, off 和 $once)。這能夠建立 event hub,用來建立在整個應用程序中可用的全局事件監聽器:

// eventHub.js

const eventHub = new Vue()

export default eventHub

// ChildComponent.vue
import eventHub from './eventHub'

export default {
  mounted() {
    // 添加 eventHub 監聽器
    eventHub.$on('custom-event', () => {
      console.log('Custom event triggered!')
    })
  },
  beforeDestroy() {
    // 移除 eventHub 監聽器
    eventHub.$off('custom-event')
  }
}

// ParentComponent.vue
import eventHub from './eventHub'

export default {
  methods: {
    callGlobalCustomEvent() {
      eventHub.$emit('custom-event') // 當 ChildComponent 被掛載,控制檯中將顯示一條消息
    }
  }
}
複製代碼

3.x 語法

咱們從實例中徹底移除了 o n on、 off 和 o n c e 方法。 once 方法。 emit 仍然包含於現有的 API 中,由於它用於觸發由父組件聲明式添加的事件處理函數。

11.過濾器 移除

變化概覽:

  • 從 Vue 3.0 開始,過濾器已刪除,再也不支持。

2.x 語法

在 2.x,開發者可使用過濾器來處理通用文本格式。

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountBalance | currencyUSD }}</p>
</template>

<script> export default { props: { accountBalance: { type: Number, required: true } }, filters: { currencyUSD(value) { return '$' + value } } } </script>
複製代碼

雖然這看起來很方便,但它須要一個自定義語法,打破大括號內表達式是「只是 JavaScript」的假設,這不只有學習成本,並且有實現成本。

3.x 語法

在 3.x 中,過濾器已刪除,再也不支持。相反地,咱們建議用方法調用或計算屬性替換它們。

使用上面的例子,這裏是一個如何實現它的例子。

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountInUSD }}</p>
</template>

<script> export default { props: { accountBalance: { type: Number, required: true } }, computed: { accountInUSD() { return '$' + this.accountBalance } } } </script>
複製代碼

建議用計算屬性或方法代替過濾器,而不是使用過濾器

全局過濾器

若是在應用中全局註冊了過濾器,那麼在每一個組件中用計算屬性或方法調用來替換它可能就沒那麼方便了。

相反地,你能夠經過全局屬性在全部組件中使用它:

// main.js
const app = createApp(App)

app.config.globalProperties.$filters = {
  currencyUSD(value) {
    return '$' + value
  }
}
複製代碼

而後,你能夠經過 $filters 對象修改全部的模板,像下面這樣:

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>
複製代碼

注意,這種方式只能用於方法中,不能夠在計算屬性中使用,由於後者只有在單個組件的上下文中定義時纔有意義。

12.片斷 新增

變化概覽:

  • Vue 3 如今正式支持了多根節點的組件,也就是片斷!

2.x 語法

在 2.x 中,因爲不支持多根節點組件,當開發者意外建立一個時會發出警告。爲了修復這個問題,許多組件被包裹在一個 <div> 中。

<!-- Layout.vue -->
<template> <div> <header>...</header> <main>...</main> <footer>...</footer> </div> </template>
複製代碼

3.x 語法

在 3.x 中,組件能夠包含多個根節點!可是,這要求開發者顯式定義 attribute 應該分佈在哪裏。

<!-- Layout.vue -->
<template> <header>...</header> <main v-bind="$attrs">...</main> <footer>...</footer> </template>
複製代碼

13.函數式組件 非兼容

變化概覽:

  • 在 3.x 中,函數式組件 2.x 的性能提高能夠忽略不計,所以咱們建議只使用有狀態的組件
  • 函數式組件只能使用接收 props 和 context 的普通函數建立 (即:slots,attrs,emit)。
  • 非兼容變動:functional attribute 在單文件組件 (SFC) <template> 已被移除
  • 非兼容變動:{ functional: true } 選項在經過函數建立組件已被移除

介紹:

在 Vue 2 中,函數式組件有兩個主要應用場景:

  • 做爲性能優化,由於它們的初始化速度比有狀態組件快得多
  • 返回多個根節點

然而,在 Vue 3 中,有狀態組件的性能已經提升到能夠忽略不計的程度。此外,有狀態組件如今還包括返回多個根節點的能力。

所以,函數式組件剩下的惟一應用場景就是簡單組件,好比建立動態標題的組件。不然,建議你像日常同樣使用有狀態組件。

2.x 語法

使用 組件,負責提供適當的標題 (即:h1,h2,h3,等等),在 2.x 中,這多是做爲單個文件組件編寫的:

// Vue 2 函數式組件示例
export default {
  functional: true,
  props: ['level'],
  render(h, { props, data, children }) {
    return h(`h${props.level}`, data, children)
  }
}
複製代碼

或者,對於喜歡在單個文件組件中使用 <template> 的用戶:

<!-- Vue 2 函數式組件示例使用 <template> -->
<template functional> <component :is="`h${props.level}`" v-bind="attrs" v-on="listeners" /> </template>

<script> export default { props: ['level'] } </script>
複製代碼

3.x 語法

  • 經過函數建立組件

如今在 Vue 3 中,全部的函數式組件都是用普通函數建立的,換句話說,不須要定義 { functional: true } 組件選項。

它們將接收兩個參數:props 和 context。context 參數是一個對象,包含組件的 attrs,slots,和 emit property。

此外,如今不是在 render 函數中隱式提供 h,而是全局導入 h。

使用前面提到的 <dynamic-heading> 組件的示例,下面是它如今的樣子。

import { h } from 'vue'

const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading
複製代碼
  • 單文件組件 (SFC)

在 3.x 中,有狀態組件和函數式組件之間的性能差別已經大大減小,而且在大多數用例中是微不足道的。所以,在 SFCs 上使用 functional 的開發人員的遷移路徑是刪除該 attribute,並將 props 的全部引用重命名爲 $props,將 attrs 重命名爲 $attrs。

使用以前的 <dynamic-heading> 示例,下面是它如今的樣子。

<template>
  <component v-bind:is="`h${$props.level}`" v-bind="$attrs" />
</template>

<script> export default { props: ['level'] } </script>
複製代碼

主要的區別在於:

  • functional attribute 在 <template> 中移除
  • listeners 如今做爲 $attrs 的一部分傳遞,能夠將其刪除

14.全局 API 非兼容

Vue 2.x 有許多全局 API 和配置,這些 API 和配置能夠全局改變 Vue 的行爲。例如,要註冊全局組件,可使用 Vue.component 這樣的 API:

Vue.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
複製代碼

相似地,使用全局指令的聲明方式以下:

Vue.directive('focus', {
  inserted: el => el.focus()
})
複製代碼

雖然這種聲明方式很方便,但它也會致使一些問題。從技術上講,Vue 2 沒有「app」的概念,咱們定義的應用只是經過 new Vue() 建立的根 Vue 實例。從同一個 Vue 構造函數建立的每一個根實例共享相同的全局配置,所以:

  • 在測試期間,全局配置很容易意外地污染其餘測試用例。用戶須要仔細存儲原始全局配置,並在每次測試後恢復 (例如重置 Vue.config.errorHandler)。有些 API 像 Vue.use 以及 Vue.mixin 甚至連恢復效果的方法都沒有,這使得涉及插件的測試特別棘手。實際上,vue-test-utils 必須實現一個特殊的 API createLocalVue 來處理此問題:
    import { createLocalVue, mount } from '@vue/test-utils'
    
    // 建擴展的 `Vue` 構造函數
    const localVue = createLocalVue()
    
    // 在 「local」 Vue構造函數上 「全局」 安裝插件
    localVue.use(MyPlugin)
    
    // 經過 `localVue` 來掛載選項
    mount(Component, { localVue })
    複製代碼
  • 全局配置使得在同一頁面上的多個「app」之間共享同一個 Vue 副本很是困難,但全局配置不一樣。
    // 這會影響兩個根實例
    Vue.mixin({
      /* ... */
    })
    
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })
    複製代碼

爲了不這些問題,在 Vue 3 中咱們引入...

一個新的全局 API:createApp

調用 createApp 返回一個應用實例,這是 Vue 3 中的新概念:

import { createApp } from 'vue'

const app = createApp({})
複製代碼

若是你使用的是 Vue 的 CDN 構建,那麼 createApp 是經過全局的 Vue 對象暴露的。

const { createApp } = Vue

const app = createApp({})
複製代碼

應用實例暴露了 Vue 2 當前全局 API 的子集,經驗法則是,任何全局改變 Vue 行爲的 API 如今都會移動到應用實例上,如下是當前 Vue2 全局 API 及其相應實例 API 的表

2.x 全局 API 3.x 實例 API (app)
Vue.config app.config
Vue.config.productionTip removed (見下方)
Vue.config.ignoredElements app.config.isCustomElement (見下方)
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use (見下方)
Vue.prototype app.config.globalProperties (見下方)

全部其餘不全局改變行爲的全局 API 如今被命名爲 exports,文檔見 全局 API Treeshaking

config.productionTip 移除

在 Vue 3.x 中,「使用生產版本」提示僅在使用「dev + full build」(包含運行時編譯器並有警告的構建) 時纔會顯示。

對於 ES 模塊構建,因爲它們是與 bundler 一塊兒使用的,並且在大多數狀況下,CLI 或樣板已經正確地配置了生產環境,因此本技巧將再也不出現。

config.ignoredElements 替換爲 config.isCustomElement

引入此配置選項的目的是支持原生自定義元素,所以重命名能夠更好地傳達它的功能,新選項還須要一個比舊的 string/RegExp 方法提供更多靈活性的函數:

// 以前
Vue.config.ignoredElements = ['my-el', /^ion-/]

// 以後
const app = createApp({})
app.config.isCustomElement = tag => tag.startsWith('ion-')
複製代碼

在 Vue 3 中,元素是不是組件的檢查已轉移到模板編譯階段,所以只有在使用運行時編譯器時才考慮此配置選項。若是你使用的是 runtime-only 版本 isCustomElement 必須經過 @vue/compiler-dom 在構建步驟替換——好比,經過 compilerOptions option in vue-loader。

  • 若是 config.isCustomElement 當使用僅運行時構建時時,將發出警告,指示用戶在生成設置中傳遞該選項;
  • 這將是 Vue CLI 配置中新的頂層選項。

Vue.prototype 替換爲 config.globalProperties

在 Vue 2 中, Vue.prototype 一般用於添加全部組件都能訪問的 property。

在 Vue 3 等同於config.globalProperties。這些 property 將被複制到應用中做爲實例化組件的一部分。

// 以前 - Vue 2
Vue.prototype.$http = () => {}
複製代碼
// 以後 - Vue 3
const app = createApp({})
app.config.globalProperties.$http = () => {}
複製代碼

插件使用者須知

插件開發者一般使用 Vue.use。例如,官方的 vue-router 插件是如何在瀏覽器環境中自行安裝的:

var inBrowser = typeof window !== 'undefined'
/* … */
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}
複製代碼

因爲 use 全局 API 在 Vue 3 中再也不使用,此方法將中止工做並中止調用 Vue.use() 如今將觸發警告,因而,開發者必須在應用程序實例上顯式指定使用此插件:

const app = createApp(MyApp)
app.use(VueRouter)
複製代碼

掛載 App 實例

使用 createApp(/* options */) 初始化後,應用實例 app 可用 app.mount(domTarget) 掛載根組件實例:

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

const app = createApp(MyApp)
app.mount('#app')
複製代碼

通過全部這些更改,咱們在指南開頭的組件和指令將被改寫爲以下內容:

const app = createApp(MyApp)

app.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

app.directive('focus', {
  mounted: el => el.focus()
})

// 如今全部應用實例都掛載了,與其組件樹一塊兒,將具備相同的 「button-counter」 組件 和 「focus」 指令不污染全局環境
app.mount('#app')
複製代碼

Provide / Inject

與在 2.x 根實例中使用 provide 選項相似,Vue 3 應用實例還能夠提供可由應用內的任何組件注入的依賴項:

// 在入口
app.provide('guide', 'Vue 3 Guide')

// 在子組件
export default {
  inject: {
    book: {
      from: 'guide'
    }
  },
  template: `<div>{{ book }}</div>`
}
複製代碼

使用 provide 在編寫插件時很是有用,能夠替代 globalProperties。

在應用之間共享配置

在應用之間共享配置 (如組件或指令) 的一種方法是建立工廠功能,以下所示:

import { createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

const createMyApp = options => {
  const app = createApp(options)
  app.directive('focus', /* ... */)

  return app
}

createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')
複製代碼

如今,Foo 和 Bar 實例及其後代中均可以使用 focus 指令。

15.全局 API Treeshaking 非兼容

2.x 語法

若是你曾經在 Vue 中手動操做過 DOM,你可能會遇到如下模式:

import Vue from 'vue'

Vue.nextTick(() => {
  // 一些和DOM有關的東西
})
複製代碼

或者,若是你一直在對涉及 async components 的應用程序進行單元測試,那麼極可能你編寫了如下內容:

import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // 執行一些DOM相關的任務

  await wrapper.vm.$nextTick()

  // 運行你的斷言
})
複製代碼

Vue.nextTick() 是一個全局的 API 直接暴露在單個 Vue 對象上。事實上,實例方法 $nextTick() 只是 Vue.nextTick() 的一個便利的包裹器,回調的 this 上下文自動綁定到當前實例上,以方便使用。

可是,若是你歷來沒有處理過手動的 DOM 操做,也沒有在你的應用中使用或測試異步組件,怎麼辦?或者,無論出於什麼緣由,你更喜歡使用老式的 window.setTimeout() 來代替呢?在這種狀況下,nextTick() 的代碼就會變成死代碼--也就是說,寫了代碼但從未使用過。而死代碼幾乎不是一件好事,尤爲是在咱們的客戶端上下文中,每一行代碼都很重要。

模塊捆綁程序,如 webpack 支持 tree-shaking,這是「死代碼消除」的一個花哨術語。不幸的是,因爲代碼是如何在之前的 Vue 版本中編寫的,全局 API Vue.nextTick() 不可搖動,將包含在最終捆綁中無論它們實際在哪裏使用。

3.x 語法

在 Vue 3 中,全局和內部 API 都通過了重構,並考慮到了 tree-shaking 的支持。所以,全局 API 如今只能做爲 ES 模塊構建的命名導出進行訪問。例如,咱們以前的片斷如今應該以下所示:

import { nextTick } from 'vue'

nextTick(() => {
  // 一些和DOM有關的東西
})
複製代碼

import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // 執行一些DOM相關的任務

  await nextTick()

  // 運行你的斷言
})
複製代碼

直接調用 Vue.nextTick() 將致使臭名昭著的 undefined is not a function 錯誤。

經過這一更改,若是模塊綁定器支持 tree-shaking,則 Vue 應用程序中未使用的全局 api 將從最終捆綁包中消除,從而得到最佳的文件大小。

受影響的 API

Vue 2.x 中的這些全局 API 受此更改的影響:

  • Vue.nextTick
  • Vue.observable (用 Vue.reactive 替換)
  • Vue.version
  • Vue.compile (僅全構建)
  • Vue.set (僅兼容構建)
  • Vue.delete (僅兼容構建)

內部幫助器

除了公共 api,許多內部組件/幫助器如今也被導出爲命名導出,只有當編譯器的輸出是這些特性時,才容許編譯器導入這些特性,例如如下模板:

<transition>
  <div v-show="ok">hello</div>
</transition>
複製代碼

被編譯爲相似於如下的內容:

import { h, Transition, withDirectives, vShow } from 'vue'

export function render() {
  return h(Transition, [withDirectives(h('div', 'hello'), [[vShow, this.ok]])])
}

複製代碼

這實際上意味着只有在應用程序實際使用了 Transition 組件時纔會導入它。換句話說,若是應用程序沒有任何 Transition 組件,那麼支持此功能的代碼將不會出如今最終的捆綁包中。

隨着全局 tree-shaking,用戶只需爲他們實際使用的功能「付費」,更好的是,知道了可選特性不會增長不使用它們的應用程序的捆綁包大小,框架大小在未來已經再也不是其餘核心功能的考慮因素了,若是有的話。

以上僅適用於 ES Modules builds,用於支持 tree-shaking 的綁定器——UMD 構建仍然包括全部特性,並暴露 Vue 全局變量上的全部內容 (編譯器將生成適當的輸出,才得以使用全局外的 api 而不是導入)。

插件中的用法

若是你的插件依賴受影響的 Vue 2.x 全局 API,例如:

const plugin = {
  install: Vue => {
    Vue.nextTick(() => {
      // ...
    })
  }
}
複製代碼

在 Vue 3 中,必須顯式導入:

import { nextTick } from 'vue'

const plugin = {
  install: app => {
    nextTick(() => {
      // ...
    })
  }
}
複製代碼

若是使用 webpack 這樣的模塊捆綁包,這可能會致使 Vue 的源代碼綁定到插件中,並且一般狀況下,這並非你所指望的。防止這種狀況發生的一種常見作法是配置模塊綁定器以將 Vue 從最終捆綁中排除。對於 webpack,你可使用 externals 配置選項:

// webpack.config.js
module.exports = {
  /*...*/
  externals: {
    vue: 'Vue'
  }
}
複製代碼

這將告訴 webpack 將 Vue 模塊視爲一個外部庫,而不是捆綁它。

若是你選擇的模塊綁定器剛好是 Rollup,你基本上能夠無償得到相同的效果,由於默認狀況下,Rollup 會將絕對模塊 id (在咱們的例子中爲 'vue') 做爲外部依賴項,而不會將它們包含在最終的 bundle 中。可是在綁按期間,它可能會拋出一個「將 vue 做爲外部依賴」警告,可以使用 external 選項抑制該警告:

// rollup.config.js
export default {
  /*...*/
  external: ['vue']
}
複製代碼

最後:

本筆記主要基於官方文檔 遷移策略 彙總而來。若有理解出入,請以官方文檔爲主。建議您以官方文檔爲主,本文爲輔。這樣您能夠「以本身爲主」審視的閱讀,從而不被個人觀點帶偏

分享下本身整理的部分知識點文章連接

相關文章
相關標籤/搜索