美文网首页
VUE3与VUE2的区别,以及迁移方案

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

作者: 我叫Aliya但是被占用了 | 来源:发表于2021-12-17 11:03 被阅读0次

新特性

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 修饰符

相关文章

网友评论

      本文标题:VUE3与VUE2的区别,以及迁移方案

      本文链接:https://www.haomeiwen.com/subject/inqifrtx.html