Vue3到底发生了哪些变化?
前言
Vue3距离现在2021-11-28已经发布差不多一年时间,市面上现在还是主要使用的vue2,那工作的伙伴是不是还在踌躇要不要学Vue3?此篇文章中并没有告诉你答案,作者现在也是处于一个学习研究Vue3的一个阶段,只会罗列出Vue3到底是发生了哪些变化,与有没有跟我们带来惊喜......相信看完这篇文章你们自有判断
你知道的越多,你不知道的越多
点赞
再看,手留余香,与有荣焉
有一说一这张图我是非常满意的
一.Vue3的生态和优势
- 社区生态 - 逐步完善
- 整体优化 - 性能优化/TS支持优化/组合式API加持
- 市场使用 - 部分技术选型激进的公司已经在生产环境使用了vue3
社区生态
组件(插件)名称 | 官方地址 | 简介 |
---|---|---|
ant-design-vue | antdv.com/docs/vue/in… | ant-design-vue 是 Ant Design 的 Vue 实现,组件的风格与 Ant Design 保持同步 |
element-plus | element-plus.gitee.io/#/zh-CN | Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库 |
vant | vant-contrib.gitee.io/vant/v3/#/z… | 有赞前端团队开源的移动端组件库,于 2016 年开源,已持续维护 4 年时间 |
Naive UI | www.naiveui.com/zh-CN/ | 一个 Vue 3 组件库比较完整,主题可调,使用 TypeScript,不算太慢,有点意思 |
VueUse | vueuse.org/ | 基于composition组合api的常用集合 |
整体优化
-
性能提升
- 首次渲染更快
- diff算法更快
- 内存占用更少
- 打包体积更小
-
更好的Typescript支持
-
Composition API (重点)
相关阅读:
- Vue3 中文文档 vue3js.cn/docs/zh/
- Vue3 设计理念 vue3js.cn/vue-composi…
市场使用
潜力很大,还没完全火起来
二. Vue3开发环境搭建
- 全局安装vue-cli
npm i -g @vue/cli
- 创建脚手架
vue create vue3-demo(名称自定义)
我选择的默认vue3的版本,可以自定义哈
- vue3的调试工具需要自己去下载额外的插件,把vue2的调试工具给禁用了才可以使用,双手奉上作者一直使用的插件 插件地址
- 以上步骤回车之后,vue-cli会帮助我们跑起来一个内置了vue3版本的vue项目
首先我们可以看一下package.json
文件,在dependencies配置项中显示,我们当前使用的版本为3.2.0
"dependencies": {
"vue": "^3.2.0"
}
然后打开main.js
入口文件,发现Vue的实例化发生了一些变化,由先前的new关键词实例化,转变为createApp方法的调用形式 (更多详情:www.bilibili.com/read/cv1013…)
vue2.x
new Vue({
el: '#app',
render: h => h(App)
})
vue3.x
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
最后我们打开一个单文件组件发现,vue3.0的单文件组件中不再强制要求必须有唯一根元素, 可以放任意多个根元素, 将 template 中的根元素全部以 appendChild 方式加入 #app, 不是替换
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
但是呢,eslint会报错滴,需要在vscode找到vuter点开小齿轮-》扩展设置-》
把这个框框取消勾选就阔以啦
三.Vue3中的两种API
组合式API(Composition API)算是vue3对我们开发者来说非常有价值的一个API更新,我们先不关注具体语法,先对它有一个大的感知
1. vue2中的选项式API
<template>
<p>
<button @click="show">显示</button>
<button @click="hide">隐藏</button>
</p>
<div class="id" v-if="isShow">我是一个显示隐藏的div</div>
<p>
<button @click="changeWhite">白色</button>
<button @click="changeBlue">蓝色</button>
</p>
<div class="id" :style="{color}">我是改变文字颜色的div</div>
</template>
<script>
export default {
name: "",
data() {
return {
isShow: true,
color:''
};
},
methods: {
show() {
this.isShow = true;
},
hide() {
this.isShow = false;
},
changeWhite(){
this.color = '#fff'
},
changeBlue(){
this.color = 'blue'
}
}
};
</script>
<style scoped >
.id {
width: 200px;
height: 200px;
background-color: red;
}
</style>
2. 组合式API
<template>
<p>
<button @click="show">显示</button>
<button @click="hide">隐藏</button>
</p>
<div class="id" v-if="isShow">我是一个显示隐藏的div</div>
<p>
<button @click="changeWhite">白色</button>
<button @click="changeGreen">绿色</button>
</p>
<!-- <div class="id" :style="`color:${changeColor}`">我是一个修改文字颜色的div</div> -->
<div class="id" :style="`color:${color}`">我是一个修改文字颜色的div</div>
</template>
<script>
// ref 响应式,简单与复杂数据类型 (必须.value)
import { ref } from "vue";
// 功能一:盒子显示与隐藏
function useShow() {
const isShow = ref(true);
function show() {
isShow.value = true;
}
function hide() {
isShow.value = false;
}
return { isShow, show, hide };
}
// 功能二:文字颜色
function useChangeColor(){
const color = ref('')
function changeWhite(){
color.value = '#fff'
}
function changeGreen(){
color.value = 'green'
}
return {color,changeWhite,changeGreen}
}
export default {
name: "App",
setup() {
const { isShow, show, hide } = useShow();
const {color,changeWhite,changeGreen} =useChangeColor()
return {
isShow,
show,
hide,
color,
changeWhite,
changeGreen
};
}
};
</script>
<style scoped>
.id {
width: 200px;
height: 200px;
background-color: red;
}
</style>
3.总结
综合上面两个写demo可以得到以下几点
- vue2中你会发现逻辑会很乱,所有的方法都写在methods中,如果data中数据越来愈多,你会发现找数据非常困难
- vue3中采用组合式API(Composition API)看上述代码你会发现,代码越写越麻烦了,但是逻辑思维非常清晰,可以单独封装,逻辑复用, 让功能的代码可以集中抽取到一个函数中
四.setup组合式API
1.setup入口函数
(1)主要内容
- setup 函数是一个新的组件选项,作为组件中组合式API 的起点(入口函数)
- setup 函数只会在组件初始化的时候执行一次
- setup 函数在beforeCreate生命周期钩子执行之前执行,实例还没生成,没有this
- Vue2 如果修改数组的元素 list[0] = 1 页面不会更新, Vue3 解决了该问题
(2)代码演示
export default {
setup () {
console.log('setup执行了')
console.log(this)
},
beforeCreate() {
console.log('beforeCreate执行了')
console.log(this)
}
}
2.响应式API
(1)reactive
作用
reactive是一个函数,接收一个普通的对象传入,把对象数据
转化为响应式对象并返回,用于声明响应式数据的函数
- 参数: 传入数据(必须是引用数据类型)
- 返回值: 实现了响应式的数据 注意: reactive 必须传入引用数据类型的数据, 不能做基本数据类型的响应式操作
示例:
<template>
<div>
{{ msg }}
<button @click="msg = '张三'">点我改 msg</button>
<p v-for="item in list" :key="item">{{ item }}</p>
<button @click="list.push(4)">点我添加数据</button>
<button @click="list[0] = '张三'">点我改索引0的数据</button>
</div>
</template>
<script>
// 1. 导入 reactive 函数
import { reactive } from 'vue'
export default {
setup() {
// 直接声明变量不是响应式的数据, 修改数据时页面不会更新
// let list = [1, 2, 3]
// 2. 在 setup 函数中调用 reactive 函数
// 传入一个引用数据类型的数据, 会将该数据变成响应式的
let list = reactive([1, 2, 3])
let msg = reactive('消息')
// 3. setup 的返回值是一个对象, 将数据放入对象中返回
return {
list,
msg
}
},
};
</script>
(2)ref
作用
ref是一个函数,接受一个简单类型或者复杂类型的传入并返回一个响应式且可变的 ref 对象
- 参数: 传入数据 所有数据类型都支持
- 返回值: 实现了响应式的数据 注意: 在 setup 中使用数据时必须要 .value
示例:
<template>
{{ msg }}
<button @click="msg = '张三'">点我改 msg</button>
<button @click="changeMsg">点我在 setup 中改 msg</button>
<button @click="getMsg">点我在 setup 中取 msg</button>
<p v-for="item in list" :key="item">{{ item }}</p>
<button @click="list.push(4)">点我添加数据</button>
<button @click="list[0] = '张三'">点我改索引0的数据</button>
<button @click="changeList">点我在 setup 中改索引0的数据</button>
</template>
<script>
// 1. 导入 ref 函数
import { ref, reactive } from "vue";
export default {
setup() {
// 直接声明变量不是响应式的数据, 修改数据时页面不会更新
// let list = [1, 2, 3]
// 2. 在 setup 函数中调用 ref 函数
let list = ref([1, 2, 3]);
let msg = ref("消息"); // 将数据放到对象的 value 属性上, 从而实现响应式的效果
console.log("list:", list);
console.log("msg:", msg);
function changeMsg() {
console.log("changeMsg"
// msg = '李四' // 必须加 .value
msg.value = "李四";
}
function getMsg() {
console.log(msg.value);
}
function changeList() {
list.value[0] = "王五";
}
// 3. setup 的返回值是一个对象, 将数据放入对象中返回
return {
list,
msg,
changeMsg,
getMsg,
changeList,
};
},
};
</script>
(3)ref响应函数与reactive响应函数的区别
两个函数都是用于声明响应式数据的函数
- ref支持基本数据类型与引用数据类型
- reactive只支持引用数据类型 注意: ref 其实就是对 reactive 的一层包装,内部调用 reactive 生成一个响应式的对象, 在外面包一个对象
(4)computed
1.作用
根据现有响应式数据经过一定的计算得到全新的数据
- 参数1: 回调函数, 就是计算属性的 get 函数
- 返回值: 计算属性
2.示例
<template>
完整数组: {{ list }}
<br />
所有偶数: {{ evenList }}
<button @click="evenList = '111'">点我修改计算属性</button>
</template>
<script>
// 1. 导入 computed 函数
import { ref, computed } from "vue";
export default {
setup() {
const list = ref([1, 2, 3, 4, 5, 6]);
// 2. 在 setup 中调用 computed 函数来生成计算属性
// const evenList = computed(() => {
// return list.value.filter(item => item % 2 === 0)
// })
// 完整写法, 将传入的函数改为 对象 即可
const evenList = computed({
get() {
return list.value.filter((item) => item % 2 === 0);
},
set(val) {
console.log(val)
},
});
// 3. 返回计算属性
return {
list,
evenList,
};
},
};
</script>
3.vue2写法
// Vue 2 的写法
computed: {
evenList: {
get() {
return '数据'
},
set(val) {}
}
evenList() { // 计算属性的 get 函数
return '测试数据'
}
},
(5)watch
1.作用
基于响应式数据的变化执行回调逻辑,和vue2中的watch的功能一致
- 普通监听
- 立即执行
- 深度监听
- 参数1: source 源 要监听的数据, 传入函数
- 参数2: 数据源发生变化后触发的函数, 传入函数
- 参数3: 配置对象 (deep, immediate)
2.示例:
<template>
总数: {{ count }}
<button @click="count++">点我加加</button>
<hr />
{{ list }}
<button @click="list.push(4)">点我添加数组的数据</button>
</template>
<script>
// 目标: 使用 watch 侦听 count 的变化
// 1. 导入 watch 函数
import { ref, watch } from "vue";
export default {
setup() {
const count = ref(0);
// 2. 在 setup 函数中调用 watch 函数
// watch(() => {
// // 返回你要监听的数据
// // 必须加 value
// return count.value
// }, (newVal) => {
// // 数据变化时会触发
// console.log(newVal)
// })
// 不推荐使用, 容易产生理念冲突, 因为不用加 .value
// watch(count, (newVal) => {
// // 数据变化时会触发
// console.log(newVal)
// })
// 深度侦听: 参数 3 传入一个配置对象
const list = ref([1, 2, 3]);
watch(
() => { // 参数1: source 源 要监听的数据, 传入函数
return list.value;
},
() => { // 参数2: 数据源发生变化后触发的函数, 传入函数
console.log(list.value);
},
{ // 参数3: 配置对象 (deep, immediate)
deep: true, // 深度侦听
immediate: true, // 立即侦听, 页面加载时执行一下侦听函数
}
);
return {
count,
list,
};
},
};
</script>
3.vue2写法
Vue 2 计算属性写法
watch: {
1.简单写法
// list(newVal) {}
2.完整写法
list: {
// 复杂数据类型开启深度侦听
deep: true,
// 开启立即侦听
immediate: true,
handler() {}
}
}
3.生命周期函数
(1)社区翻译的vue3生命周期图示
(2)在 setup 函数中使用生命周期钩子函数
选项式API下的生命周期函数使用 | 组合式API下的生命周期函数使用 |
---|---|
beforeCreate | 不需要(直接写到setup函数中) |
created | 不需要(直接写到setup函数中) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy Vue 3: beforeUnmount | onBeforeUnmount |
destroyed Vue 3: unmounted | onUnmounted |
注意啦:
- vue3 的组合式API中没有提供初始化阶段的两个钩子函数: onBeforeCreate onCreated
- 因为 Vue3 认为这两个阶段要做的事情, 都可以在 setup 里面直接做
(3) 生命周期钩子函数使用场景
生命周期钩子函数 | 应用场景 |
---|---|
created | 发送ajax请求 / 挂载共用属性 |
mounted | 发送ajax请求 / 依赖于dom的业务,比如地图,图表 |
destroyed -> unmounted | 销毁操作,比如定时器 |
4.父子通信
在vue3的组合式API中,父传子的基础套路完全一样,基础思想依旧为:父传子是通过prop进行传入,子传父通过调用自定义事件完成
- 父组件中给子组件传递数据, 属性绑定
- 在子组件中定义 props 接收并使用 props 特点:
- 单向数据流, 单向下行绑定(父亲变了儿子会同步, 但儿子改数据 老子不认)
- 如果传入的是引用数据类型, 修改对象的属性是可行的, 但是 ESLint 默认会检查并报错, 所以需要单独配置或关闭 ESLint
如何关闭eslint
配置vue.config.js
module.exports={
lintOnSave:false
}
反正我是不会关的
实现步骤
- setup函数提供俩个参数,第一个参数为props,第二个参数为一个对象context
- props为一个对象,内部包含了父组件传递过来的所有prop数据,context对象包含了attrs,slots, emit属性,其中的
emit
可以触发自定义事件的执行从而完成子传父
示例:
// setup 钩子函数中有两个参数:
// 参数1: props 父组件传过来的数据都会挂载到这个对象上
// 参数2: context 上下文对象, 有一些全局的实例方法, 例如 emit slots attrs
// setup(props, context) {
setup(props, { emit }) {
// 以前使用 props 数据: this.msg
// 现在没有 this 了, 如果要在 setup 中使用 props 的数据, 则需要接受第一个参数: props
// console.log(props)
// console.log(context)
// this // 没有 this
function sendValue() {
// 在此处触发自定义事件, 携带数据给父组件
// 这里的 this 是指 setup 返回的对象, 所以没有办法调用 $emit 函数
// 故而无法在此处使用 this.$emit() 触发事件, 怎么办呢?
// console.log(this.$emit)
// 参数1: 事件名
// 参数2~n: 要传递的参数
emit('getValue', '这是儿子孝敬您的数据')
// 为什么 Vue2 的实例方法都是 $ 开头?
// 因为 Vue2 设计时所有的方法都放到原型上
// data 和 methods 的成员也会被挂载到对象本身 this.msg
// 如果原型上的成员不用 $ 开头, 很容易命名冲突, 命名冲突之后的结果
// this.$set this.$nextTick() this.$emit() this.$mount()
// Vue3 就没有这样设计了, 以上下文对象的形式暴露给用户使用, compositionAPI设计的出现, 让我们更少的依赖 data 和 methods 了
}
return {sendValue}
5.provide 和 inject
1. 使用场景
通常我们使用props进行父子之间的数据传递,但是如果组件嵌套层级较深,一层一层往下传递将会变的非常繁琐,有没有一种手段可以把这个过程简化一下呢,有的,就是我们马上要学习的provide 和 inject,它们配合起来可以方便的完成从顶层组件向任意底层组件传递数据的效果
2. 基础使用
需求:爷组件中有一份数据 传递给孙组件直接使用 实现步骤
-
顶层组件在setup方法中使用
provide函数
提供数据provide('key',数据)
-
任何底层组件在setup方法中使用
inject函数
获取数据const data = inject('key')
落地代码
爷爷组件 - app.vue
<template>
<father></father>
</template>
<script>
import Father from '@/components/Father'
import { provide } from 'vue'
export default {
components: {
Father
},
setup() {
let name = '张三'
// 使用provide配置项注入数据 key - value
provide('name', name)
}
}
</script>
孙组件 - components/Son.vue
<template>
我是子组件
{{ name }}
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
const name = inject('name')
return {
name
}
}
}
</script>
3. 传递响应式数据
provide默认情况下传递的数据不是响应式的,也就是如果对provide提供的数据进行修改,并不能响应式的影响到底层组件使用数据的地方,如果想要传递响应数据也非常简单,只需要将传递的数据使用ref或者reactive生成即可
<template>
<father></father>
<button @click="changeName">change name</button>
</template>
<script>
import Father from '@/components/Father'
import { provide, ref } from 'vue'
export default {
components: {
Father
},
setup() {
// 使用ref转换成响应式再传递
let name = ref('张三')
function changeName(){
name.value = 'pink'
}
provide('name', name)
return {
changeName
}
}
}
</script>
4.用法
6.TemplateRef
背景知识
在模板中使用ref,我们都很清楚,它一般有三种使用场景
- ref + 普通dom标签 获取真实dom对象 this.$refs.box
- ref + 组件标签 获取组件实例对象 this.$refs.form.validate()
- ref + v-for 获取由dom对象(实例对象)组成的数组 (不经常使用)
实现步骤
- 使用ref函数传入null创建 ref对象 =>
const hRef = ref(null)
- 模板中通过定义ref属性等于1中创建的ref对象名称建立关联 =>
<h1 ref="hRef"></h1>
- 把
hRef
return出去 - 使用 =>
hRef.value
<template>
<!-- Vue2 的做法 -->
<!-- <p ref="cuteP">这是一个可爱的 p</p>
<Father ref="dad"></Father> -->
<!-- 3. 在需要获取对象的标签上使用 ref 绑定返回的数据 -->
<p ref="cp">这是一个可爱的 p</p>
<Father ref="father"></Father>
</template>
<script>
import { onMounted, ref } from 'vue';
// 目标: 在 setup 函数中使用 ref 获取原生 DOM 对象
import Father from './components/Father.vue'
export default {
components: {
Father
},
setup() {
// 1. 使用 ref 创建一个空的数据 用于后面产生关联
const cp = ref(null)
const father = ref(null)
// 此时无法获取, 都是 null, 因为 setup 在 beforeCreate 之前执行
console.log(cp.value)
console.log(father.value)
onMounted(() => {
// 4. 在合适的时机使用 数据.value 即可访问原生 DOM
// 在原生 DOM 挂载完毕后执行
console.log(cp.value)
console.log(father.value)
})
// 2. 将数据返回出去供 template 使用
return {
cp,
father
}
}
// mounted() {
// // Vue2 的做法: 必须在 mounted 中获取
// console.log(this.$refs.cuteP)
// console.log(this.$refs.dad)
// }
};
</script>
五. 非兼容语法
vue3.0对于2.0版本的大部分语法都是可以兼容的,但是也有一些破坏性的语法更新,这个大家要格外注意
1.实例方法$on移除 (eventBus现有实现模式不再支持 可以使用三方插件替代)
event Bus
1. Vue.prototype.$eventBus = new Vue()
2. 接收数据的组件里 this.$eventBus.$on('get-msg',(msg)=>{ })
3. 发送数据的组件里 this.$eventBus.$emit('get-msg','传递的数据')
vue3中默认情况下eventBus模式是不支持的
使用三方插件替代
2.过滤器filter移除 (插值表达式里不能再使用过滤器 可以使用methods替代)
filter过滤器
字符串的格式化 方法 接收原字符串 返回格式化之后的字符串
{{ msg | formatMsg }}
vue3直接移除了该语法 可以直接使用methods替代
{{ formatMsg('this is msg') }} // 渲染的结果是什么呢? 函数return值
methods:{
formatMsg(msg){
return msg + 'zs'
}
}
3.sync语法移除 (和v-model语法合并)
.sync语法
elementUI -> Dialog visible.sync="showFlag"
作用: 简化父子通信 自定义事件的触发
.sync -> v-model(重点)
如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请著明出处,如果有问题也欢迎私信交流
转载自:https://juejin.cn/post/7035805730372321294