VUE3与VUE2的区别,以及迁移方案

新特性

API

- 组合式 API setup

import { ref, toRefs, readonly, watch, computed } from "vue";

export default {
  name: "demo",
  // 添加组合式API,在组件创建前执行所以,没有this
  setup(props) {
    // 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
    const { user } = toRefs(props);

    // 将属性变为响应式的  ref(attr)
    const list = ref([]); // { value: [] }
    const GetData = async () => {
      list.value = await FetchList(props.user);
    };

    // 生命周期函数
    // on `mounted` call `GetData`
    onMounted(GetData);

    // 监听 user 的变化,重新请求 GetData
    watch(user, GetData);

    // 计算属性
    const keyword = ref("");
    const resList = computed(() => {
      return list.value.filter((item) => item.name.includes(keyword.value));
    });

    // 返回对象可以组件内this.调用
    return {
      list,
      GetData,
      keyword,
      resList,
    };
  },
};

如上所示,相同模块的功能被整合到了一起,下面把这部分功能提取到单独的文件 combined.js

// -- combined.js --
import { ref, toRefs, watch, computed } from "vue";

export default function Combined(user) {
  const list = ref([]); // { value: [] }
  const GetData = async () => {
    list.value = await FetchList(props.user);
  };

  const keyword = ref("");
  const resList = computed(() => {
    return list.value.filter((item) => item.name.includes(keyword.value));
  });

  onMounted(GetData);
  watch(user, GetData);

  return { GetData, keyword, resList };
}

// -- demo.vue --
import Combined from "./combined.js";
import { toRefs } from "vue";

export default {
  props: {
    user: { type: String },
  },
  setup(props) {
    const { user } = toRefs(props);
    const { GetData, keyword, resList } = Combined(user);
    return { GetData, keyword, resList };
  },
  methods: {
    Reset() {
      this.keyword = ""; // 自动触发搜索
    },
  },
};

- Teleport

<template>
  <button @click="modalOpen = true">点我弹框</button>

  <teleport to="body">
    <div v-if="modalOpen" class="modal">我在body下</div>
  </teleport>
</template>

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

teleport 下内容将会挂载到 body 下

- 组件内根元素可以有多个,即片段

<template>
  <header>...</header>
  <!-- 调用处传入的属性绑定在这个标签上 -->
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

- 组件事件定义和类型验证。emits 里定义的原生事件(比如 click)将替代原生事件的监听器

