博客 分类专栏 专题 成员
30分钟从vue2 上手 vue3
2022-06-12 12:12:58
分类专栏: Vue3

vue3.x 和 vue2.x的一些区别

后面的讲解都是使用的vue3.x 使用了setup语法糖,这个语法糖的确好用,不用每个都去return,实际开发中经常忘记return,这个语法糖就很好用了,对于setup语法糖是3.2才支持的。
主要讲解我们开发中经常用到的一些内容,包括 router,prototype,父子组件调用 等。

vue3.x组合式api

vue2 定义属性和方法

<template>
  <div>
    <div>简单属性</div>
    <div>{{ msg }}</div>
    <div><button @click="updateMsg">修改马杀鸡</button></div>

    <div>对象属性</div>
    <div>{{ ObjMsg.msg }}</div>
    <div><button @click="updateObjMsg">修改对象马杀鸡</button></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "我是马杀鸡",
      ObjMsg: {
        msg: "我是对象马杀鸡",
      },
    };
  },
  methods: {
    updateMsg() {
      this.msg = "我是马杀鸡修改后的内容";
    },
    updateObjMsg() {
      this.ObjMsg.msg = "我是对象马杀鸡修改后的内容";
    },
  },
};
</script>

<style scoped>
</style>

vue3 定义属性和方法

<template>
  <div>
    <div>简单属性</div>
    <div>{{ msg }}</div>
    <div><button @click="updateMsg">修改马杀鸡</button></div>

    <div>对象属性</div>
    <div>{{ ObjMsg.msg }}</div>
    <div><button @click="updateObjMsg">修改对象马杀鸡</button></div>
  </div>
</template>

<script setup>
//注意要导入 reactive和ref
import { reactive, ref } from "vue";

//只是基本数据类型相关操作块
const msg = ref("我是马杀鸡");
const updateMsg = () => {
  msg.value = "我是马杀鸡修改后的内容";
};

//这是对象数据类型相关操作
const ObjMsg = reactive({
  msg: "我是对象马杀鸡",
});
const updateObjMsg = () => {
  ObjMsg.msg = "我是对象马杀鸡修改后的内容";
};
</script>

<style scoped>
</style>

官方建议简单类型使用 ref,对象类型使用reactive,reactive最终也是转为ref的,那么上面对象类型也可以这么写

const ObjMsg = ref({
  msg: "我是对象马杀鸡",
});
const updateObjMsg = () => {
  ObjMsg.value.msg = "我是对象马杀鸡修改后的内容";
};

操作的时候需要带上value,这个就看个人习惯了,都用ref也是没有问题的

可以看出 vue3 的组合式api,可以将逻辑块写在一起,vue2,属性定义和逻辑代码是分开的,不利于维护。

router 使用的区别

vue2.x router使用

vue2使用的router是3.x

定义router

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    name: '首页',
    path: '/home',
    component: () => import('../views/Home.vue'),
  }
];

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router

在main.js中引入router

import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

在vue中使用

export default {
  data() {
    return {};
  },
  created() {
    console.log("当前路由", this.$route.path);
    console.log("过五秒后跳转到home");
    setTimeout(() => {
      this.$router.push("home");
    }, 5000);
  },
};

直接使用this.$route 和 this.$router 就可以直接调用,比较方便

vue3.x使用router

vue3.x使用的router版本4.x

定义router

import { createRouter, createWebHistory } from 'vue-router'
const routes = [
    {
        name: '首页',
        path: '/home',
        component: () => import('../views/Home.vue'),
    }
];

//这里不是通过new来创建,通过createRouter 方法创建,使用的模式不是通过mode来定义
const router = createRouter({
    routes,
    history: createWebHistory(),
})

export default router

在main.js中引用

import router from './router/router'
createApp(App).use(router).mount('#app')

在vue中使用router

<script setup>
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const route = useRoute();
const init = () => {
  console.log("当前路由", route.path);
  console.log("过五秒后跳转到home");
  setTimeout(() => {
    router.push("home");
  }, 5000);
};
init();
</script>

使用相比vue2麻烦一些,需要先引入,然后获取router,route对象,vue3不能通过this获取vue对象了。

总结:

vue2.x 通过this.router this.route直接调用

vue3.x 需要引入useRoute,useRouter 通过useRouter()和useRoute() 方法获取相关对象,然后调用

