Vue3 has been released in RC, but element-UI, the most popular UI library in China, has not yet been released, so our team decided to upgrade ElementUI to version 3.0 to fully support VUe3.0

Welcome to open source

The warehouse address

Github.com/kkbjs/eleme… .

How to participate in open source, refer to the male text of my ran brother: juejin.cn/post/686446…

Recently, everyone in the group is eager to start to participate in the element3 open source project, but there are often some small problems that can not go forward. The reason for this article is that they are too anxious and lack of sufficient understanding of the new features and changes of VUe3. Basically, after watching it, we can practice it, and VUE3 will be won.

If you like to learn by watching video, please go here. I have also prepared video tutorials

A quick start

cdn

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.0-rc.7/vue.global.js"></script>
Copy the code

vue-cli

Upgrade the vue – cli v4.5

npm i -g @vue/cli@next
Copy the code

New projects will have the VUE3 option

vite

The experience is faster with Vite

$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
Copy the code

From vue2 migration

New features

  • Composition API
  • Teleport
  • Fragments
  • Emits Component Option
  • createRendererAPI for creating custom renderers

Destructive change

  • The Global API is instead called by the application instance
  • Global and internal APIs reconfigured for tree-shaking optimization
  • modelOptions andv-bindthesyncModifiers are removed and unified as V-model parameters
  • Render function API modified
  • Functional components can only be created in a simple functional way
  • Deprecate declaring functional components on SFC templates using functional or adding functional options
  • Asynchronous components are requireddefineAsyncComponentMethod to create
  • The component data option should always be declared as a function
  • Custom component whitelists are executed at compile time
  • isAttributes are used only whencomponentOn the label
  • $scopedSlotsProperty removed, both used$slotsInstead of
  • Feature forces policy changes
  • Custom directive APIS are consistent with components
  • Some transition class name changes:
    • v-enter -> v-enter-from
    • v-leave -> v-leave-from
  • The watch option and $watch no longer support dot delimiter string paths, using computed functions as their arguments
  • Application root container in Vue 2.xouterHTMLIs replaced (or compiled to template) by the root component’s template. Vue 3.x now uses the application root containerinnerHTMLTo replace.

remove

  • removekeyCodeAs av-onThe modifier

  • o n . on,
    Off and $once removed
  • Filters to remove
  • Inline Templates Attributes removed

composition api

Composition API provides better logic reuse and code organization for VUE applications.

<template>
  <div>
    <p>counter: {{counter}}</p>
    <p>doubleCounter: {{doubleCounter}}</p>
    <p ref="desc"></p>
  </div>
</template>

<script>
import {
  reactive,
  computed,
  watch,
  ref,
  toRefs,
  onMounted,
  onUnmounted,
} from "vue";

export default {
  setup() {
    const data = reactive({
      counter: 1.doubleCounter: computed(() = > data.counter * 2)});let timer

    onMounted(() = > {
      timer = setInterval(() = > {
        data.counter++
      }, 1000);
    })

    onUnmounted(() = > {
      clearInterval(timer)
    })

    const desc = ref(null)

    watch(() = >data.counter, (val,oldVal) = >{
      // console.log(`counter change from ${oldVal} to ${val}`);
      desc.value.textContent = `counter change from ${oldVal} to ${val}`
    })
    
    return{... toRefs(data), desc}; }};</script>
Copy the code

Teleport

The portal component provides a concise way to specify the parent element of its contents.

<template>
  <button @click="modalOpen = true">A full-screen modal window pops up</button>

  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>This is a modal window! My parent element is "body"!<button @click="modalOpen = false">Close</button>
      </div>
    </div>
  </teleport>
</template>

<script>
export default {
  data() {
    return {
      modalOpen: true}}};</script>

