vue2 升级 vue3

Scroll Down

v-for array refs

2.0


返回一个数组、效率低下


3.0


不再自动生成数组,绑定一个函数,使得操作更灵活。


with options api

<div v-for="item in list" :ref="setItemRef"></div>

<script>
export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      this.itemRefs.push(el)
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}
 </script>

with composition api

import { ref, onBeforeUpdate, onUpdated } from 'vue'

export default {
  setup() {
    let itemRefs = []
    const setItemRef = el => {
      itemRefs.push(el)
    }
    onBeforeUpdate(() => {
      itemRefs = []
    })
    onUpdated(() => {
      console.log(itemRefs)
    })
    return {
      itemRefs,
      setItemRef
    }
  }
}

itemRefs 不一定是数组,也可以是对象。
itemRefs 也可以由reactive构造,也可以被监听。

Async Components

  • New defineAsyncComponent helper method that explicitly defines async components
  • component option renamed to loader
  • Loader function does not inherently receive resolve and reject arguments and must return a Promise


2.0

const asyncPage = () => import('./NextPage.vue')
const asyncPage = {
  component: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
}

3.0

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

// Async component without options
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))

// Async component with options
const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})
// 2.x version
const oldAsyncComponent = (resolve, reject) => {
  /* ... */
}

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

Attribute Coercion Behavior


属性强制行为

see attribute-coercion

Custom Directives

  • API has been renamed to better align with component lifecycle
  • Custom directives will be controlled by child component via v-bind="$attrs"

  • API已重命名以更好地与组件生命周期保持一致
  • 自定义指令将通过v-bind ="$attrs"由子组件控制

2.0
bind - 将指令绑定到元素后执行,仅执行一次
inserted - 插入父Dom中执行
update - 元素更新,但子元素未更新时执行
componentUpdated - 组件和子组件更新后执行
unbind - 删除指令后执行,仅执行一次

<p v-highlight="yellow">Highlight this text bright yellow</p>
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value
  }
})

3.0
bind → beforeMount
inserted → mounted
beforeUpdate - 元素更新之前执行
update 被移除,使用updated
componentUpdated → updated
beforeUnmount 卸载前执行
unbind -> unmounted

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

实现上面指令:

const app = Vue.createApp({})

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

Custom Elements Interop


自定义元素


see custom-elements

Data Option


In 3.x, the data option has been standardized to only accept a function that returns an object.

Mixin Merge Behavior Change

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}

const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}

2.0

{
  user: {
    id: 2,
    name: 'Jack'
  }
}

3.0

{
  user: {
    id: 2
  }
}

Events API

3.0
remove $on$off and $once
use mitt or tiny-emitter

Filters


3.0
remove Filters
use computed properties or methods

Fragments


3.0
support muti-root node components

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

more info, see Non-Prop Attributes

Functional Components


see render-function-apirender-function

Global API


see a-new-global-api-createapp

Global API Treeshaking


2.0

import Vue from 'vue'

Vue.nextTick(() => {
  // something DOM-related
})

3.0

import { nextTick } from 'vue'

nextTick(() => {
  // something DOM-related
})

Inline Template Attribute


2.0

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>

3.0
no longer be support.

  1. use script tag
<script type="text/html" id="my-comp-template">
  <div>{{ hello }}</div>
</script>
  1. default slot
<!-- 2.x Syntax -->
<my-comp inline-template :msg="parentMsg">
  {{ msg }} {{ childState }}
</my-comp>

<!-- Default Slot Version -->
<my-comp v-slot="{ childState }">
  {{ parentMsg }} {{ childState }}
</my-comp>
<!--
  in child template, render default slot while passing
  in necessary private state of child.
-->
<template>
  <slot :childState="childState" />
</template>

key attribute

3.0
NEW: keys are no longer necessary on v-if/v-else/v-else-if branches, since Vue now automatically generates unique keys.
BREAKING: If you manually provide keys, then each branch must use a unique key. You can no longer intentionally use the same key to force branch reuse.
BREAKING:  key should be placed on the  tag (rather than on its children).

KeyCode Modifiers

2.0

<!-- keyCode version -->
<input v-on:keyup.13="submit" />

<!-- alias version -->
<input v-on:keyup.enter="submit" />
Vue.config.keyCodes = {
  f1: 112
}
<!-- keyCode version -->
<input v-on:keyup.112="showHelpText" />

<!-- custom alias version -->
<input v-on:keyup.f1="showHelpText" />