定义全局方法,变量

vue2.x定义全局方法

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

const Utils = {
  isEmpty: (value) => {
    if (value === null || value === undefined || value.trim() === "") {
      return true
    }
    return false;
  }
}

Vue.prototype.Utils = Utils;

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

vue2.x中使用全局方法

<script>
export default {
  data() {
    return {};
  },
  created() {
    let test = " ";
    console.log("调用全局为空判断", this.Utils.isEmpty(test));
  },
};
</script>
输出结果:调用全局为空判断 true

vue2中可以使用原生类型扩展vue对象,然后通过this直接调用

vue3.x定义全局方法

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/router'


const Utils = {
    isEmpty: (value) => {
        if (value === null || value === undefined || value.trim() === "") {
            return true
        }
        return false;
    }
}

const app = createApp(App);
app.config.globalProperties.Utils = Utils;

app.use(router)
app.mount('#app')


vue3.x中使用全局方法

<script setup>
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
const init = () => {
  let test = " ";
  console.log("调用全局为空判断", proxy.Utils.isEmpty(test));
};

init();
</script>

输出结果:调用全局为空判断 true

vue3 是通过代理获取全局对象,所以在使用的时候需要通过getCurrentInstance 方法获取当前实例,然后调用

总结

vue2.x通过prototype原生类扩展属性,通过this.xxx调用

vue3.x 通过app.config.globalProperties 定义全局方法,通过getCurrentInstance 获取当前对象,然后调用

父子组件调用

vue2.x 父组件调用子组件

定义子组件

<template>
  <div :style="{ border: '1px solid red' }">
    <div>这里展示的是父组件传过来的参数:{{ msg }}</div>
    <div>这里展示的是父组件调用子组件方法修改的参数:{{ msg2 }}</div>
  </div>
</template>

<script>
export default {
  props: {
    msg: String,
  },
  data() {
    return {
      msg2: "",
    };
  },
  methods: {
    changeMsg2(msg) {
      this.msg2 = msg;
    },
  },
};
</script>

<style scoped>
</style>

通过props定义参数,同时定义了一个changeMsg2的方法,提供父组件调用

定义父组件

<template>
  <div>
    <Son ref="refSon" :msg="msg"></Son>
    <button @click="opSonMethod">调用子组件中的方法</button>
  </div>
</template>

<script>
import Son from "./ParentSon.vue";
export default {
  components: {
    Son,
  },
  data() {
    return {
      msg: "我是父组件传到子组件的参数",
      sonParams: "",
    };
  },
  methods: {
    opSonMethod() {
      this.$refs.refSon.changeMsg2("我是父组件调用子组件方法传过来的参数");
    },
  },
};
</script>

image.png

父组件运行起来的效果,红框中的内容就是子组件

传入参数 msg,同时定义子组件的ref,父组件通过this.$refs.refSon 找到子组件,然后调用子组件的方法

点击按钮后的效果

!

看到,子组件显示了父组件传入的方法参数

这是父组件调用子组件

vue2.x子组件调用父组件

定义子组件

我们继续使用上面的组件

<template>
  <div :style="{ border: '1px solid red' }">
    <div>这里展示的是父组件传过来的参数:{{ msg }}</div>
    <div>这里展示的是父组件调用子组件方法修改的参数:{{ msg2 }}</div>
    <button @click="opParentMethod">
      我是子组件中的按钮,我来调用父组件的方法
    </button>
  </div>
</template>

<script>
export default {
  props: {
    msg: String,
  },
  data() {
    return {
      msg2: "",
    };
  },
  methods: {
    changeMsg2(msg) {
      this.msg2 = msg;
    },
    opParentMethod() {
      this.$emit("opParent", "我是子组件调用父组件传入的参数");
    },
  },
};
</script>

<style scoped>
</style>

这里子组件定义了一个按钮,来调用父组件的opParent

定义父组件

<template>
  <div>
    <Son ref="refSon" :msg="msg" @opParent="myParentMethod"></Son>
    <button @click="opSonMethod">调用子组件中的方法</button>
    <div>展示子组件传入的方法参数:{{ sonParams }}</div>
  </div>
</template>