<style scoped>
.modal {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background-color: rgba(0.0.0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: white;
  width: 300px;
  height: 300px;
  padding: 5px;
}
</style>
Copy the code

Fragments

Components in VUe3 can have multiple roots.

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

Emits Component Option

Custom events sent by components in vue3 need to be defined in the emits option:

  • The native event fires twice, for exampleclick
  • Better indication of how components work
  • Object event verification
<template>
  <div @click="$emit('click')">
    <h3>Custom events</h3>
  </div>
</template>

<script>
export default {
  emits: ['click']}</script>
Copy the code

Custom renderer

Renderer customization is supported in Vue3.0: This API allows custom rendering logic. For example, in the following example we can render data to canvas.

First we create a component that describes the data to render. We want to render a component called piechart. We don’t need to declare this component separately because we just want to draw the data it carries to the canvas. Create CanvasApp. Vue

<template>
  <piechart @click="handleClick" :data="state.data" :x="200" :y="200" :r="200"></piechart>
</template>
<script>
import { reactive, ref } from "vue";
export default {
  setup() {
    const state = reactive({
      data: [{name: "College".count: 200.color: "brown" },
        { name: "Undergraduate".count: 300.color: "yellow" },
        { name: "Master".count: 100.color: "pink" },
        { name: "博士".count: 50.color: "skyblue"}}]);function handleClick() {
      state.data.push({ name: "Other".count: 30.color: "orange" });
    }
    return{ state, handleClick }; }};</script>
Copy the code

Next we create a custom renderer, main.js

import { createApp, createRenderer } from 'vue'
import CanvasApp from './CanvasApp.vue'

const nodeOps = {
  insert: (child, parent, anchor) = > {
    // We overwrote the insert logic because there is no actual DOM insertion in our canvasApp
    // Save the parent-child relationship between the elements
    child.parent = parent;

    if(! parent.childs) { parent.childs = [child] }else {
      parent.childs.push(child);
    }

    // Only canvas has nodeType. This is to start drawing content to canvas
    if (parent.nodeType == 1) {
      draw(child); 
      // If an event is attached to a child element, we add a listener to the canvas
      if (child.onClick) {
        ctx.canvas.addEventListener('click'.() = > {
          child.onClick();
          setTimeout(() = > {
            draw(child)
          }, 0); }}}}),remove: child= > {},
  createElement: (tag, isSVG, is) = > {
    // Since there are no DOM elements to create, we simply return the current element data object
    return {tag}
  },
  createText: text= > {},
  createComment: text= > {},
  setText: (node, text) = > {},
  setElementText: (el, text) = > {},
  parentNode: node= > {},
  nextSibling: node= > {},
  querySelector: selector= > {},
  setScopeId(el, id) {},
  cloneNode(el) {},
  insertStaticContent(content, parent, anchor, isSVG) {},
  patchProp(el, key, prevValue, nextValue){ el[key] = nextValue; }};// Create a renderer
let renderer = createRenderer(nodeOps);

// Save the canvas and its context
let ctx;
let canvas;

// Extend mount to create a canvas element first
function createCanvasApp(App) {
  const app = renderer.createApp(App);
  const mount = app.mount
  app.mount = function (selector) {
    canvas = document.createElement('canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    document.querySelector(selector).appendChild(canvas);
    ctx = canvas.getContext('2d');
    mount(canvas);
  }
  return app
}

createCanvasApp(CanvasApp).mount('#demo')
Copy the code

Add a div#demo to index.html

Write draw logic

const draw = (el,noClear) = > {
  if(! noClear) { ctx.clearRect(0.0, canvas.width, canvas.height)
  }
  if (el.tag == 'piechart') {
    let { data, r, x, y } = el;
    let total = data.reduce((memo, current) = > memo + current.count, 0);
    let start = 0,
        end = 0;
    data.forEach(item= > {
      end += item.count / total * 360;
      drawPieChart(start, end, item.color, x, y, r);
      drawPieChartText(item.name, (start + end) / 2, x, y, r);
      start = end;
    });
  }
  el.childs && el.childs.forEach(child= > draw(child,true));
}

const d2a = (n) = > {
  return n * Math.PI / 180;
}
const drawPieChart = (start, end, color, cx, cy, r) = > {
  let x = cx + Math.cos(d2a(start)) * r;
  let y = cy + Math.sin(d2a(start)) * r;
  ctx.beginPath();
  ctx.moveTo(cx, cy);
  ctx.lineTo(x, y);
  ctx.arc(cx, cy, r, d2a(start), d2a(end), false);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.stroke();
  ctx.closePath();
}
const drawPieChartText = (val, position, cx, cy, r) = > {
  ctx.beginPath();
  let x = cx + Math.cos(d2a(position)) * r/1.25 - 20;
  let y = cy + Math.sin(d2a(position)) * r/1.25;
  ctx.fillStyle = '# 000';
  ctx.font = '20px Microsoft Yahei ';
  ctx.fillText(val,x,y);
  ctx.closePath();
}
Copy the code

The Global API is instead called by the application instance

There are many global apis in Vue2 that can change the behavior of vue, such as Vue.com Ponent. This leads to some problems:

  • Vue2 has no concept of app, and the root instance obtained by new Vue() is treated as app. In this way, all root instances created share the same global configuration, which can pollute other test cases during testing and make testing difficult.
  • Global configuration also makes it impossible to create multiple app instances with different global configurations on a single page.

Vue3 uses createApp to return an app instance that exposes a set of global apis

import { createApp } from 'vue'
const app = createApp({})
	.component('comp', { render: () = > h('div'.'i am comp') })
  .mount('#app')
Copy the code

The list is as follows:

2.x Global API 3.x Instance API (app)
Vue.config app.config
Vue.config.productionTip removed (see below)
Vue.config.ignoredElements app.config.isCustomElement (see below)
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use (see below)
Vue.filter removed

Global and internal APIs reconfigured for tree-shaking optimization

Many of vue2’s global-apis hang directly on constructors as static functions, such as vue.nexttick (), which would be dead code if we never used them in our code. Such global-API dead code cannot be ruled out using Webpack’s tree-shaking.

import Vue from 'vue'

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

Vue3 has made a change to extract them as separate functions so that tree shaker optimization of the packaging tool can eliminate these dead codes.

import { nextTick } from 'vue'

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

Affected APIS:

  • Vue.nextTick
  • Vue.observable (replaced by Vue.reactive)
  • Vue.version
  • Vue.compile (only in full builds)
  • Vue.set (only in compat builds)
  • Vue.delete (only in compat builds)

modelOptions andv-bindthesyncModifiers are removed and unified as V-model parameters

In VUe2, the functions of sync and V-Model overlap and are easily confused, but vue3 is unified.

<div id="app">
  <h3>{{data}}</h3>    
  <comp v-model="data"></comp>
</div>
Copy the code
app.component('comp', {
  template: ` 
      
i am comp, {{modelValue}}
`
.props: ['modelValue'],})Copy the code

Render function API modified

Rendering functions have been made simpler and easier to use. The main changes are as follows:

No longer pass h function, we need to manually import; Pat flat props structure. ScopedSlots has been deleted. It’s consolidated into slots

import {h} from 'vue'

render() {
  const emit = this.$emit
  const onclick = this.onclick
  return h('div', [
    h('div', {
      onClick() {
      	emit('update:modelValue'.'new value')}},`i am comp, The ${this.modelValue}`
    ),
    h('button', {
      onClick(){
      	onclick()
    	}}, 
      'buty it! ')])},Copy the code

Functional components can only be created as simple functions; the functional option is deprecated

Functional components vary greatly, mainly in the following aspects:

  • Performance gains are negligible in VUe3, so the use of state components is recommended in VUe3
  • Function components can only be declared and received in purely functional formpropsandcontextTwo parameters
  • The SFC<template>Can not addfunctionalA feature declaration function is a component
  • { functional: true }Component option Removal

Declare a Functional component, function.js

import { h } from 'vue'

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

Heading.props = ['level']

export default Heading
Copy the code
<Functional level="3">This is an H3</Functional>
Copy the code

Remove the functional option. The divider in the Element is used as an example

Asynchronous components are requireddefineAsyncComponentMethod to create

Since functional components in VUe3 must be defined as pure functions, asynchronous components are defined as follows:

  • You must explicitly use the defineAsyncComponent wrapper

  • Rename the Component option to loader

  • The Loader function no longer accepts resolve and reject and must return a Promise

Define an asynchronous component

import { defineAsyncComponent } from 'vue'

// Asynchronous components with no configuration
const asyncPage = defineAsyncComponent(() = > import('./NextPage.vue'))
Copy the code

Asynchronous component with configuration, loader option is previous Component

import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// The asynchronous component to be configured
const asyncPageWithOptions = defineAsyncComponent({
  loader: () = > import('./NextPage.vue'),
  delay: 200.timeout: 3000.errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})
Copy the code

The component data option should always be declared as a function

Vue3 uses the data option as a function and returns responsive data.

createApp({
  data() {
    return {
      apiKey: 'a1b2c3'
    }
  }
}).mount('#app')
Copy the code

User-defined component whitelist

Custom element detection in VUe3 occurs when the template is compiled. If you want to add custom elements other than VUE, you need to set the isCustomElement option in the compiler options.

When using the build tool, templates are precompiled with vue-loader. Set the provided compilerOptions:

rules: [
  {
    test: /\.vue$/,
    use: 'vue-loader'.options: {
      compilerOptions: {
        isCustomElement: tag= > tag === 'plastic-button'}}}// ...
]
Copy the code

VueCompilerOptions can be configured in vite. Config. js:

module.exports = {
  vueCompilerOptions: {
    isCustomElement: tag= > tag === 'piechart'}}Copy the code

If you are using the runtime compiled version of VUE, you can configure isCustomElement globally

const app = Vue.createApp({})
app.config.isCustomElement = tag= > tag === 'plastic-button'
Copy the code

isAttributes are used only whencomponentOn the label

When setting dynamic components in VUe3, the IS attribute can only be used on the Component tag

<component is="comp"></component>
Copy the code

Intra-dom template parsing uses V-IS instead

<table>
  <tr v-is="'blog-post-row'"></tr>
</table>
Copy the code

In-dom templates only, so we tested it on a separate page, index2.html

<div id="app">
  <table>
    <tr v-is="'row'" v-for="item in items" :data="item"></tr>
  </table>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.0-rc.9/vue.global.js"></script>
<script>
  Vue.createApp({
    data() {
      return {
        items: ["aaa"."bbb"]}; }, }) .component("row", {
      props: ["data"].template: "<tr><td>{{this.data}}</td></tr>",
    })
    .mount("#app");
</script>
Copy the code

$scopedSlotsProperty removed, both used$slotsInstead of

Vue3 unified normal slots and scoped slots to $slots. The changes are as follows:

  • Slots are exposed as functions
  • $scopedSlots removed

Function form to access slot content, mylink.vue

<script> import {h} from 'vue' export default { props: { to: { type: String, required: true, }, }, render() { return h("a", { href: this.to }, this.$slots.default()); }}; </script>Copy the code

When migrating, change $slots.xx to $slots.xx(). Here uses uploader in Element as an example

Feature forces policy changes

The underlying API changes do not affect most developers

V3.vuejs.org/guide/migra…

The custom directive API is consistent with the component

Vue3 directive apis are consistent with components as follows:

  • The bind – beforeMount
  • He inserted – mounted
  • beforeUpdate: new! Called before the element itself is updated, much like a component lifecycle hook
  • Update – removed! Updated is essentially the same as updated, so removed and replaced with updated.
  • ComponentUpdated – updated
  • beforeUnmount new! Similar to component lifecycle hooks, this is called just before an element is to be removed.
  • Unbind and unmounted

Experiment by writing a command

const app = Vue.createApp({})

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

Transition class name change:

  • v-enterv-enter-from
  • v-leavev-leave-from

Flow chart of transitions in Vue2: The two starting class names change in the figure

Try it out, transitionTest.vue

<template>
  <div id="demo">
    <button @click="show = !show">Toggle</button>

    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true,
    };
  },
};
</script>

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
Copy the code

