Vue3复习

创建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')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。相当于计算属性,可以在插值语法中少写

  • 扩展:toRefstoRef功能一致,但可以批量创建多个 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:

    • 作用:标记一个对象,使其永远不会再成为响应式对象。

    • 应用场景:

      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。

      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

<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(Vue3.x 实例 API (app)
      Vue.config.xxxxapp.config.xxxx
      Vue.config.productionTip移除
      Vue.componentapp.component
      Vue.directiveapp.directive
      Vue.mixinapp.mixin
      Vue.useapp.use
      Vue.prototypeapp.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” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

    • ......

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值