export default {
  props: {
    list: {
      type: Array,
    },
  },
  emits: {
    // 没有验证
    click: null,

    // 验证submit 事件
    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 绑定

app.component("user-name", {
  props: {
    firstName: String,
    lastName: String,
  },
  template: `
    <input 
      type="text"
      :value="firstName"
      @input="$emit('update:firstName', $event.target.value)">

    <input
      type="text"
      :value="lastName"
      @input="$emit('update:lastName', $event.target.value)">
  `,
});
<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>

使用v-model:xxx传值,使用$emit('update:xxx')同步变更

- 自定义 v-model 修饰符

<my-component v-model:first-name.lowcase="bar"></my-component>
app.component("my-component", {
  props: {
    firstName: String,
    firstNameModifiers: {
      // 所有修饰符都在它里:{ lowcase: true }
      default: () => ({}),
    },
  },
  template: `
    <input type="text" 
      :value="firstName"
      @input="$emit('update:firstName', $event.target.value)">
  `,
  created() {
    console.log(this.firstNameModifiers.lowcase); // true,可以根据此值做一些逻辑处理
  },
});
  • 原来的value -> modelValueinput -> update:modelValue

  • 组件内使用xxxModifiersmodelModifiers记录 v-model 的修饰符。

  • Modifiers 为一个对象,比如 { lowcase: true }

  • 2.x 中的.sync被弃用

css <style scoped>

/* 深度选择器 类似/deep/,vue3不再支持/deep/ */
::v-deep(.foo) {
}
:deep(.foo) {
}

/* 插槽 */
::v-slotted(.foo) {
}
:slotted(.foo) {
}

/* 一次性全局规则 */
::v-global(.foo) {
}
:global(.foo) {
}

/* 尽量不使用 :deep .foo 这样的组合选择器*/

不兼容的变更

API

创建应用不再使用 new Vue

import { createApp } from "vue";
import MyApp from "./MyApp.vue";

const app = createApp(MyApp);
// 或链式调用 createApp(comp|page).mount('#app')

app.component("button-counter", {
  data: () => ({
    count: 0,
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>',
});

app.directive("focus", {
  mounted: (el) => el.focus(),
});

app.use(VueRouter);

app.mount("#app");
2.x 全局 API 3.x 实例 API (app)
Vue.config app.config
Vue.config.productionTip removed
Vue.config.ignoredElements app.config.isCustomElement
Vue.use app.use
Vue.mixin app.mixin
Vue.component app.component
Vue.directive app.directive

.

  • nextTick 必须显式导入

    import { nextTick } from "vue";
    nextTick(() => {});
    
    // webpack.config.js
    // 在插件开发中,需要将vue源码从代码里剔除
    module.exports = {
      /*...*/
      externals: {
        vue: "Vue",
      },
    };
    
  • <template v-for key> key 应该设置在 template 标签上

  • 与 2 不同,在 3 中同一元素上 v-if 比 v-for 优先级更高(最好不要一起用)

  • v-for 中的 Ref:不会再自动创建数组,需要为它指定一个方法

    <div v-for="item in list" :ref="RecardEl"></div>
    
    methods: {
      RecardEl(el) {
        this.itemRefs.push(el) // 需要自已记录el
      }
    },
    
  • 函数式组件:仅是个简单函数(不再是对象);不再有 functional 属性;只接受 props 和 context 两个参数;不再隐式提供 h;

    import { h } from "vue";
    
    const myComp = (props, context) => {
      return h(`h${props.level}`, context.attrs, context.slots);
    };
    
    myComp.props = ["level"];
    
    export default myComp;
    
  • 异步组件:

    // in vue2.x
    const asyncPage = () => import("./NextPage.vue");
    
    // in vue3
    import { defineAsyncComponent } from "vue";
    const asyncPage = defineAsyncComponent(() => import("./NextPage.vue"));
    

    带选项的异步组件 都不知道还有这种用法

    // in vue2.x
    const asyncPage = {
      component: () => import("./NextPage.vue"),
      delay: 200,
      timeout: 3000,
      error: ErrorComponent,
      loading: LoadingComponent,
    };
    
    // in vue3
    import { defineAsyncComponent } from "vue";
    import ErrorComponent from "./components/ErrorComponent.vue";
    import LoadingComponent from "./components/LoadingComponent.vue";
    
    const asyncPageWithOptions = defineAsyncComponent({
      loader: () => import("./NextPage.vue"), // 不使用component,使用loader
      delay: 200,
      timeout: 3000,
      errorComponent: ErrorComponent,
      loadingComponent: LoadingComponent,
    });
    
  • 自定义指令 directive:

    • bind → beforeMount
    • inserted → mounted
    • beforeUpdate:新的!这是在元素本身更新之前调用的,很像组件生命周期钩子。
    • update → 移除!有太多的相似之处要更新,所以这是多余的,请改用 updated。
    • componentUpdated → updated
    • beforeUnmount:新的!与组件生命周期钩子类似,它将在卸载元素之前调用。
    • unbind -> unmounted
    app.directive("highlight", {
      // in 2.x,bind(el, binding, vnode) {
      beforeMount(el, binding, vnode) {
        el.style.background = binding.value;
    
        // in 2.x,const vm = vnode.context
        const vm = binding.instance;
      },
    });
    

    由于现在组件支持多根节点,所以绑定在组件上的自定义指定:

    <template>
      <header>...</header>
      <!-- 调用处传入的属性绑定在这个标签上 -->
      <main v-bind="$attrs">...</main>
      <footer>...</footer>
    </template>
    
    // 没明白
    <div @vnodeMounted="myHook" />
    
  • watch 不再支持点分隔字符串路径,请改用计算函数作为参数

this.$watch(
  () => this.c.d,
  (newVal, oldVal) => {
    // 做点什么
  }
)
// or
computed: {
  cd() { return this.c.d; }
},
watch{
  cd(newVal) {
    // do something
  }
}
  • destroyed => unmounted,beforeDestroy => beforeUnmount
  • 来自 mixin 的 data 现在将会替换属性的值,而不再合并
  • <template> 没有特殊指令的标记 (v-if/else-if/else、v-for 或 v-slot) 现在被视为普通元素,并将生成原生的 <template> 元素,而不是渲染其内部内容。
  • on,off 和 $once 实例方法已被移除,eventBus 退出历史。
  • 过滤器已删除,官方建议使用计算属性。
  • 删除了$destroy 方法,官方认为用户不应再手动管理单个 Vue 组件的生命周期
  • 移除了 click.native 中 native 修饰符
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350