The component watch option and instance method $watch no longer support dot delimiter string paths

The expression divided by. Is no longer supported by watch and watch, and can be implemented by using calculation function as watch, calculation function as watch parameter.

this.$watch(() = > this.foo.bar, (v1, v2) = > {
  console.log(this.foo.bar)
})
Copy the code

Application root container in Vue 2.xouterHTMLWill be replaced (or compiled to template) by the root component’s template, Vue 3.x now uses the root container’sinnerHTMLreplace

keyCodeAs av-onModifiers are removed

Vue2 can use keyCode to refer to a key. Vue3 does not support keyCode.

<! -- keyCode mode is no longer supported
<input v-on:keyup.13="submit" />

<! Use alias only -->
<input v-on:keyup.enter="submit" />
Copy the code


o n . on,
Off and $once removed

The above three methods were deemed not to be provided by VUE, so they were removed and can be implemented using other tripartite libraries.

<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
Copy the code
/ / create the emitter
const emitter = mitt()

// Send events
emitter.emit('foo'.'foooooooo')

// Listen on events
emitter.on('foo'.msg= > console.log(msg))
Copy the code

Filters to remove

Filters have been removed from VUe3, call methods or calculate properties instead.

Inline Templates Attributes removed

Vue2 provides the inline-template feature to provide custom component internal content as its template

<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>
Copy the code

Vue3 is no longer supported and script can be used instead

<script type="text/html" id="my-comp-template">
  <div>{{ hello }}</div>
</script>
Copy the code
const MyComp = {
  template: '#my-comp-template'
  // ...
}
Copy the code