Vue2到Vue3的變化細(xì)節(jié)

1. 異步組件(新增

  • 通過(guò)defineAsyncComponent方法顯式地定義異步組件
  • component 選項(xiàng)被重命名為 loader
  • Loader 函數(shù)本身不再接收 resolve 和 reject 參數(shù),且必須返回一個(gè) Promise
  • 注意:VueRouter支持一個(gè)類似的機(jī)制來(lái)異步加載路由組件,也就是俗稱的懶加載。盡管類似,但是這個(gè)功能和Vue支持的異步組件是不同的
  • 當(dāng)用 Vue Router 配置路由組件時(shí),你不應(yīng)該使用 defineAsyncComponent
  • Vue2

以前,異步組件是通過(guò)將組件定義為返回 Promise 的函數(shù)來(lái)創(chuàng)建的,例如:

const asyncModal = () => import('./Modal.vue')

或者,對(duì)于帶有選項(xiàng)的更高階的組件語(yǔ)法

const asyncModal = {
  component: () => import('./Modal.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
}
  • Vue3

現(xiàn)在,在 Vue 3 中,由于函數(shù)式組件被定義為純函數(shù),因此異步組件需要通過(guò)將其包裹在新的 defineAsyncComponent 助手方法中來(lái)顯式地定義

import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// 不帶選項(xiàng)的異步組件
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))

