创建vue3
vue-cli创建
确保@vue/cli版本在4.5.0以上 vue --version 或vue -V 创建 vue create 名称
vite创建
使用vite创建能够提高项目响应速度,可快速冷启动,真正的按需编译
npm init vite-app 名称
文件介绍
主要说明一些和vue2不同的
mian
//引入的不再是vue的构造函数,而是一个名为createApp的工厂函数 import { createApp } from 'vue' /*创建应用实例对象(createApp(App) 类似于vue2的vm) 将该实例对象挂载(.mount('#app'))*/ createApp(App).mount('#app')
常用组合式API
setup
setup为vue3的一个配置项,值为一个函数,组件用到的数据,方法等,都需要在setup中配置
<script> export default{ name:'APP', setup(){ let name='张三', let age=23, function sayHello(){ console.log(`你好啊,我是${name},我${age}岁了`) } } } </script>
setup函数的返回值,若返回一个对象,那么里面的内容可用在模板中直接使用
<template> <h2> 姓名:{{name}} 年龄:{{age}} </h2> <button @click='sayHello'>说话</button> </template> <script> export default{ name:'APP', setup(){ let name='张三', let age=23, function sayHello(){ console.log(`你好啊,我是${name},我${age}岁了`) }, return{ name, age, sayHello } } } </script>
若返回一个渲染函数,可以自定义渲染内容,需要先引入
<template> <h2> 姓名:{{name}} 年龄:{{age}} </h2> </template> <script> //引入渲染函数 import {h} from 'vue' export default{ name:'APP', setup(){ let name='张三', let age=23, function sayHello(){ console.log(`你好啊,我是${name},我${age}岁了`) }, //此时页面展示的是尚硅谷,上面写再多东西也不管用 return ()=>h('h1','尚硅谷') } } </script>
注意:setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到看不到promise的内容
后面可以返回promise,但是需要Suspense和异步组件配合
setup参数
-
两个注意点
-
setup执行时机
-
在beforeCreate配置项之前执行一次,this的值为undefined
-
-
setup的参数
-
props:值为对象,包含组件外部传递来,且组件内部声明接受了的属性
-
context:上下文对象
-
attrs:值为对象,包含组件外部传递过来,但是没有在props配置中声明的属性,相当于vue2的this.$attrs
-
slots:收到的插槽内容,相当于vue2的this.$slots
-
emit:触发自定义事件的函数 相当于vue2的this.$emit、
-
-
-
<!--APP.vue--> <template> <!--传递默认插槽--> <Demo msg='你好啊' school='尚硅谷' @hello='show'> <span>尚硅谷</span> </Demo> <!--传递具名插槽--> <Demo msg='你好啊' school='尚硅谷' @hello='show'> <!--设置插槽名称为qwe--> <template v-slot:qwe> <span>尚硅谷</span> </template> </Demo> </template> <script> import Demo from './Demo' export default{ name:'APP', components:{Demo}, setup(){ function show(value){ alert(`你好,接收到参数${value}`) } return{ show } } } </script> <!--Demo.vue--> <template> <h1>一个人的信息</h1> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <button @click='text'>触发hello事件</button> <!--默认插槽位置--> <slot></slot> <!--具名插槽位置--> <slot name='qwe'></slot> </template> <script> import {reactive} from 'vue' export default{ name:'Demo', props:['msg','school'], //明确在父组件中给该组件绑定了什么事件 emits:['hello'], setup(props,context){ //输出为{msg:'你好啊',school:'尚硅谷'} 就是父组件传递的数据,并将其整理为一个对象,是一个proxy对象,可以实现响应式 console.log(props) //输出为空,因为父组件传递的数据props都接收了 console.log(context.attrs) //触发在父组件中设置的自定义hello事件,传递参数666 function test(){ context.emit('hello',666) }, let person=reactive({ name:'张三', age:18 }), return{ person } } } </script>
ref函数
用于定义响应式数据 需要引入该函数
使用ref定义的数据,在js中需要使用.value来读取,在模板中不需要
<template> <h2>名字:{{name}}</h2> <button @click='changeName'>修改名字</button> </template> <script> import {ref} from 'vue' export default{ name:'APP', setup(){ let name=ref('李四'), function changeName(){ name.value='王五' } return {name,changeName} } } </script>
ref函数接收的数据可以是基本类型,也可以是对象类型
定义的对象类型数据也需要使用.value进行访问
<template> <h2>名字:{{job.name}}</h2> <h2>薪水:{{salary}}</h2> <button @click='changesalary'>修改薪水</button> </template> <script> export default{ name:'APP', setup(){ let job=ref({ name:'张三', salary:'10K' }) function changesalary(){ job.value.salary='12K' } return {job,changesalary} } } </script>
reactive函数
定义一个对象类型的响应式数据
reactive接收一个对象,返回一个代理对象
使用reactive定义的对象均不需要使用.value调用
<template> <h2>名字:{{job.name}}</h2> <h2>薪水:{{salary}}</h2> <button @click='changesalary'>修改薪水</button> </template> <script> export default{ name:'APP', setup(){ let job=reactive({ name:'张三', salary:'10K' }) function changesalary(){ job.value.salary='12K' } return {job,changesalary} } } </script>
reactive对比ref
-
从定义数据角度对比
ref用来定义基本数据类型
reactive用来定义对象或数组类型数据
ref也可以用来定义对象或数组类型数据,他内部会自动通过reactive转为代理对象
-
从原理角度对比
ref通过Object.defineProperty()的get和set来实现响应式(数据劫持)
reactive通过使用Proxy(数据劫持)来实现响应式,并通过Reflect操作源对象内部的数据
-
从使用角度对比
ref定义的数据,操作数据需要通过.value读取,模板中读取数据不需要
reactive定义的数据,操作数据和读取数据均不需要
响应式原理
vue2响应式
实现原理:
对象类型:通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了重写)。
存在问题:
新增属性,删除属性,数据修改了,但是界面不会更新
直接通过下标修改数组,界面也不会更新,因为vue2检测不到
解决方法:
使用this.$set()直接告诉vue我要添加数据了,除此之外还有this.$delete()移除数据,
引入Vue,使用Vue.set()或Vue.delete()也可以实现
<template> <h2>名字:{{job.name}}</h2> <h2>爱好:{{person.bobby}}</h2> <h2 v-show='job.salary'>薪水:{{job.salary}}</h2> <button @click='addSalary'>添加薪水</button> <button @click='deleteSalary'>删除薪水</button> <button @click='updateSalary'>修改爱好</button> </template> <script> export default{ name:'APP', data(){ return { person:{ name:'张三', bobby:['睡觉','打游戏'] } } }, methods:{ addSalary(){ this.$set(this.person,'salary','20K') }, deleteSalary(){ this.$delete(this.person,'salary') }, //修改数组下标为0的数据为打豆豆 updateSalary(){ this.$set(this.person,0,'打豆豆') } } } </script>
原理:
//源数据 let person={ name:'张三', age:18 } //模拟实现响应式 let p={} Object.defineProperty(p,'name',{ get(){//有人读取name时调用 return person.name }, /*单纯进行删除添加操作,不会触发set方法*/ set(value){//有人修改name时调用 person.name=value console.log('有人修改了name,我要去修改页面') } })
vue3响应式
实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作
<template> <h2>名字:{{job.name}}</h2> <h2>薪水:{{salary}}</h2> <button @click='updteSalary'>添加薪水</button> </template> <script> export default{ name:'APP', setup(){ let job=ref({ name:'张三', }) function updteSalary(){ job.salary='12K' } return {job,updteSalary} } } </script>
原理
//源对象 let person={ name:'张三', age:19 } //p是代理对象 const p=new Proxy(person,{ //target参数是传入的源对象 propName参数是你读取的参数 get(target,propName){ console.log(`有人读取了p上的${propName}属性`) return target[propName] }, // 拦截修改/追加属性 value参数是传入的数据 set(target,propName,value){ console.log(`有人修改了p上的${propName}属性,我要去更新页面`), target[propName]=value }, //拦截删除属性 deleteProperty(target,propName){ console.log(`有人删除了p上的${propName}属性,我要去更新页面`), return delete target[propName] } })
reflect(反射)
对于上面的target[propName]和target[propName]=value,vue底层做了更深度的操作,不是仅仅将数据拿出来,修改这么简单
相比于Object.defineProperty,reflect通过返回值可以判断当前操作是否成功,而Object.defineProperty只能通过try,catch捕捉,而try,catch捕捉会停止后面代码运行,而这对于框架开发显然是不合适的,不能因为一点错误整个框架就停止运行了,而reflect可以通过返回值判断,来告诉所有者当前操作失败,不影响框架的运行。
let obj={ a:1, b:2 } /*vue的底层操作就是使用了reflect进行*/ //原本底层是通过Object.defineProperty实现 try{ Object.defineProperty(obj,'c',{ get(){ return 3 } }) Object.defineProperty(obj,'c'{ get(){ return 4 } }) }catch(error){ console.log(error) } //vue3使用reflect实现 const x1=Reflect.defineProperty(obj,'c'{ get(){ return 3 } }) const x2=Reflect.defineProperty(obj,'c'{ get(){ return 4 } }) if(x2){ console.log('操作成功') }esle{ console.log('操作失败') }
计算属性
computed:需要从vue中引入使用
计算属性需要写在setup内,通过return返回后可以在模板中直接使用
<template> 姓:<input type='text' v-model='person.firstName'><br> 名:<input type='text' v-model='person.lastName'> 全名:{{fullName}} <!--对计算属性进行修改--> 全名:<input type='text' v-model='fullName'> </template> <script> import {computed} from 'vue' export default{ name:'APP', setup(){ let person=reactive({ firstName:'', lastName:'' }), //计算属性简写(没有考虑计算属性被修改,只考虑读取) let fullName=computed(()=>{ return person.firstName+'-'+person.lastName }), //计算属性完整写法(考虑读取和修改) let fullName=computed({ get(){//读取 return person.firstName+'-'+person.lastName }, set(value){//修改 const nameArr=value.split('-'), person.firstName=nameArr[0], person.lastName=nameArr[1] } }) return { person, fullName } } } </script>
Watch监视
watch:用于监视数据的变换,从而做出相应的操作
监视ref数据
<template> <h2>当前求和为:{{sum}}</h2> <button @click='sum+1'>点我+1</button> <h2>当前减一为:{{qum}}</h2> <button @click='qum-1'>点我-1</button> </template> <script> import {ref,watch} from 'vue' export default{ name:'APP', setup(){ let sum=ref(0), let qum=ref(100), //监听单个ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum改变了',newValue,oldValue) },{immediate:true,deep:true}),//监视的配置项,开启一上来就执行和深度监视 //监视多个ref定义的响应式数据,也可以定义两个watch分开 wacth([sum,msg],(newValue,oldValue)=>{ console.log('sum或qum改变了',newValue,oldValue) },{immediate:true,deep:true}) return { sum, qum } } } </script>
监视ref定义的对象数据
直接监视ref定义的对象数据,监视会失败,因为此时监视的是ref生成的对象,包含了许多内置对象和方法,而我们想监视的数据在value内,只有value的数据完全被替换掉,才会监视成功。
方法一:使用.value,就是直接监视ref生成的对象的value属性,就是了我们想要监视的数据
方法二:开始深度监视,这样value不需要完全被替换掉也能监视成功
<template> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.month.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.month.salary++">涨薪</button> </template> <script> import {ref,watch} from 'vue' export default{ name:'APP', setup(){ let person=ref({ name:'张三', age:18, job:{ month:{ salary:20 } } }) //方法一:使用.value watch(person.value,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) //方法二:开启深度监视 watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{deep:true}), return { person } } } </script>
监视reactive数据
监听reactive定义的响应式数据无法获取正确的oldValue
监视reactive定义的响应式数据中的属性可以获取正确的oldValue,但是如果该属性为对象,则也无法获取正确的oldValue
监听reactive定义的响应式数据强制开启了深度监视,deep配置无效,就会有效率问题
监听reactive定义的响应式数据中的一个属性,如果该属性是对象,那么deep配置有效,且如果是深层次的对象,那么就必须手动开始深度监视才能监听到对象内部
<template> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.month.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.month.salary++">涨薪</button> </template> <script> import {,watch} from 'vue' export default{ name:'APP', setup(){ let person=reactive({ name:'张三', age:18, job:{ month:{ salary:20 } } }) //1.监视reactive定义的响应式数据中的全部属性,oldValue获取错误 watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{deep:false}) //此处的deep配置不再奏效 //2.监视reactive定义的响应式数据中的某个属性,oldValue获取成功 watch(()=>person.name,(newValue,oldValue)=>{ console.log('person的name变化了',newValue,oldValue) }) //3.监视reactive定义的响应式数据中的某些属性,oldValue获取成功 watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{ console.log('person的age或name变化了',newValue,oldValue) }), //4.特殊情况,监视reactive定义的响应式数据中的某个对象,oldValue获取错误 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}), return { person } } } </script>
watchEffect
watchEffect不需要指定监视对象,内部智能判断监视对象,其内部用到了谁,就会监视谁,也能监视多层次数据
watchEffect有点像computed,只不过watch注重结果,watchEffect注重过程
watchEffect默认开启了immediate
<template> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.month.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.month.salary++">涨薪</button> </template> <script> import {ref,watchEffect} from 'vue' export default{ name:'APP', setup(){ let person=ref({ name:'张三', age:18, job:{ month:{ salary:20 } } }), watchEffect(()=>{ const x1=sum.value const x2=person.job.month.salary console.log('回调执行') }), return { person } } } </script>
生命周期
-
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,这是使用配置项形式,但有有两个被更名:
-
beforeDestroy
改名为beforeUnmount
-
destroyed
改名为unmounted
-
-
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
使用API形式的生命周期需要放在setup里面,当然使用API也需要从vue中引入
-
beforeCreate
===>setup()
-
created
=======>setup()
-
beforeMount
===>onBeforeMount
-
mounted
=======>onMounted
-
beforeUpdate
===>onBeforeUpdate
-
updated
=======>onUpdated
-
beforeUnmount
==>onBeforeUnmount
-
unmounted
=====>onUnmounted
-
自定义hook函数
-
hook本质是一个函数,把setup函数中使用的Composition API进行了封装。
-
类似于vue2.x中的mixin。
-
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
//hook函数 import {reactive,onMounted,onBeforeUnmount} from 'vue' export default function(){ let point=reactive({ x:0, y:0 }), function savePoint(event){ point.x=event.pageX, point.y=event.pageY, console.log(event.pageX,event.pagY) }, onMounted(()=>{ //给window上添加click事件 window.addEventListener('click',savePoint) }), onBeforeUnmount(()=>{ //卸载window上的click事件 window.removeEventListener('click',savePoint) }) return point }
<!--使用hook函数--> <template> <h2>当前点击鼠标坐标为x:{{point.x}},y:{{point.y}}</h2> </template> <script> //引入hook文件 import usePoint from './hooks' export default{ name:'APP', setup(){ let point=usePoint(),//usePoint返回值就是xy坐标对象 return {point} } } </script>
toRef
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(person,'name')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。相当于计算属性,可以在插值语法中少写
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
-
但是只会将响应式对象的第一层数据提供出去
<template> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="salary++">涨薪</button> </template> <script> import {ref,toRef} from 'vue' export default{ name:'APP', setup(){ let person=ref({ name:'张三', age:18, job:{ month:{ salary:20 } } }), //这样取出来的是ref响应式数据,其中这里的数据还是person内数据,toRef就是去person寻找 const name=toRef(person,'name') const age=toRef(person,'age') return { //这样写只是赋值,是一个字符串,不具有响应式 name:person.name, age:person.name, salary:person.job.month.salary, //toRef具有响应式,这样在模板中就可以直接使用,不需要person.形式了 name:toRef(person,'name'), age:toRef(person,'age'), salary:toRef(person.job.month,'salary') //toRefs 这里的torefs返回是一个对象,对象内不能返回对象,使用扩展运算符将其展开 //toRefs会将参数对象内数据全部变成响应式数据 //toRefs会展开对象内的第一层数据,第一层数据就可以直接使用,深层数据还是需要person.的形式 ...toRefs(person) } } } </script>
其他组合式API
shallowReactive与shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
-
如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
-
如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
-
<template> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{person.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="person.salary++">涨薪</button> </template> <script> import {toRef,shallowReactive,shallowRef} from 'vue' export default{ name:'APP', setup(){ //使用shallowReactive定义响应式数据,只会定义第一层响应式数据,这里的salary就不是响应式数据了 let person=shallowReactive({ name:'张三', age:18, job:{ month:{ salary:20 } } }), //使用shallowRef定义响应式数据,不会去处理对象类型数据,即对象类型数据不是响应式数据 let person=shallowRef({ name:'张三', age:18, job:{ month:{ salary:20 } } }), return { ...toRefs(person) } } } </script>
readonly与shallowReadonly
-
readonly: 让一个响应式数据变为只读的(深只读)。
-
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
-
应用场景: 不希望数据被修改时。
<template> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{person.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="person.salary++">涨薪</button> </template> <script> import {toRef,shallowReadonly,readonly,reactive} from 'vue' export default{ name:'APP', setup(){ let person=reactive({ name:'张三', age:18, job:{ month:{ salary:20 } } }), //readonly:接收一个响应式数据,将它变成只读的,不可修改 person=readonly(person), //shallowReadonly:接收一个响应式数据,将它的第一层数据变成只读的,不可修改 //深层次的数据不受影响,还是可以修改 person=shallowReadonly(person), return { ...toRefs(person) } } } </script>
toRaw 与 markRaw
-
toRaw:
-
作用:将一个由
reactive
生成的响应式对象转为普通对象,不能操作ref定义的数据。 -
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
-
-
markRaw:
-
作用:标记一个对象,使其永远不会再成为响应式对象。
-
应用场景:
-
有些值不应被设置为响应式的,例如复杂的第三方类库等。
-
当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
-
-
<template> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{person.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="person.salary++">涨薪</button> <h2>汽车:{{person.car}} 价格:{{person.price}}</h2> </template> <script> import {toRef,shallowReadonly,readonly,reactive} from 'vue' export default{ name:'APP', setup(){ let person=reactive({ name:'张三', age:18, job:{ month:{ salary:20 } } }), function showRawPerson(){ //将响应式对象变成普通对象,修改该对象不会影响页面 console.log(toRow(person)) }, function addCar(){ let car={name:'奔驰',price:200000} //将对象标记,使其永远不会成为响应式对象 person.car=markRaw(car) } return { person, ...toRefs(person) } } } </script>
customRef
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
自定义ref实现防抖
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义ref //第一个参数是追踪器,告诉vue要追踪数据的变换,第二个参数是触发器,停止vue重新解析模板 return customRef((track,trigger)=>{ return{ get(){ //告诉Vue这个value值是需要被“追踪”的,让vue认为该数据有用 track() return value }, //参数是修改时传入的值 set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue //触发器,告诉Vue去更新界面 trigger() },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
只要父组件使用了provide传递数据,那么该父组件的全部后代都可以使用inject接收数据,但是一般不用于父子
-
具体写法:
<!--祖先组件--> <template> <div> <h3>我是APP组件(父) {{name}}--{{price}}</h3> <Child/> </div> </template> <script> import {reatuve,toRefs,provide} from vue import Child from './Child.vue' export default{ name:'APP', compontents:{Child}, setup(){ let car=reative({ name:'奔驰', price:'70W' }) //给后台组件传递数据,传递car,名为car provide('car',car) return {...toRefs(car)} } } </script> <!--子组件--> <template> <div> <h3>我是Child组件(子) {{car.name}}--{{car-price}}</h3> <Sun/> </div> </template> <script> import {inject} from vue export default{ name:'Child', setup(){ //获取父组件传递的数据 let car=inject('car') return {car} } } </script> <!--后代组件--> <template> <div> <h3>我是sun组件(孙) {{car.name}}--{{car-price}}</h3> </div> </template> <script> import {inject} from vue export default{ name:'Sun', setup(){ //获取父组件传递的数据 let car=inject('car') return {car} } } </script>
判断响应式数据
-
isRef: 检查一个值是否为一个 ref 对象
-
isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 -
isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 -
isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
组合式API的优势
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
sition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
新的组件
Fragment
在vue2中,组件必须有一个根标签
Vue3中可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
好处: 减少标签层级, 减小内存占用
<!--vue2:需要div这个根标签--> <template> <div> <h3></h3> <h3></h3> <h3></h3> </div> </template> <!--vue3不需要--> <template> <h3></h3> <h3></h3> <h3></h3> </template>
Teleport
Teleport是一种能够将我们的组件html结构移动到指定位置的技术
一般用于将深层次的组件移出,放到外面展示,方便进行定位,不然深层次的定位会受到祖先组件的影响
<!--祖先组件--> <template> <div class='app'> <h3>我是APP组件(祖)</h3> <Child/> </div> </template> <script> import Child from './Child.vue' export default{ name:'APP', compontents:{Child}, } </script> <style> .app{ background-color:blue; } </style> <!--子组件--> <template> <div class='child'> <h3>我是Child组件(子)</h3> <Sun/> </div> </template> <script> import Sun from './sun.vue' export default{ name:'Child', compontents:{Sun}, } </script> <style> .child{ background-color:red; } </style> <!--后代组件--> <template> <div class='sun'> <h3>我是sun组件(孙)</h3> <Dialog/> </div> </template> <script> import Dialog from './dailog.vue' export default{ name:'Sun', compontents:{Dialog} } </script> <style> .sun{ background-color:orange; } </style> <!--对话框组件--> <template> <div class='dailog'> <button @click='isShow=true'>点我弹出弹窗</button> <!--使用teleport将内部内容移动到body标签下,这样定位就可以参考body来编写--> <!--即脱离了它的祖先组件,独立出去--> <!--to后面也可以写css选择器--> <teleport to='body'> <div class='mack' v-if='isShow'> <div class='dialog'> <h1>弹窗</h1> <button @click='isShow=false'>点我关闭弹窗</button> </div> </div> </teleport> </div> </template> <script> import {ref} from 'vue' export default{ name:'Dialog', setup(){ let isShow=ref(false), return {isShow} } } </script> <style> /*下面的定位全部是参考body来的,不是父组件sun*/ .dailog{ position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:300px; height:300px; text-align:center; background-color:green; } /*遮罩层*/ .mask{ position:absolute; top:0;bottom:0;left:0;right:0; background-color:rgba(0,0,0,0.5); } </style>
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
即使用异步引入组件时,先展示了加载完成的内容,但是用户在某些页面可能不知道你还有内容没有加载完成,那么外面就可以使用Suspense在未加载完成的内容位置额外先渲染一些内容,让用户知道还有内容没有加载完成
Suspense是基于插槽实现的,内部提高了两个插槽
<template v-slot:default></template> 用于放在你要展示的内容
<template v-slot:fallback></template> 用于展示在加载完成之前的内容
使用异步引入组件,引入的组件的setup可以返回一个promise对象
<!--祖先组件--> <template> <div class='app'> <h3>我是APP组件(祖)</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中...</h3> </template> </Suspense> </div> </template> <script> //针对网络慢,只要子组件不引入成功,整个页面就都不会展示出来 //import Child from './Child.vue' //静态引入 import {defineAsyncComponent} from 'vue' //使用动态引入,会先展示加载完成的,再展示后加载的,给用户更好的体验 const Child = defineAsyncComponent(()=>import('./Child.vue'))//动态引入(异步引入) import Child from './Child.vue' export default{ name:'APP', compontents:{Child}, } </script> <style> .app{ background-color:blue; } </style> <!--子组件--> <template> <div class='child'> <h3>我是Child组件(子)</h3> </div> </template> <script> export default{ name:'Child', } </script> <style> .child{ background-color:red; } </style>
其他
全局API的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
其他改变
-
data选项应始终被声明为一个函数。
-
CSS过渡类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作为 v-on 的修饰符(即不能通过按键编码获取键盘按键),同时也不再支持
config.keyCodes
(设置键盘按钮别名) -
移除
v-on.native
(自定义事件转原生事件,如@click.native='show')修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close']//在子组件中声明,vue就认为该事件是自定义事件,click没有声明,vue就认为是原生事件 } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
-
......
-