Vue3.0文檔 VS Vue2.x文檔 有哪些不一樣

尤大宣佈 Vue3.0 已經進入候選階段【賀電】!!!如今也能夠提早試用啦,能夠經過 Vite 直接啓動一個項目。另外 Vue3.0 的文檔也已經上線,感興趣的小夥伴能夠先睹爲快。如下是我對比 Vue2.x 版本文檔得出的一部分差別,歡迎補充。vue

建立 Vue實例 的方法:

Vue2.x 中,經過 new Vue 建立 Vue 的實例,而且經過傳入 el參數 進行掛載 DOMnode

<!-- Vue2.x 建立實例 -->
var vm = new Vue({
  // 選項
})

<!-- Vue2.x 掛載DOM -->
var vm = new Vue({
  el: '#app',
  data: {a:1}
})
複製代碼

Vue3.0 中,經過 createApp 方法建立 Vue 的實例,建立實例後能夠把這個容器傳給 mount 方法來掛載react

<!-- Vue3.0 建立實例 -->
Vue.createApp(/* options */)

<!-- Vue3.0 掛載DOM -->
Vue.createApp(/* options */).mount('#app')
複製代碼

生命週期

生命週期沒有太大的改變,因爲建立實例的方法改變了,所以有一些細微的差異。git

值得注意的是:在 Vue2.x 中,銷燬實例的兩個鉤子是 beforeDestory 以及 destoryed,而在 Vue3.0 中這兩個鉤子的名字變動爲 beforeUnmountunmountedes6

Vue2.x 生命週期 github

Vue3.0 生命週期api

動態組件

Vue2.x 和 Vue3.0 都還是採用經過給 Vue 的 元素加一個特殊的 is 屬性來實現數組

<!-- 組件會在 `currentTabComponent` 改變時改變 -->
<component v-bind:is="currentTabComponent"></component>
複製代碼

可是對於解析 DOM 模板,諸如<ul><table>等限制內部元素的特殊狀況,相比 Vue2.x 中是經過綁定 is 屬性, Vue3.0 提供的是 v-is 指令bash

<!-- Vue2.x 使用 is 屬性 -->
<table>
  <tr is="blog-post-row"></tr>
</table>
複製代碼
<!-- Vue3.0 使用 v-is 指令 -->
<table>
  <tr v-is="'blog-post-row'"></tr>
</table>
複製代碼

自定義事件

Vue2.x 和 Vue3.0 都還是經過$emit('myEvent')觸發事件,經過v-on:myEvent來監聽事件,不一樣的是,Vue3.0 在組件中提供了 emits 屬性來定義事件app

<!-- Vue3.0 自定義事件 -->
app.component('custom-form', {
  emits: ['in-focus', 'submit']
})
複製代碼

甚至你能夠在自定義事件中添加校驗,這時須要把emits設置爲對象,而且爲事件名分配一個函數,該函數接收傳遞給 $emit 調用的參數,並返回一個布爾值以指示事件是否有效

<!-- Vue3.0 爲自定義事件添加校驗 -->
app.component('custom-form', {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})
複製代碼

自定義組件的 v-model

在 Vue2.x 中, v-model 默認會利用 value 做爲 prop 名以及 input 做爲觸發的 event 名。對於特殊的場景,也能夠經過 model 選項來指定 prop 名和 event 名(注意這時仍需在 props 裏聲明這個 prop)

<!-- Vue2.0 自定義 v-model -->
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})
複製代碼

請注意,在 Vue3.0 中, v-model 默認會利用 modelValue 做爲 prop 名以及 update:modelValue 做爲觸發的 event 名。

支持給每一個 v-model 傳入一個參數,這樣就能夠在一個組件上同時使用多個 v-model

<!-- Vue3.0 自定義 v-model 而且傳入參數 -->
<my-component v-model:foo="bar" v-model:name="userName"></my-component>
複製代碼

甚至還能夠爲 v-model 設置自定義修飾符,默認是經過在props中定義 modelModifiers 對象來接受修飾符,所以你能夠經過修飾符來設置你想要的不一樣的事件觸發機制

<!-- Vue3.0 自定義修飾符默認接收方式 -->
<div id="app">
  <my-component v-model.capitalize="myText"></my-component>
  {{ myText }}
</div>

const app = Vue.createApp({
  data() {
    return {
      myText: ''
    }
  }
})