// 帶選項(xiàng)的異步組件
const asyncModalWithOptions = defineAsyncComponent({
  loader: () => import('./Modal.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})

此外,與 2.x 不同,loader 函數(shù)不再接收 resolve 和 reject 參數(shù),且必須始終返回 Promise

// 2.x 版本
const oldAsyncComponent = (resolve, reject) => {
  /* ... */
}

// 3.x 版本
const asyncComponent = defineAsyncComponent(
  () =>
    new Promise((resolve, reject) => {
      /* ... */
    })
)

2. this.$children(移除

  • Vue2

在 2.x 中,開(kāi)發(fā)者可以使用 this.$children 訪問(wèn)當(dāng)前實(shí)例的直接子組件:

<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>
  • Vue3

在 3.x 中,children property 已被移除,且不再支持。如果你需要訪問(wèn)子組件實(shí)例,我們建議使用refs

3. 使用 vue: 前綴來(lái)解決 DOM 內(nèi)模板解析問(wèn)題(非兼容

提示:本節(jié)僅影響直接在頁(yè)面的 HTML 中寫入 Vue 模板的情況。 在 DOM 模板中使用時(shí),模板受原生 HTML 解析規(guī)則的約束。一些 HTML 元素,例如 ul、ol、table 和 select 對(duì)它們內(nèi)部可以出現(xiàn)的元素有限制,以及一些像 li、tr、和 option 只能出現(xiàn)在特定的其他元素中

  • Vue2

<!-- 在 Vue 2 中,我們建議在原生標(biāo)簽上使用 is attribute 來(lái)繞過(guò)這些限制: -->

<table>
  <tr is="blog-post-row"></tr>
</table>
  • Vue3

<!-- 隨著 is 的行為發(fā)生變化,現(xiàn)在將元素解析為 Vue 組件需要添加一個(gè) vue: 前綴: -->

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

4. Data 選項(xiàng)(非兼容

非兼容:組件選項(xiàng) data 的聲明不再接收純 JavaScript object,而是接收一個(gè) function。
非兼容:當(dāng)合并來(lái)自 mixin 或 extend 的多個(gè) data 返回值時(shí),合并操作現(xiàn)在是淺層次的而非深層次的 (只合并根級(jí)屬性)。

  • Vue2

在 2.x 中,開(kāi)發(fā)者可以通過(guò) object 或者是 function 定義 data 選項(xiàng)。

<!-- 例如: -->

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

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

<!-- 雖然這種做法對(duì)于具有共享狀態(tài)的根實(shí)例提供了一些便利,但是由于其只可能存在于根實(shí)例上,因此變得混亂。 -->
  • Vue3

在 3.x 中,data 選項(xiàng)已標(biāo)準(zhǔn)化為只接受返回 object 的 function。
使用上面的示例,代碼只可能有一種實(shí)現(xiàn):

<script>
  import { createApp } from 'vue'

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

5. emits選項(xiàng)(新增

Vue 3 現(xiàn)在提供一個(gè) emits 選項(xiàng),和現(xiàn)有的 props 選項(xiàng)類似。這個(gè)選項(xiàng)可以用來(lái)定義一個(gè)組件可以向其父組件觸發(fā)的事件
強(qiáng)烈建議使用 emits 記錄每個(gè)組件所觸發(fā)的所有事件。這尤為重要,因?yàn)槲覀円瞥?.native 修飾符。任何未在 emits 中聲明的事件監(jiān)聽(tīng)器都會(huì)被算入組件的 $attrs,并將默認(rèn)綁定到組件的根節(jié)點(diǎn)上

  • Vue2

在 Vue 2 中,你可以定義一個(gè)組件可接收的 prop,但是你無(wú)法聲明它可以觸發(fā)哪些事件:

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

和 prop 類似,現(xiàn)在可以通過(guò) emits 選項(xiàng)來(lái)定義組件可觸發(fā)的事件:

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

<!-- 該選項(xiàng)也可以接收一個(gè)對(duì)象,該對(duì)象允許開(kāi)發(fā)者定義傳入事件參數(shù)的驗(yàn)證器,和 props 定義里的驗(yàn)證器類似。 -->

6. 事件API(非兼容

on,off 和 $once 實(shí)例方法已被移除,組件實(shí)例不再實(shí)現(xiàn)事件觸發(fā)接口

  • Vue2

在 2.x 中,Vue 實(shí)例可用于觸發(fā)由事件觸發(fā)器 API 通過(guò)指令式方式添加的處理函數(shù) (on,off 和 $once)。這可以用于創(chuàng)建一個(gè)事件總線,以創(chuàng)建在整個(gè)應(yīng)用中可用的全局事件監(jiān)聽(tīng)器:

// eventBus.js
const eventBus = new Vue()
export default eventBus

// ChildComponent.vue
import eventBus from './eventBus'
export default {
  mounted() {
    // 添加 eventBus 監(jiān)聽(tīng)器
    eventBus.$on('custom-event', () => {
      console.log('Custom event triggered!')
    })
  },
  beforeDestroy() {
    // 移除 eventBus 監(jiān)聽(tīng)器
    eventBus.$off('custom-event')
  }
}

// ParentComponent.vue
import eventBus from './eventBus'
export default {
  methods: {
    callGlobalCustomEvent() {
      eventBus.$emit('custom-event') // 當(dāng) ChildComponent 已被掛載時(shí),控制臺(tái)中將顯示一條消息
    }
  }
}
  • Vue3

根組件事件

<!-- 靜態(tài)的事件監(jiān)聽(tīng)器可以通過(guò) prop 的形式傳遞給 createApp 以添加到根組件中。 -->
createApp(App, {
  // 監(jiān)聽(tīng) 'expand' 事件
  onExpand() {
    console.log('expand')
  }
})

事件總線

<!-- 事件總線模式可以被替換為使用外部的、實(shí)現(xiàn)了事件觸發(fā)器接口的庫(kù),例如 mitt 或 tiny-emitter -->
<!-- 它提供了與 Vue 2 相同的事件觸發(fā)器 API -->
// eventBus.js
import emitter from 'tiny-emitter/instance'
export default {
  $on: (...args) => emitter.on(...args),
  $once: (...args) => emitter.once(...args),
  $off: (...args) => emitter.off(...args),
  $emit: (...args) => emitter.emit(...args),
}

7. 過(guò)濾器(移除

  • Vue2

在 2.x 中,開(kāi)發(fā)者可以使用過(guò)濾器來(lái)處理通用文本格式,雖然這看起來(lái)很方便,但它需要一個(gè)自定義語(yǔ)法,打破了大括號(hào)內(nèi)的表達(dá)式“只是 JavaScript”的假設(shè),這不僅有學(xué)習(xí)成本,而且有實(shí)現(xiàn)成本

<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>
  • Vue3

在 3.x 中,過(guò)濾器已移除,且不再支持。取而代之的是,我們建議用方法調(diào)用或計(jì)算屬性來(lái)替換它們。以上面的案例為例,以下是一種實(shí)現(xiàn)方式。

<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>

8. 片段(新增

Vue 3 現(xiàn)在正式支持了多根節(jié)點(diǎn)的組件,也就是片段!

  • Vue2

在 2.x 中,由于不支持多根節(jié)點(diǎn)組件,當(dāng)其被開(kāi)發(fā)者意外地創(chuàng)建時(shí)會(huì)發(fā)出警告。結(jié)果是,為了修復(fù)這個(gè)問(wèn)題,許多組件被包裹在了一個(gè) div 中。

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>
  • Vue3

在 3.x 中,組件可以包含多個(gè)根節(jié)點(diǎn)!但是,這要求開(kāi)發(fā)者顯式定義 attribute 應(yīng)該分布在哪里。

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

9. propsData(移除

propsData 選項(xiàng)之前用于在創(chuàng)建 Vue 實(shí)例的過(guò)程中傳入 prop,現(xiàn)在它被移除了。如果想為 Vue 3 應(yīng)用的根組件傳入 prop,請(qǐng)使用 createApp 的第二個(gè)參數(shù)

  • Vue2

在 2.x 中,我們可以在創(chuàng)建 Vue 實(shí)例的時(shí)候傳入 prop:

const Comp = Vue.extend({
  props: ['username'],
  template: '<div>{{ username }}</div>'
})
new Comp({
  propsData: {
    username: 'Evan'
  }
})
  • Vue3

propsData 選項(xiàng)已經(jīng)被移除。如果你需要在實(shí)例創(chuàng)建時(shí)向根組件傳入 prop,你應(yīng)該使用 createApp 的第二個(gè)參數(shù):

const app = createApp(
  {
    props: ['username'],
    template: '<div>{{ username }}</div>'
  },
  { username: 'Evan' }
)

10. 在 prop 的默認(rèn)函數(shù)中訪問(wèn)this(非兼容

生成 prop 默認(rèn)值的工廠函數(shù)不再能訪問(wèn) this。
取而代之的是:

  • 組件接收到的原始 prop 將作為參數(shù)傳遞給默認(rèn)函數(shù);
  • inject API 可以在默認(rèn)函數(shù)中使用。
import { inject } from 'vue'
export default {
  props: {
    theme: {
      default (props) {
        // `props` 是傳遞給組件的、
        // 在任何類型/默認(rèn)強(qiáng)制轉(zhuǎn)換之前的原始值,
        // 也可以使用 `inject` 來(lái)訪問(wèn)注入的 property
        return inject('theme', 'default-theme')
      }
    }
  }
}

11. 渲染函數(shù)API(非兼容

此更改不會(huì)影響 template 用戶。
以下是更改的簡(jiǎn)要總結(jié):

  • h 現(xiàn)在是全局導(dǎo)入,而不是作為參數(shù)傳遞給渲染函數(shù)
  • 更改渲染函數(shù)參數(shù),使其在有狀態(tài)組件和函數(shù)組件的表現(xiàn)更加一致
  • VNode 現(xiàn)在有一個(gè)扁平的 prop 結(jié)構(gòu)
  • Vue2

<!-- 渲染函數(shù)參數(shù) -->

<!-- 在 2.x 中,render 函數(shù)會(huì)自動(dòng)接收 h 函數(shù) (它是 createElement 的慣用別名) 作為參數(shù): -->
export default {
  render(h) {
    return h('div')
  }
}
<!-- 渲染函數(shù)簽名更改 -->

<!-- 在 2.x 中,render 函數(shù)自動(dòng)接收參數(shù),如 h 函數(shù)。 -->
export default {
  render(h) {
    return h('div')
  }
}
<!-- VNode Prop 格式化 -->

<!-- 在 2.x 中,domProps 包含 VNode prop 中的嵌套列表: -->
{
  staticClass: 'button',
  class: { 'is-outlined': isOutlined },
  staticStyle: { color: '#34495E' },
  style: { backgroundColor: buttonColor },
  attrs: { id: 'submit' },
  domProps: { innerHTML: '' },
  on: { click: submitForm },
  key: 'submit-button'
}
<!-- 注冊(cè)組件 -->

<!-- 在 2.x 中,注冊(cè)一個(gè)組件后,把組件名作為字符串傳遞給渲染函數(shù)的第一個(gè)參數(shù),它可以正常地工作: -->
Vue.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Clicked {{ count }} times.
    </button>
  `
})
export default {
  render(h) {
    return h('button-counter')
  }
}
  • Vue3

<!-- 渲染函數(shù)參數(shù) -->

<!-- 在 3.x 中,h 函數(shù)現(xiàn)在是全局導(dǎo)入的,而不是作為參數(shù)自動(dòng)傳遞。 -->
import { h } from 'vue'
export default {
  render() {
    return h('div')
  }
}
<!-- 渲染函數(shù)簽名更改 -->

<!-- 在 3.x 中,由于 render 函數(shù)不再接收任何參數(shù),它將主要在 setup() 函數(shù)內(nèi)部使用。這還有一個(gè)好處:可以訪問(wèn)在作用域中聲明的響應(yīng)式狀態(tài)和函數(shù),以及傳遞給 setup() 的參數(shù)。 -->
import { h, reactive } from 'vue'
export default {
  setup(props, { slots, attrs, emit }) {
    const state = reactive({
      count: 0
    })

    function increment() {
      state.count++
    }

    // 返回渲染函數(shù)
    return () =>
      h(
        'div',
        {
          onClick: increment
        },
        state.count
      )
  }
}
<!-- VNode Prop 格式化 -->

<!-- 在 3.x 中,整個(gè) VNode prop 的結(jié)構(gòu)都是扁平的。使用上面的例子,來(lái)看看它現(xiàn)在的樣子。 -->
{
  class: ['button', { 'is-outlined': isOutlined }],
  style: [{ color: '#34495E' }, { backgroundColor: buttonColor }],
  id: 'submit',
  innerHTML: '',
  onClick: submitForm,
  key: 'submit-button'
}
<!-- 注冊(cè)組件 -->

<!-- 在 3.x 中,由于 VNode 是上下文無(wú)關(guān)的,不能再用字符串 ID 隱式查找已注冊(cè)組件。取而代之的是,需要使用一個(gè)導(dǎo)入的 resolveComponent 方法: -->
import { h, resolveComponent } from 'vue'
export default {
  setup() {
    const ButtonCounter = resolveComponent('button-counter')
    return () => h(ButtonCounter)
  }
}

12. 插槽統(tǒng)一(非兼容

此更改統(tǒng)一了 3.x 中的普通插槽和作用域插槽。
以下是變化的變更總結(jié):

  • this.$slots 現(xiàn)在將插槽作為函數(shù)公開(kāi)
  • 非兼容:移除 this.$scopedSlots
  • Vue2

<!-- 當(dāng)使用渲染函數(shù),即 h 時(shí),2.x 曾經(jīng)在內(nèi)容節(jié)點(diǎn)上定義 slot 數(shù)據(jù) property。 -->
// 2.x 語(yǔ)法

h(LayoutComponent, [
  h('div', { slot: 'header' }, this.header),
  h('div', { slot: 'content' }, this.content)
])

<!-- 此外,可以使用以下語(yǔ)法引用作用域插槽: -->
// 2.x 語(yǔ)法

this.$scopedSlots.header
  • Vue3

<!-- 在 3.x 中,插槽以對(duì)象的形式定義為當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn): -->
// 3.x Syntax

h(LayoutComponent, {}, {
  header: () => h('div', this.header),
  content: () => h('div', this.content)
})

<!-- 當(dāng)你需要以編程方式引用作用域插槽時(shí),它們現(xiàn)在被統(tǒng)一到 $slots 選項(xiàng)中了。 -->
// 2.x 語(yǔ)法

this.$scopedSlots.header

// 3.x 語(yǔ)法

this.$slots.header()

13. 過(guò)渡的class名更改(非兼容

過(guò)渡類名 v-enter 修改為 v-enter-from、過(guò)渡類名 v-leave 修改為 v-leave-from。

  • Vue2

<!-- 在 v2.1.8 版本之前,每個(gè)過(guò)渡方向都有兩個(gè)過(guò)渡類:初始狀態(tài)與激活狀態(tài)。 -->
<!-- 在 v2.1.8 版本中,引入了 v-enter-to 來(lái)定義 enter 或 leave 變換之間的過(guò)渡動(dòng)畫插幀。然而,為了向下兼容,并沒(méi)有變動(dòng) v-enter 類名: -->

.v-enter,
.v-leave-to {
  opacity: 0;
}

.v-leave,
.v-enter-to {
  opacity: 1;
}

<!-- 這樣做會(huì)帶來(lái)很多困惑,類似 enter 和 leave 含義過(guò)于寬泛,并且沒(méi)有遵循類名鉤子的命名約定。 -->
  • Vue3

<!-- 為了更加明確易讀,我們現(xiàn)在將這些初始狀態(tài)重命名為: -->

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

.v-leave-from,
.v-enter-to {
  opacity: 1;
}

<!-- 現(xiàn)在,這些狀態(tài)之間的區(qū)別就清晰多了。 -->

14. Transition 作為根節(jié)點(diǎn)(非兼容

當(dāng)使用 <transition> 作為根結(jié)點(diǎn)的組件從外部被切換時(shí)將不再觸發(fā)過(guò)渡效果

  • Vue2

<!-- 在 Vue 2 中,通過(guò)使用 <transition> 作為一個(gè)組件的根節(jié)點(diǎn),過(guò)渡效果存在從組件外部觸發(fā)的可能性: -->
<!-- 模態(tài)組件 -->
<template>
  <transition>
    <div class="modal"><slot/></div>
  </transition>
</template>
<!-- 用法 -->
<modal v-if="showModal">hello</modal>
<!-- 切換 showModal 的值將會(huì)在模態(tài)組件內(nèi)部觸發(fā)一個(gè)過(guò)渡效果。
這是無(wú)意為之的,并不是設(shè)計(jì)效果。一個(gè) <transition> 原本是希望被其子元素觸發(fā)的,而不是 <transition> 自身。
這個(gè)怪異的現(xiàn)象現(xiàn)在被移除了。 -->
  • Vue3

<!-- 換做向組件傳遞一個(gè) prop 就可以達(dá)到類似的效果: -->

<template>
  <transition>
    <div v-if="show" class="modal"><slot/></div>
  </transition>
</template>
<script>
export default {
  props: ['show']
}
</script>
<!-- 用法 -->
<modal :show="showModal">hello</modal>

15. Transition Group 根元素(非兼容

<transition-group> 不再默認(rèn)渲染根元素,但仍然可以用 tag attribute 創(chuàng)建根元素

  • Vue2

<!-- 在 Vue 2 中,<transition-group> 像其它自定義組件一樣,需要一個(gè)根元素。默認(rèn)的根元素是一個(gè) <span>,但可以通過(guò) tag attribute 定制。 -->

<transition-group tag="ul">
  <li v-for="item in items" :key="item">
    {{ item }}
  </li>
</transition-group>
  • Vue3

<!-- 在 Vue 3 中,我們有了片段的支持,因此組件不再需要根節(jié)點(diǎn)。所以,<transition-group> 不再默認(rèn)渲染根節(jié)點(diǎn)。 -->
<!-- 如果像上面的示例一樣,已經(jīng)在 Vue 2 代碼中定義了 tag attribute,那么一切都會(huì)和之前一樣 -->
<!-- 如果沒(méi)有定義 tag attribute,而且樣式或其它行為依賴于 <span> 根元素的存在才能正常工作,那么只需將 tag="span" 添加到 <transition-group>: -->

<transition-group tag="span">
  <!-- -->
</transition-group>

16. 移除v-on.native修飾符(非兼容

v-on 的 .native 修飾符已被移除

  • Vue2

<!-- 默認(rèn)情況下,傳遞給帶有 v-on 的組件的事件監(jiān)聽(tīng)器只能通過(guò) this.$emit 觸發(fā)。要將原生 DOM 監(jiān)聽(tīng)器添加到子組件的根元素中,可以使用 .native 修飾符: -->

<my-component
  v-on:close="handleComponentEvent"
  v-on:click.native="handleNativeClickEvent"
/>
  • Vue3

<!-- v-on 的 .native 修飾符已被移除。同時(shí),新增的 emits 選項(xiàng)允許子組件定義真正會(huì)被觸發(fā)的事件。 -->

<!-- 因此,對(duì)于子組件中未被定義為組件觸發(fā)的所有事件監(jiān)聽(tīng)器,Vue 現(xiàn)在將把它們作為原生事件監(jiān)聽(tīng)器添加到子組件的根元素中 (除非在子組件的選項(xiàng)中設(shè)置了 inheritAttrs: false)。 -->

<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>

// MyComponent.vue

<script>
  export default {
    emits: ['close']
  }
</script>

17. v-model(非兼容

以下是對(duì)變化的總體概述:

  • 非兼容:用于自定義組件時(shí),v-model prop 和事件默認(rèn)名稱已更改:
    • prop:value -> modelValue;
    • 事件:input -> update:modelValue;
  • 非兼容:v-bind 的 .sync 修飾符和組件的 model 選項(xiàng)已移除,可在 v-model 上加一個(gè)參數(shù)代替;
  • 新增:現(xiàn)在可以在同一個(gè)組件上使用多個(gè) v-model 綁定;
  • 新增:現(xiàn)在可以自定義 v-model 修飾符。

在 Vue 2.0 發(fā)布后,開(kāi)發(fā)者使用 v-model 指令時(shí)必須使用名為 value 的 prop。如果開(kāi)發(fā)者出于不同的目的需要使用其他的 prop,他們就不得不使用 v-bind.sync。此外,由于v-model 和 value 之間的這種硬編碼關(guān)系的原因,產(chǎn)生了如何處理原生元素和自定義元素的問(wèn)題。
在 Vue 2.2 中,我們引入了 model 組件選項(xiàng),允許組件自定義用于 v-model 的 prop 和事件。但是,這仍然只允許在組件上使用一個(gè) v-model。
在 Vue 3 中,雙向數(shù)據(jù)綁定的 API 已經(jīng)標(biāo)準(zhǔn)化,以減少開(kāi)發(fā)者在使用 v-model 指令時(shí)的混淆,并且更加靈活。

  • Vue2

在 2.x 中,在組件上使用 v-model 相當(dāng)于綁定 value prop 并觸發(fā) input 事件:

<ChildComponent v-model="pageTitle" />

<!-- 是以下的簡(jiǎn)寫: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

如果想要更改 prop 或事件名稱,則需要在 ChildComponent 組件中添加 model 選項(xiàng):

<!-- ParentComponent.vue -->

<ChildComponent v-model="pageTitle" />
// ChildComponent.vue

export default {
  model: {
    prop: 'title',
    event: 'change'
  },
  props: {
    // 這將允許 `value` 屬性用于其他用途
    value: String,
    // 使用 `title` 代替 `value` 作為 model 的 prop
    title: {
      type: String,
      default: 'Default title'
    }
  }
}

所以,在這個(gè)例子中 v-model 是以下的簡(jiǎn)寫:

<ChildComponent :title="pageTitle" @change="pageTitle = $event" />

使用 v-bind.sync
在某些情況下,我們可能需要對(duì)某一個(gè) prop 進(jìn)行“雙向綁定”(除了前面用 v-model 綁定 prop 的情況)。為此,我們建議使用 update:myPropName 拋出事件。例如,對(duì)于在上一個(gè)示例中帶有 title prop 的 ChildComponent,我們可以通過(guò)下面的方式將分配新 value 的意圖傳達(dá)給父級(jí):

this.$emit('update:title', newValue)

然后父組件可以在需要時(shí)監(jiān)聽(tīng)該事件,并更新本地的 data property。例如:

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

為了方便起見(jiàn),我們可以使用 .sync 修飾符來(lái)縮寫,如下所示:

<ChildComponent :title.sync="pageTitle" />
  • Vue3

在 3.x 中,自定義組件上的 v-model 相當(dāng)于傳遞了 modelValue prop 并接收拋出的 update:modelValue 事件:

<ChildComponent v-model="pageTitle" />

<!-- 是以下的簡(jiǎn)寫: -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>

v-model 參數(shù)
若需要更改 model 的名稱,現(xiàn)在我們可以為 v-model 傳遞一個(gè)參數(shù),以作為組件內(nèi) model 選項(xiàng)的替代:

<ChildComponent v-model:title="pageTitle" />

<!-- 是以下的簡(jiǎn)寫: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

這也可以作為 .sync 修飾符的替代,而且允許我們?cè)谧远x組件上使用多個(gè) v-model。

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

<!-- 是以下的簡(jiǎn)寫: -->

<ChildComponent
  :title="pageTitle"
  @update:title="pageTitle = $event"
  :content="pageContent"
  @update:content="pageContent = $event"
/>

v-model 修飾符
除了像 .trim 這樣的 2.x 硬編碼的 v-model 修飾符外,現(xiàn)在 3.x 還支持自定義修飾符:

<ChildComponent v-model.capitalize="pageTitle" />

18. v-if 與 v-for 的優(yōu)先級(jí)對(duì)比(非兼容

非兼容:兩者作用于同一個(gè)元素上時(shí),v-if 會(huì)擁有比 v-for 更高的優(yōu)先級(jí)。
Vue.js 中使用最多的兩個(gè)指令就是 v-if 和 v-for,因此開(kāi)發(fā)者們可能會(huì)想要同時(shí)使用它們。雖然不建議這樣做,但有時(shí)確實(shí)是必須的,于是我們想提供有關(guān)其工作方式的指南。

  • Vue2

2.x 版本中在一個(gè)元素上同時(shí)使用 v-if 和 v-for 時(shí),v-for 會(huì)優(yōu)先作用

  • Vue3

3.x 版本中 v-if 總是優(yōu)先于 v-for 生效

19. v-bind 合并行為(非兼容

不兼容:v-bind 的綁定順序會(huì)影響渲染結(jié)果
在一個(gè)元素上動(dòng)態(tài)綁定 attribute 時(shí),同時(shí)使用 v-bind="object" 語(yǔ)法和獨(dú)立 attribute 是常見(jiàn)的場(chǎng)景。然而,這就引出了關(guān)于合并的優(yōu)先級(jí)的問(wèn)題

  • Vue2

在 2.x 中,如果一個(gè)元素同時(shí)定義了 v-bind="object" 和一個(gè)相同的獨(dú)立 attribute,那么這個(gè)獨(dú)立 attribute 總是會(huì)覆蓋 object 中的綁定。

<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 結(jié)果 -->
<div id="red"></div>
  • Vue3

在 3.x 中,如果一個(gè)元素同時(shí)定義了 v-bind="object" 和一個(gè)相同的獨(dú)立 attribute,那么綁定的聲明順序?qū)Q定它們?nèi)绾伪缓喜?。換句話說(shuō),相對(duì)于假設(shè)開(kāi)發(fā)者總是希望獨(dú)立 attribute 覆蓋 object 中定義的內(nèi)容,現(xiàn)在開(kāi)發(fā)者能夠?qū)ψ约核M暮喜⑿袨樽龈玫目刂啤?/p>

<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 結(jié)果 -->
<div id="blue"></div>

<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 結(jié)果 -->
<div id="red"></div>

20. VNode 生命周期事件(非兼容

在 Vue 2 中,我們可以通過(guò)事件來(lái)監(jiān)聽(tīng)組件生命周期中的關(guān)鍵階段。這些事件名都是以 hook: 前綴開(kāi)頭,并跟隨相應(yīng)的生命周期鉤子的名字。
在 Vue 3 中,這個(gè)前綴已被更改為 vnode-。額外地,這些事件現(xiàn)在也可用于 HTML 元素,和在組件上的用法一樣

  • Vue2

在 Vue 2 中,這些事件名和相應(yīng)的生命周期鉤子一致,并帶有 hook: 前綴:

<template>
  <child-component @hook:updated="onUpdated">
</template>
  • Vue3

在 Vue 3 中,事件名附帶的是 vnode- 前綴:

<template>
  <child-component @vnode-updated="onUpdated">
</template>

21. 偵聽(tīng)數(shù)組(非兼容

非兼容: 當(dāng)偵聽(tīng)一個(gè)數(shù)組時(shí),只有當(dāng)數(shù)組被替換時(shí)才會(huì)觸發(fā)回調(diào)。如果你需要在數(shù)組被改變時(shí)觸發(fā)回調(diào),必須指定 deep 選項(xiàng)

  • Vue2

  • Vue3

當(dāng)使用 watch 選項(xiàng)偵聽(tīng)數(shù)組時(shí),只有在數(shù)組被替換時(shí)才會(huì)觸發(fā)回調(diào)。換句話說(shuō),在數(shù)組被改變時(shí)偵聽(tīng)回調(diào)將不再被觸發(fā)。要想在數(shù)組被改變時(shí)觸發(fā)偵聽(tīng)回調(diào),必須指定 deep 選項(xiàng)。

watch: {
  bookList: {
    handler(val, oldVal) {
      console.log('book list changed')
    },
    deep: true
  },
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容