<script>
import Son from "./ParentSon.vue";
export default {
  components: {
    Son,
  },
  data() {
    return {
      msg: "我是父组件传到子组件的参数",
      sonParams: "",
    };
  },
  methods: {
    opSonMethod() {
      this.$refs.refSon.changeMsg2("我是父组件调用子组件方法传过来的参数");
    },
    myParentMethod(e) {
      this.sonParams = e;
    },
  },
};
</script>

在调用的父组件中定义 @opParent 这里的名字要和子组件中的 this.$emit(“opParent”, “我是子组件调用父组件传入的参数”); emit里面的方法名一样

然后定义opParent要调用的方法,这样点击子组件的按钮就可以直接调用父组件的方法了。

点击子组件中的按钮,运行效果

image.png

总结:父组件通过ref调用子组件中的方法 子组件通过this.$emit()调用父组件方法

vue3.x父组件调用子组件

定义子组件

<template>
  <div :style="{ border: '1px solid red' }">
    <div>这里展示的是父组件传过来的参数:{{ msg }}</div>
    <div>这里展示的是父组件调用子组件方法修改的参数:{{ msg2 }}</div>
    <button @click="opParentMethod">
      我是子组件中的按钮,我来调用父组件的方法
    </button>
  </div>
</template>

<script setup>
import { ref } from "vue";
const props = defineProps({
  msg: String,
});

const msg2 = ref();
const changeMsg2 = (msg) => {
  msg2.value = msg;
};

defineExpose({
  changeMsg2,
});
</script>

定义props 方式跟vue2不一样,需要对外的方法要使用 defineExpose 对外,否则 父组件无法调用

定义父组件

<template>
  <div>
    <Son ref="refSon" :msg="msg"></Son>
    <button @click="opSonMethod">调用子组件中的方法</button>
  </div>
</template>

<script setup>
import Son from "./ParentSon.vue";
import { ref } from "vue";

const refSon = ref();
const msg = ref("我是父组件传到子组件的参数");
const opSonMethod = () => {
  refSon.value.changeMsg2("我是父组件调用子组件方法传过来的参数");
};
</script>

调用子组件不能使用this.$refs了,需要先在html中定义ref,然后在js中定义同名的ref()对象,然后通过ref对象的.value调用

vue3.x子组件调用父组件

定义子组件

<template>
  <div :style="{ border: '1px solid red' }">
    <button @click="opParentMethod">
      我是子组件中的按钮,我来调用父组件的方法
    </button>
  </div>
</template>

<script setup>
import { ref,defineExpose } from "vue";
const emit = defineEmits();
const props = defineProps({
  msg: {
    type: String,
    defualt: ""
  }
})
const msg2 = ref("");

const opParentMethod = () => {
  emit("opParent", "我是子组件调用父组件传入的参数");
};
</script>

//父组件调用子组件的方法
const changeMsg2 = (msg) => {
  msg2.value = msg
}

defineExpose({
  changeMsg2
})

这里不能使用this.$emit()直接调用,需要先通过defineEmits() 获取emit对象,然后调用

定义父组件

<template>
  <div>
    <Son @opParent="myParentMethod"></Son>
    <div>展示子组件传入的方法参数:{{ sonParams }}</div>
  </div>
</template>

<script setup>
import Son from "./ParentSon.vue";
import { ref } from "vue";

const sonParams = ref();

const myParentMethod = (e) => {
  sonParams.value = e;
};
</script>

运行效果
image.png

父组件跟vue2.x 没啥区别,定义方法,调用即可。

总结:vue2.x和vue3.x 父子组件调用

vue2.x 引入子组件,然后定义在components中直接通过 props定义子组件参数,通过this.$emit()调用父组件方法,父组件直接通过this.$refs.ref 调用子组件方法

vue3.x 引入子组建后,直接使用,子组件参数需要定义在defineProps 中,子组件方法要通过 defineExpose导出父组件才能调用,子组件调用defineEmits()获取emit对象调用父组件 父组件通过定义const xxx=ref() 来定义ref组件的引用,然后通过对象来调用子组件

watch的使用

vue2.x使用watch

<template>
  <div>
    <div>需要监听的值:{{ msg }}</div>
    <button @click="changeMsg">修改值</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "123456",
    };
  },
  methods: {
    changeMsg() {
      this.msg = new Date().getTime();
    },
  },
  watch: {
    msg: {
      immediate: true,
      deep: false,
      handler(newValue, oldValue) {
        console.log("watch,oldValue:" + oldValue + ",newValue:" + newValue);
      },
    },
  },
};
</script>