app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  },
  template: `<input
    type="text"
    v-bind:value="modelValue"
    v-on:input="emitValue">`
})

app.mount('#app')
複製代碼

固然,對於傳入了參數的 v-model ,則須要在props裏面配置arg + "Modifiers"來接收這個帶參數的v-model的修飾符

<!-- Vue3.0 自定義參數的自定義修飾符 -->
<my-component v-model:foo.capitalize="bar"></my-component>

app.component('my-component', {
  props: ['foo', 'fooModifiers'],
  template: `
    <input type="text" 
      v-bind:value="foo"
      v-on:input="$emit('update:foo', $event.target.value)">
  `,
  created() {
    console.log(this.fooModifiers) // { capitalize: true }
  }
})
複製代碼

混入 (mixin)

Vue2.x 混入的方式 經過 Vue.extend({mixins: [myMixin]}) 定義一個使用混入對象的組件

// 定義一個混入對象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混入對象的組件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"
複製代碼

而 Vue3.0 則和建立一個實例類似,經過 Vue.createApp({mixins: [myMixin]}) 定義一個使用混入對象的組件

// 定義一個混入對象
const myMixin = {
  created() {
    this.hello()
  },
  methods: {
    hello() {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混入對象的組件
const app = Vue.createApp({
  mixins: [myMixin]
})

app.mount('#mixins-basic') // => "hello from mixin!"
複製代碼

自定義指令

Vue2.x 的指令定義對象包含 5 個鉤子:

  • bind:只調用一次,指令第一次綁定到元素時調用。在這裏能夠進行一次性的初始化設置。
  • inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不必定已被插入文檔中)。
  • update:所在組件的 VNode 更新時調用,可是可能發生在其子 VNode 更新以前。指令的值可能發生了改變,也可能沒有。可是你能夠經過比較更新先後的值來忽略沒必要要的模板更新。
  • componentUpdated:指令所在組件的 VNode 及其子 VNode 所有更新後調用。
  • unbind:只調用一次,指令與元素解綁時調用。

Vue3.0 的指令對象包含 6 個鉤子:

  • beforeMount:指令第一次綁定到元素時調用。在這裏能夠進行一次性的初始化設置。
  • mounted:當被綁定元素插入父節點時調用。
  • beforeUpdate:在更新所在組件的VNode以前調用。
  • updated:指令所在組件的 VNode 及其子 VNode 所有更新後調用。
  • beforeUnmount:在綁定元素的父組件卸載以前調用。(對比 Vue2.x 新增
  • unmounted:只調用一次,指令與元素解綁且父組件已卸載時調用。

在 Vue3.0 中,因爲對片斷的支持,組件可能會存在多個根節點,這時使用自定義指令可能會產生問題。自定義指令對象包含的鉤子會被包裝並做爲 Vnode 生命週期鉤子注入到 Vnode 的數據中。

<!-- Vue3.0 自定義指令對象包含的鉤子包裝後 -->
{
  onVnodeMounted(vnode) {
    // call vDemo.mounted(...)
  }
}
複製代碼

當在組件中使用自定義指令時,這些onVnodeXXX鉤子將做爲無關屬性直接傳遞給組件,能夠像這樣在模板中直接掛接到元素的生命週期中(這裏不太明白,以後試驗過再來更新)

<div @vnodeMounted="myHook" />
複製代碼

當子組件在內部元素上使用 v-bind="$attrs" 時,它也將應用它上面的任何自定義指令。

內置的傳送組件 Teleport

Vue3.0 內置<teleport>的組件能夠傳送一段模板到其餘位置,

<!-- Vue3.0 <teleport>傳送組件 -->
<body>
  <div id="app" class="demo">
    <h3>Move the #content with the portal component</h3>
    <div>
      <teleport to="#endofbody">
        <p id="content">
          This should be moved to #endofbody.
        </p>
      </teleport>
      <span>This content should be nested</span>
    </div>
  </div>
  <div id="endofbody"></div>
</body>
複製代碼

若是<teleport>包含Vue組件,它將仍然是<teleport>父組件的邏輯子組件,也就是說,即便在不一樣的地方呈現子組件,它仍將是父組件的子組件,並將從父組件接收 prop

使用多個傳送組件 會採用累加的邏輯,像這樣

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- 結果 B 渲染在 A 後面 -->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
複製代碼

渲染函數

Vue2.x 的渲染函數的參數是createElement

Vue3.0 的渲染函數的參數createVNode(這個名字更接近它實際的意義,返回虛擬 DOM)

一樣將h做爲別名,在 Vue3.0 中能夠直接經過 Vue.h 獲取

const app = Vue.createApp({})

app.component('anchored-heading', {
  render() {
    const { h } = Vue

    return h(
      'h' + this.level, // tag name
      {}, // props/attributes
      this.$slots.default() // array of children
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
複製代碼

事件 & 按鍵修飾符 Vue2.x 對於 .passive、.capture 和 .once 這些事件修飾符,提供了相應的前綴能夠用於 on:、

修飾符 前綴
.passive &
.capture !
.once ~
.capture.once
.once.capture ~!
<!-- Vue2.x 對修飾符使用前綴 -->
on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}
複製代碼

而 Vue3.0 則是使用對象語法

<!-- Vue3.0 對修飾符使用對象語法 -->
render() {
  return Vue.h('input', {
    onClick: {
      handler: this.doThisInCapturingMode,
      capture: true
    },
    onKeyUp: {
      handler: this.doThisOnce,
      once: true
    },
    onMouseOver: {
      handler: this.doThisOnceInCapturingMode,
      once: true,
      capture: true
    },
  })
}
複製代碼

插件

開發插件 Vue3.0 仍須要暴露一個 install方法,傳入兩個參數,第一個是經過Vue.createApp構造的對象,第二個是用戶傳入的options

// plugins/i18n.js
export default {
  install: (app, options) => {
  // Plugin code goes here
  }
}
複製代碼

插件中經過暴露出的app.config.globalProperties屬性註冊全局方法

// plugins/i18n.js
<!-- 經過 app.config.globalProperties 全局注入 translate 方法 -->
export default {
  install: (app, options) => {
    app.config.globalProperties.$translate = (key) => {
      return key.split('.')
        .reduce((o, i) => { if (o) return o[i] }, i18n)
    }
  }
}
複製代碼

還能夠經過inject來爲用戶提供方法或屬性

// plugins/i18n.js
<!-- 這樣組件裏就能夠經過 inject 訪問 i18n 和 options -->
export default {
  install: (app, options) => {
    app.config.globalProperties.$translate = (key) => {
      return key.split('.')
        .reduce((o, i) => { if (o) return o[i] }, i18n)
    }

    app.provide('i18n', options)
  }
}

將['i18n']注入組件並訪問 -->

複製代碼

使用插件 仍然是經過 use() 方法,能夠接受兩個參數,第一個參數是要使用的插件,第二個參數可選,會傳入到插件中去。

import { createApp } from 'vue'
import App from './App.vue'
import i18nPlugin from './plugins/i18n'

const app = createApp(App)
const i18nStrings = {
  greetings: {
    hi: 'Hallo!'
  }
}

app.use(i18nPlugin, i18nStrings)
app.mount('#app')
複製代碼

👆基本用法👇深刻原理

響應式

衆所周知,Vue2.x 是經過 Object.defineProperty結合訂閱/發佈模式實現的。

給 Vue 實例傳入 data 時,Vue 將遍歷data 對象全部的 property,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter,在屬性被訪問和修改時追蹤到依賴。每一個組件實例都對應一個 watcher 實例,它會在組件渲染的過程當中把「接觸」過的數據屬性記錄爲依賴。當依賴項的 setter 觸發時,會通知 watcher,從而使它關聯的組件從新渲染。

、 而 Vue3.0 則是採用 ES6 的 Proxy 代理來攔截對目標對象的訪問。 給 Vue 實例傳入 data 時,Vue 會將其轉換爲 Proxy。它能使 Vue 在訪問或修改屬性時執行 依賴追蹤以及 更改通知。每一個屬性都被視爲一個依賴項。 在初次渲染後,組件將追蹤依賴(也就是它在渲染時訪問過的屬性)。換句話說,組件成爲這些屬性的 訂閱者。當代理攔截到 set 操做時,該屬性將通知其訂閱者從新渲染。

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop, receiver) {
    track(target, prop) // Track the function that changes it 依賴項跟蹤
    return Reflect.get(...arguments)
  },
  set(target, key, value, receiver) {
    trigger(target, key) // Trigger the function so it can update the final value 更改通知
    return Reflect.set(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// intercepted!
// tacos
複製代碼

值得注意的是,原始對象與代理對象是不相等

const obj = {}
const wrapped = new Proxy(obj, handlers)
console.log(obj === wrapped) // false
複製代碼

響應式基礎原理 Reactivity Fundamentals

聲明響應式狀態reactive

能夠經過reactive方法建立一個響應式狀態(也就是 Vue2.x 中的 Vue.observable() ),這個方法會返回一個響應式對象。模板編譯的過程當中 render 方法用的就是這些響應式屬性。

import { reactive } from 'vue'

// reactive state
const state = reactive({
  count: 0
})
複製代碼

還能夠建立只讀的響應式狀態

const original = reactive({ count: 0 })

const copy = readonly(original)

// mutating original will trigger watchers relying on the copy
original.count++

// mutating the copy will fail and result in a warning
copy.count++ // warning: "Set operation on key 'count' failed: target is readonly."
複製代碼

建立獨立的響應式屬性 refs

能夠經過ref方法建立一個響應式的獨立的原始值,一樣也會返回一個響應式的可變對象,這個對象只包含一個value屬性

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
複製代碼

當一個ref做爲在渲染上下文中返回的屬性且在模板中被訪問時,會自動展開內部的value,所以無需再使用xx.value的方式來訪問,這樣就像訪問一個普通屬性同樣。要注意,自動解析 value 只發生在響應式的對象裏,從數組或集合中訪問時仍須要.value

<template>
  <div>
    <span>{{ count }}</span>
    <button @click="count ++">Increment count</button>
  </div>
</template>

<script>
  import { ref } from 'vue'
  export default {
    setup() {
      const count = ref(0)
      return {
        count
      }
    }
  }
</script>
複製代碼

另外若是將一個新的ref賦值給現有的屬性,那將替換掉舊的ref

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
複製代碼

計算和監聽

computed方法

能夠經過computed方法直接建立一個計算值,接收一個getter函數做爲參數而且返回一個不可變的響應式對象。

const count = ref(1)
const plusOne = computed(() => count.value++)

console.log(plusOne.value) // 2

plusOne.value++ // error
複製代碼

或者能夠傳入一個帶有gettersetter方法的對象來建立一個可修改的響應式對象

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
複製代碼

watchEffect方法

能夠經過watchEffect方法,它會當即運行傳入的函數,而且跟蹤這個函數的依賴項,當依賴項更新時,當即再次執行這個函數。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)
複製代碼

在組件執行setup()或生命週期鉤子時會被調用,而且在組件卸載時自動中止。或者能夠經過執行watchEffect方法返回的中止操做來中止觀察。

const stop = watchEffect(() => {
  /* ... */
})
Vue3
// later
stop()
複製代碼

Side Effect Invalidation 附帶的失效方法

有時watchEffect中執行的方法多是異步的接收一個onInvalidate函數做爲參數來註冊一個失效時的回調方法,能夠執行一些清理操做,它將會在watchEffect從新執行時或者watchEffect終止(即在setup()生命週期鉤子中使用了watchEffect的組件卸載時)時執行。

watchEffect(onInvalidate => {
  const token = performAsyncOperation(id.value)
  onInvalidate(() => {
    // id has changed or watcher is stopped.
    // invalidate previously pending async operation
    token.cancel()
  })
})
複製代碼

注意setup()將在組件掛載前調用,所以若是想要在watchEffect中使用 DOM (或者組件),請在掛載的鉤子中聲明watchEffect

onMounted(() => {
  watchEffect(() => {
    // access the DOM or template refs
  })
})
複製代碼

還能夠爲watchEffect傳入額外的對象做爲參數。 好比經過設置flush來設置監聽方法是異步執行仍是在組件更新前執行

// fire synchronously
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'sync'
  }
)

// fire before component updates
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'pre'
  }
)
複製代碼

onTrackonTrigger參數能夠用來調試watchEffect的方法

watchEffect(
  () => {
    /* side effect */
  },
  {
    onTrigger(e) {
      debugger
    }
  }
)
複製代碼

watch相比watchEffect的區別 watch是惰性的,只有在被監聽的屬性更新時纔會調用回調,而且能夠訪問被監聽屬性的當前值和原始值。

// watching a getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})
複製代碼
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
複製代碼

後面再更,晚安

相關文章
相關標籤/搜索