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]) => {
  /* ... */
})
复制代码

后面再更,晚安

相关文章
相关标签/搜索