我们点击按钮,控制台可以输出监听值的变化

vue3.x使用watch

<template>
  <div>
    <div>需要监听的值:{{ msg }}</div>
    <button @click="changeMsg">修改值</button>
  </div>
</template>

<script setup>
import { ref, watch } from "vue";
const msg = ref("123456");
const changeMsg = () => {
  msg.value = new Date().getTime();
};
//监听单个
watch(
  msg,
  (newValue, oldValue) => {
    console.log("监听单个watch,oldValue:" + oldValue + ",newValue:" + newValue);
  },
  { immediate: true, deep: false }
);
//监听多个
watch(
  [msg, msg],
  (newMsg, oldMsg) => {
    console.log("监听多个watch,newMsg:" + newMsg + ",oldMsg:" + oldMsg);
  },
  { immediate: true, deep: false }
);
//此时 newMsg 和oldMsg是数组    
</script>

需要先引入watch,而且监听的值,需要先定义,否则报错

image.png

监听多个,newMsg 和 oldMsg 都为数组

生命周期

vue2.x生命周期

<template>
  <div>
    我是被修改的属性:{{ msg }}
    <button @click="updatePage">我来修改界面</button>
    <button @click="goHome">我要离开,触发一下destory</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "",
    };
  },
  methods: {
    updatePage() {
      this.msg = "我来修改界面";
    },
    goHome() {
      this.$router.push("/home");
    },
  },
  beforeCreate() {
    console.log("我是beforeCreate");
  },
  created() {
    console.log("我是created");
  },
  beforeMount() {
    console.log("beforeMount");
  },
  mounted() {
    console.log("mounted");
  },
  beforeUpdate() {
    console.log("beforeUpdate");
  },
  updated() {
    console.log("updated");
  },
  beforeDestroy() {
    console.log("beforeDestroy");
  },
  destroyed() {
    console.log("destroyed");
  },
};
</script>

<style lang="scss" scoped>
</style>

vue3.x生命周期

<template>
  <div>
    我是被修改的属性:{{ msg }}
    <button @click="updatePage">我来修改界面</button>
    <button @click="goHome">我要离开,触发一下destory</button>
  </div>
</template>

<script setup>
import {
  onBeforeMount,
  onBeforeUnmount,
  onBeforeUpdate,
  onMounted,
  onUnmounted,
  onUpdated,
} from "@vue/runtime-core";
import { ref } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();

const msg = ref("");

const updatePage = () => {
  msg.value = "我来修改界面";
};

const goHome = () => {
  router.push("/home");
};

const init = () => {
  console.log("我是页面初始化的方法");
};
init();

onBeforeMount(() => {
  console.log("beforeMount");
});
onMounted(() => {
  console.log("mounted");
});
onBeforeUpdate(() => {
  console.log("onBeforeUpdate");
});
onUpdated(() => {
  console.log("onUpdated");
});
onBeforeUnmount(() => {
  console.log("onBeforeUnmount");
});
onUnmounted(() => {
  console.log("onUnmounted");
});
</script>

<style lang="scss" scoped>
</style>

vue3中没有 beforeCreate created 也没有 beforeDestory destroyed 使用 onBeforeUnmount onUnmounted来代替

根节点不同

vue2.x 再template中只能又一个根节点 vue3.x可以有多个跟节点

总结

以上就是目前我发现的vue2.x和vue3.x在使用上最大的几个变化。通过上面的例子可以看出,在使用一些常用方法的时候,vue2的确方便,直接使用this就可以调用,vue3.x需要引入后再调用,vue3是按需引入的。但是vue3.x是未来的趋势,而且性能方面vue3.x比vue2.x高很多。后续的一些ui框架,组件,工具,肯定会持续维护vue3.x版本,vue2.x基本就停止维护了,比如我们最常用的ui框架 element ui 就是活生生的例子。其实用习惯了也是一样,就跟最开始我觉得jquery也挺好,用习惯了vue,觉得vue还是香,目前我觉得vue2还是很香的,也许用一段时间我觉得vue3.x会比vue2.x香,大家一起学起来吧。