VueThings Vue 3 does that Vue 2 could not: new-key reactivity, Map, fragments, Teleport
<script setup>
import { reactive, ref, computed, version } from "vue";
const user = reactive({ name: "Ada" });
const scores = reactive([10, 20]);
const tags = reactive(new Map([["vue", 1]]));
function update() {
user.age = 36;
scores[0] = 99;
tags.set("vite", 2);
}
const count = ref(0);
const double = computed(() => count.value * 2);
const showModal = ref(false);
</script>
<template>
<h3>Vue {{ version }}</h3>
<p class="state">{{ user.name }} {{ user.age ?? "(no age)" }} | {{ scores.join(",") }} | {{ [...tags.keys()].join(",") }}</p>
<button class="update" @click="update">Update</button>
<button class="count" @click="count++">{{ count }} x 2 = {{ double }}</button>
<button class="open" @click="showModal = true">Open modal</button>
<Teleport to="body">
<div v-if="showModal" class="modal">I live directly under body</div>
</Teleport>
</template>
The same scenarios run on Vue 2.7.16 and Vue 3.5.44 side by side (Node + jsdom)
const path = require("path");
const { JSDOM } = require(path.resolve(__dirname, "../pkgs/vue2/node_modules/jsdom"));
const dom = new JSDOM('<!doctype html><div id="v2"></div><div id="v3"></div>');
Object.assign(global, { window: dom.window, document: dom.window.document, Element: dom.window.Element, SVGElement: dom.window.SVGElement, Node: dom.window.Node });
const Vue2 = require(path.resolve(__dirname, "../pkgs/vue2/node_modules/vue/dist/vue.common.dev.js"));
const Vue3 = require(path.resolve(__dirname, "../../../vlab/node_modules/vue/dist/vue.cjs.js"));
Vue2.config.productionTip = false; Vue2.config.devtools = false;
const tick = () => new Promise((r) => setTimeout(r, 10));
const origWarn = console.error, origW = console.warn;
const capture = (fn) => { const msgs = []; console.error = console.warn = (...a) => msgs.push(String(a[0]).split("\n")[0]); try { fn(); } finally { console.error = origWarn; console.warn = origW; } return msgs; };
(async () => {
console.log("versions:", Vue2.version, "/", Vue3.version);
console.log("Vue 2 has new Vue():", typeof Vue2 === "function", "| Vue 3 default export:", Vue3.default, "| Vue 3 createApp:", typeof Vue3.createApp);
const tpl = `<p>{{ user.name }} {{ user.age }} | {{ list.join(",") }}</p>`;
const vm2 = new Vue2({ data: () => ({ user: { name: "Ada" }, list: ["a", "b"] }), template: tpl }).$mount("#v2");
const app3 = Vue3.createApp({ data: () => ({ user: { name: "Ada" }, list: ["a", "b"] }), template: tpl });
const vm3 = app3.mount("#v3");
vm2.user.age = 36; vm2.list[0] = "z";
vm3.user.age = 36; vm3.list[0] = "z";
await tick();
console.log("new prop + index write -> Vue 2:", JSON.stringify(vm2.$el.textContent), "| Vue 3:", JSON.stringify(document.querySelector("#v3").textContent));
vm2.$set(vm2.user, "age", 36); vm2.$set(vm2.list, 0, "z"); await tick();
console.log("Vue 2 after this.$set :", JSON.stringify(vm2.$el.textContent));
app3.unmount();
const filt = `<p>{{ msg | upper }}</p>`;
const f2 = new Vue2({ data: () => ({ msg: "hi" }), filters: { upper: (s) => s.toUpperCase() }, template: filt }).$mount(document.body.appendChild(document.createElement("div")));
const host = document.body.appendChild(document.createElement("div"));
const w = capture(() => { Vue3.createApp({ data: () => ({ msg: "hi", upper: 2 }), template: filt }).mount(host); });
console.log("filter {{ msg | upper }} -> Vue 2:", JSON.stringify(f2.$el.textContent), "| Vue 3 (upper=2):", JSON.stringify(host.textContent), "(| is bitwise OR)");
const bus2 = new Vue2();
let got = null; bus2.$on("ping", (x) => (got = x)); bus2.$emit("ping", 42);
const inst3 = Vue3.createApp({ template: "<i/>" }).mount(document.body.appendChild(document.createElement("div")));
console.log("event bus -> Vue 2 $on works:", got === 42, "| Vue 3 typeof this.$on:", typeof inst3.$on);
const multi = `<h1>a</h1><h2>b</h2>`;
const w2 = capture(() => { try { new Vue2({ template: multi }).$mount(document.body.appendChild(document.createElement("div"))); } catch (e) { console.error(e.message); } });
const h3 = document.body.appendChild(document.createElement("div"));
Vue3.createApp({ template: multi }).mount(h3);
console.log("two root elements -> Vue 2 warns:", JSON.stringify(w2[0]), "| Vue 3 renders:", h3.innerHTML);
})();