3.0

  • BREAKING: Using numbers, i.e. keyCodes, as v-on modifiers is no longer supported
  • BREAKING: config.keyCodes is no longer supported


use the kebab-case name
see keyCode

<!-- Vue 3 Key Modifier on v-on -->
<input v-on:keyup.delete="confirmDelete" />

Props Default Function this Access


Instead:

  • Raw props received by the component are passed to the default function as argument;
  • The inject API can be used inside default functions.
import { inject } from 'vue'

export default {
  props: {
    theme: {
      default (props) {
        // `props` is the raw values passed to the component,
        // before any type / default coercions
        // can also use `inject` to access injected properties
        return inject('theme', 'default-theme')
      }
    }
  }
}

Render Function API

changes:

  • h is now globally imported instead of passed to render functions as an arguments
  • render function arguments changed to be more consistent between stateful and functional components
  • VNodes now have a flat props structure

Render Function Argument

2.0

// Vue 2 Render Function Example
export default {
  render(h) {
    return h('div')
  }
}


3.0

// Vue 3 Render Function Example
import { h } from 'vue'

export default {
  render() {
    return h('div')
  }
}

Render Function Signature Change


In 2.x, the render function automatically received arguments such as h.

// Vue 2 Render Function Example
export default {
  render(h) {
    return h('div')
  }
}

In 3.x, since the render function no longer receives any arguments, it will primarily be used inside of the setup() function. This has the added benefit of gaining access to reactive state and functions declared in scope, as well as the arguments passed to setup().

import { h, reactive } from 'vue'

export default {
  setup(props, { slots, attrs, emit }) {
    const state = reactive({
      count: 0
    })

    function increment() {
      state.count++
    }

    // return the render function
    return () =>
      h(
        'div',
        {
          onClick: increment
        },
        state.count
      )
  }
}

VNode Props Format

2.0

// 2.x
{
  class: ['button', 'is-outlined'],
  style: { color: '#34495E' },
  attrs: { id: 'submit' },
  domProps: { innerHTML: '' },
  on: { click: submitForm },
  key: 'submit-button'
}

3.0
more flattened

// 3.x Syntax
{
  class: ['button', 'is-outlined'],
  style: { color: '#34495E' },
  id: 'submit',
  innerHTML: '',
  onClick: submitForm,
  key: 'submit-button'
}

Slots Unification

  • this.$slots now exposes slots as functions
  • BREAKING: this.$scopedSlots is removed

2.0

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

// 2.x Syntax
this.$scopedSlots.header

3.0


In 3.x, slots are defined as children of the current node as an object:

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

And when you need to reference scoped slots programmatically, they are now unified into the $slots option.

// 2.x Syntax
this.$scopedSlots.header

// 3.x Syntax
this.$slots.header()

Transition Class Change


The v-enter transition class has been renamed to v-enter-from and the v-leave transition class has been renamed to v-leave-from.

v-model

BREAKING: When used on custom components, v-model prop and event default names are changed:
prop: value -> modelValue;
event: input -> update:modelValue;
BREAKING: v-bind's .sync modifier and component model option are removed and replaced with an argument on v-model;
NEW: Multiple v-model bindings on the same component are possible now;
NEW: Added the ability to create custom v-model modifiers.


2.0

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

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

export default {
  model: {
    prop: 'title',
    event: 'change'
  },
  props: {
    // this allows using the `value` prop for a different purpose
    value: String,
    // use `title` as the prop which take the place of `value`
    title: {
      type: String,
      default: 'Default title'
    }
  }
}

So, v-model in this case would be a shorthand to

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

3.0

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

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

v-model arguments

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

<!-- would be shorthand for: -->

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

This also serves as a replacement to .sync modifier and allows us to have multiple v-models on the custom component.

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

<!-- would be shorthand for: -->

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

修饰符

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


.trim、 .number、.lazy 支持自定义
more see handling-v-model-modifiers

v-if vs. v-for Precedence


BREAKING: If used on the same element, v-if will have higher precedence than v-for


2.0
v-if and v-for on the same element, v-for would take precedence.
v-for 优先级更高
3.0
v-if will always have the higher precedence than v-for
v-if优先级更高

v-bind Merge Behavior


BREAKING: Order of bindings for v-bind will affect the rendering result

2.0
单个属性覆盖对象中的绑定。

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>

3.0
根据绑定的顺序决定。

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>

<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>