vue3的

本文回顾了Vue的使用,详细介绍了Vue3中新增的组件通信方式如props+emit、provide/inject,路由配置,Vuex状态管理,全局挂载,setup语法以及V3响应式原理。同时,探讨了Vue2的响应式机制,包括数据劫持和依赖收集,并讨论了Vue3中的代理和反射机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回忆

1 v3 特点: 高性能  按需引入  组合式api  新组件(fragment) 
2 v3的舞台 setup 
3 import {reactive,ref,toRef,toRefs,watch,computed,watchEffect,on..} from 'vue'	

补充vue中使用scss

<style lang="scss">
    #demo{
        ul{
            list-style-type: none;
        }
    }

</style>

练习:todolist v3版:



<template>
    <div id="demo">
        <input type="text" v-model="data.t" @keyup.enter="add">
        <ul>
            <li :class="{done:item.done}" v-for="item in list_" :key="item.id">
                <a href="" @click.prevent="item.done=!item.done"> {{item.title}}</a>
                &nbsp;
                <a href="" @click.prevent="del(item.id)">删除</a>
            </li>
        </ul>

        <template v-for="item in data.menus" :key="item">
            <span v-if="data.current==item">{{item}}</span>
            <a href="" @click.prevent="data.current=item" v-else>{{item}}</a>
             &nbsp;
        </template>
    </div>
</template >

<script setup>
import {computed, reactive, ref, toRefs} from 'vue'



//数据
let data=reactive({
    list:[
        {id:1,title:"打游戏",done:false},
        {id:2,title:"吃豆豆",done:true},
        {id:3,title:"睡觉觉",done:false},
    ],
    id:4,
    menus:["全部","已完成","未完成"],
    current:"全部",
    t:""
})

//方法
let add=()=>{
    data.list.push({
        id:data.id++,
        title:data.t,
        done:false
    })
}

let del=id=>{
    data.list=data.list.filter(item=>item.id!=id)
}

//计算属性
let list_=computed(()=>{
    switch(data.current){
        case "全部":
            return data.list
        case "已完成":
            return data.list.filter(item=>item.done)
        case "未完成":
             return data.list.filter(item=>!item.done)
    }
})


</script>

<style scoped lang="scss">
    #demo{
        ul{
            list-style-type: none;
        }

        .done a{
            color:red;
            text-decoration: line-through;
        }
        a{
            text-decoration: none;
        }
    }




</style>

组件通信

1 父子:props+emit

1.1子组件通过defineProps来接受

语法:

import {defineProps} from 'vue'

let xx=defineProps(['自定义属性',....])
渲染和js使用:xx.自定义属性

Father.vue:

 <Son :name="name"/>
 
<script setup>
import Son from './Son.vue'
import {ref} from 'vue'
let name=ref('张三')
</script>

Son.vue

<template>
    <div>
            son  
            {{name}}--{{props.name}}
    </div>
</template>

<script setup>
    import {defineProps} from 'vue'
    //接受
    let props=defineProps(['name']);
    //在js中使用
    console.log(props.name);
    
</script>

完整版:
defineProps({
	name:{
		type:String,
		required:true,
		default:""
	}
});

1.2子组件通过defineEmits来调用父组件方法

语法:

  import {defineEmits} from 'vue'
  let xx=defineEmits(['自定义属性'])
  xx('自定义属性',参数)
  

Father.vue

 <Son   @change="change"/>
 
 let change=params=>{
    name.value=params
}

Son.vue

 import {defineEmits} from 'vue'
 
 
     //自定义事件
    let emits=defineEmits(['change'])

    let ff=()=>{
        emits('change','李四')
    }

2 爷孙:provide 、inject

语法:

provide  用于父组件的存值    --  provide("k",v)

inject 用于子组件的获取值    --  inject("k")

特点:

爷爷组件传递过来的参数是可以逆向更改的,和单向数据流无关

爷爷组件:

import {provide, ref} from 'vue'

let age=ref(18)
provide("age",age)

孙子组件

    import { inject} from 'vue'
    let age=inject("age")

路由

设置路由模式

import { createRouter, createWebHashHistory ,createWebHistory} from 'vue-router'

hash模式:
const router = createRouter({
  history: createWebHashHistory(),
  routes
})

history模式:
const router = createRouter({
  history: createWebHistory(),
  routes
})

路由跳转和传参

跳转:

import {useRouter} from 'vue-router'
let router=useRouter();

router.push.....

传参:

import {useRoute} from 'vue-router'
let route=useRoute()

route.query/params.......

状态机vuex

组件中引入store对象,其他和v2完全一样

import {useStore} from 'vuex'
let store=useStore();

挂载全局

v2 :main.js  Vue.prototype.api=api

v3需要用到自己的api

main.js:


//引入api
import api from './http/api'

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

let app=createApp(App);
//全局挂载
app.config.globalProperties.api=api //类似于Vue.prototype.api=api

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

组件:

import {getCurrentInstance} from 'vue'
let {proxy}=getCurrentInstance()
console.log(proxy.api);

关于setup的另一种用法

语法1:

 <script>

 	export default {
 		setup(){
 			//定义的数据、方法、计算属性。。。
 			return {
 				返回的一定是template中用到东东
 			}
 		
 		}
 	}
 
 

Demo:

<script >
   import {computed, reactive, ref,onMounted,toRefs} from  'vue'
    export default {
        setup(){

            //基本数据
            let name=ref('张三')
            let user=reactive({
                username:"aaa",
                password:"bbb",
                n1:1,
                n2:2
            })

            //计算属性
            let sum=computed(()=>{
                return user.n1+user.n2
            })

            //方法
            let change=()=>{
                name.value="李四"
            }

            onMounted(()=>{
                console.log("加载完毕");
            })



            return {
                name,
                user,
                sum,
                change,
                ... toRefs(user)
            }
        }
    }
</script>

语法2:如果是子组件父组件的参数

    export default {
        props:['name'], //自定义数据
        setup(props){
            
            console.log(props.name);

            return {
                name:props.name
            }

        }
    }

补充:vite 脚手架 单独安装 router vuex scss

scss:

npm i sass-loader -D

如果是下载报错1 ,尝试带镜像
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

如果是版本错误,尝试带版本下载
npm i node-sass@1.26.5

vuex:

npm i vuex@next

router

npm i vue-router@next

记住:vuex和router下载完毕后需要手动配置文件(router/index.js store/index.js main.js)

hook函数

hook就是钩子函数的意思:钩子函数特点能够在某些条件中,自动触发
自定义hook函数:把setup函数中使用的组合式api进行封装,最终暴露一个函数---就是一个封装,增加了复用性
类似于v2的 混入(mixins)

定义hook函数 习惯取名: useXxx...

总结:我们能够在任意js文件中用到 v3提供的组合式api(computed, reactive, ref,onMounted,toRefs........)

Demo1:

组件中,有一个按钮,和一个{username:“aaa”,},点击按钮后,能够动态更改username

useUsername.js


import {reactive,onMounted} from  'vue'
export default function(t){

   //数据
   let user=reactive({
        username:"aaa",
   })


   onMounted(()=>{
        //事件的注册
        let btn=document.getElementById("btn")
        btn.addEventListener("click",()=>{
            user.username=t.value
        })
   })

   return user;

}

组件:

<template>
    <div>
        <h1>home</h1>
        
        {{user}}
        <input type="text" v-model="t">
        <button id="btn">更改</button>


    </div>
</template>

<script setup>
  import {reactive, ref} from 'vue'
  import useUsername from '../hooks/useUsername.js'
  let t=ref("")
  let user=useUsername(t)

demo2:

Father.vue

​ 有一个按钮,可以切换Son组件的隐藏和显示

Son.vue

​ 点击网页的任何地方,实时渲染出x和y的坐标 -------》hook封装·

usePoint.js

import { reactive, onMounted, onUnmounted } from "vue";
export default function () {

    let point = reactive({
        x: 0,
        y: 0
    })

    let getPoint= e => {
        console.log(e.pageX, e.pageY);
        point.x = e.pageX;
        point.y = e.pageY
    }

    //事件的注册
    onMounted(() => {
        window.addEventListener("click",getPoint )
    })


    //事件的销毁
    onUnmounted(() => {
        window.removeEventListener('click',getPoint)
    })



    return point
}

Father.vue:

<template>
    <div>

        Father 
        <button @click="isb=!isb">切换</button>
        <Son v-if="isb" />

    </div>
</template>

<script setup>
import Son from './Son.vue'
import {ref}  from 'vue'
let isb=ref(true)




</script>

Son.vue

<template>
    <div>
            son  
            <hr>
           {{point}}
            
    </div>
</template>

<script setup>
import  usePoint from '../hooks/usePoint.js'
let point=usePoint();

组合式api的优势

组合式api搭配hook函数 使得项目结构清晰变得易维护

V3的新组件

1 Fragment

   碎片  每一个template中可以不写根节点,Fragment就是一个虚拟元素
	
   好处:减少了层级,减少内存占用

2 Teleport (瞬移)

语法:

<teleport to="选择器">
		
	布局...

</teleport>

作用:

1 能够使 teleport包裹的元素瞬移到对应选择器中
2 选择器指的:id选择  class选择器  标签选择器
3 只能瞬移到index.html中的其他位置

vue2的响应式原理

什么技术 ---》 vue中数据驱动(数据改变、则页面自动渲染)

1 监听数据的改变----》数据劫持

Object.defineProperty()

Demo1: 基本使用

let user={
    name:"张三"
}

let value=user.name;//张三

//劫持user对象的name属性
Object.defineProperty(user,"name",{
    //获取使触发
    get(){
        console.log("有人在获取user的name属性");
        return value
    },
    //更改时触发
    set(val){
        console.log("有人在改变user的name属性,更改之后的值是"+val);
        value=val;
    }
})

console.log(user.name);//张三

user.name='李四'

console.log(user.name);//李四







Demo2: 封装方法

let user = {
    name: "张三",
    age: 19
}


//封装了数据劫持的方法
function defineReactive(data,k,v) {
    //劫持user对象的name属性
    Object.defineProperty(data, k, {
        //获取使触发
        get() {
            console.log("有人在获取user的name属性");
            return v
        },
        //更改时触发
        set(val) {
            console.log("有人在改变user的name属性,更改之后的值是" + val);
            v = val;
        }
    })
}

//劫持user的name
defineReactive(user,"name",user.name)
//劫持user的age
defineReactive(user,"age",user.age)




console.log(user.name);//张三

user.name = '李四'

console.log(user.name);//李四



console.log(user.age);
user.age=1000;
console.log(user.age);







Demo3:

let user = {
    name: "张三",
    age: 19
}


//封装了数据劫持的方法
function defineReactive(data,k,v) {
    //劫持user对象的name属性
    Object.defineProperty(data, k, {
        //获取使触发
        get() {
            console.log("有人在获取user的name属性");
            return v
        },
        //更改时触发
        set(val) {
            console.log("有人在改变user的name属性,更改之后的值是" + val);
            v = val;
        }
    })
}


//再次封装
function observer(data){

    for(let k in data){
        //k就是属性 name
        //data[j] 就是属性值  张三
        defineReactive(data,k,data[k])
    }
}

observer(user)



console.log(user.name);//张三

user.name = '李四'

console.log(user.name);//李四



console.log(user.age);
user.age=1000;
console.log(user.age);

demo4:

let user = {
    name: "张三",
    age: 19,
    dog:{
        type:"金毛",
    }
}


//封装了数据劫持的方法
function defineReactive(data,k,v) {
    //递归
    observer(v)
    //劫持user对象的name属性
    Object.defineProperty(data, k, {
        //获取使触发
        get() {
            console.log("有人在获取user的name属性");
            return v
        },
        //更改时触发
        set(val) {
            console.log("有人在改变user的name属性,更改之后的值是" + val);
            v = val;
        }
    })
}


//再次封装
function observer(data){
     
    //不是对象
    if(typeof data !='object'){
        return data
    }


    for(let k in data){
        //k就是属性 name
        //data[j] 就是属性值  张三
        defineReactive(data,k,data[k])
    }
}

observer(user)




// console.log(user.dog.type);
// user.dog.type="泰迪"
// console.log(user.dog.type);


// console.log(user.name);//张三

// user.name = '李四'

// console.log(user.name);//李四



// console.log(user.age);
// user.age=1000;
// console.log(user.age);







2 收集试图依赖了哪些数据 —>依赖收集

视图: {{name}}  {{name}}  {{name}}   {{age}} {{age}}
data(){
	return{
		name:"",
		age:
	}
}

两个角色 : Watcher(观察者)--奴隶  Dep(订阅者)--主人

data中有几个属性,则会实例化几个Dep对象  
template 中有几个{{}},则会实例化几何Watcher
每个Dep拥有一个数组,该数组用来装对应的Watcher对象

Dep_name   [Watcher_name  ,  Watcher_name  ,  Watcher_name ]
Dep_age    [Watcher_age  ,   Watcher_age]
 
 


Object.defineProperty(xx,"xx",{
    
    get(){
        
        //依赖收集相关代码
        
    },
   
    set(val){
       
       //发布订阅相关代码
        
    }
})

3 数据发生变动时,通知页面要重新渲染—》发布订阅

每个Watcher有一个render:用来渲染页面
每个Dep有一个notify :遍历当前Dep的watcer数组,让每一个watcher对象执行redner函数

当数据更改时,dep就会执行 notify  ,-----》页面就能够重新渲染


原理:

当data中数据劫持后,首次渲染时,程序就会根据{{}}来进行依赖收集----初始化Dep、Watcher ----数据劫持的get函数
当data中的数据发生改变后,程序机会让对应的dep执行“通知”方法继而让对应的Watcher执行"渲染"的方法达到页面重新渲染----数据劫持的set函数

因此:vue2响应式原理也叫做 观察者模式 / 观察者订阅者模式

总结: vue2响应式理解

1 底层用到数据劫持 Object.defineProperty
2 有两个步骤:
	2.1初始化-数据劫持get-依赖收集 涉及到 Dep(订阅者)-notify通知     Watcher(观察者) -render渲染
    2.2数据变化时-数据劫持set-发布订阅 dep执行notify方法,通知对应的watchers 执行 render方法 达到页面渲染

面试题: 请说出v-model双向绑定的原理

语法糖: change/input事件  + value 的语法糖
数据----》视图 (vue2的响应式原理)
视图---》数据  触发了change/input 事件

VUE3 响应式原理

结论:

代理 (proxy) + 反射(Reflect)

代理 proxy

let user={
    name:"张三"
}

let user_proxy=new Proxy(user,{
    //访问时触发
    get(target,prop){
        console.log(`有人在访问user的${prop}属性`);
        return target[prop]
    },
    //更新使触发
    set(target,prop,val){
        console.log(`有人在更新user的${prop}属性,值是${val}`);
        target[prop]=val;

    },
    //删除时触发
    deleteProperty(target,prop){
        console.log(`有人在删除user的${prop}属性`);
        delete target[prop]
    }
})

代理好处:

1 避免直接操作目标对象
2 能够在代理的同时添加其他业务逻辑

反射 Reflect

能够通过另一种方式来操作某个对象
let user={
    name:"张三"
}


//访问
// console.log(user.name);
// console.log(Reflect.get(user,'name'));

// 更新
// user.name="李四"
// Reflect.set(user,'name','李四')

//删除
// delete user.name;
Reflect.deleteProperty(user,'name')

console.log(user.name);
console.log(Reflect.get(user,'name'));

反射的好处:

不会轻易报错,避免了大量的try-catch ,更加优雅

以前的做法:它会报错,只能使用try-catch捕获

    "use strict"

    try {
        let user = {
            name: "李四"
        }
        //冻结,冻结后不能更改user的属性
        Object.freeze(user)
        user.name = '王五'
        console.log(user);
    } catch (e) {



    }

使用反射的做法:

    "use strict"


    let user = {
        name: "李四"
    }
    //冻结
    Object.freeze(user)
    let n = Reflect.set(user, 'name', '王五')
    console.log(n);
    console.log(user);

总结:

vue3的代理+反射好处:
代理:扩展额外业务,避免直接操作目标对象
反射:更加优雅,不会轻易报错,适合自己搭建框架
let user={
    name:"张三"
}

let user_proxy=new Proxy(user,{
    //访问时触发
    get(target,prop){
        console.log(`有人在访问user的${prop}属性`);
        return Reflect.get(target,prop)
    },
    set(target,prop,val){
        console.log(`有人在更新user的${prop}属性,值是${val}`);
        Reflect.set(target,prop,val)

    },
    deleteProperty(target,prop){
        console.log(`有人在删除user的${prop}属性`);
        Reflect.deleteProperty(target,prop)
    }
})

事件循环:

事件循环:
同步任务(主线程执行栈)和异步任务(队列)
异步任务:宏任务和微任务
任务的分类:
1 同步任务
2 异步任务
	2.1 宏任务
	2.2 微任务
宏任务: setTimeout、setinterval、DoM事件、ajax异步请求
微任务: Promise 、async/await
异步任务执行机制:
微任务  优先于   宏任务
console.log(1);

setTimeout(() => {
    console.log(2);
}, 0);

new Promise(resolve => {
    console.log(3);
    resolve();
}).then(() => {
    console.log(4);
})

console.log(5);

vue性能优化:

	组件懒加载
 	v-if和v-show 的使用场景的区别
	keep-alive 缓存
	computed和watch的使用场景的区别
	v-for 的:key
	data只放需要响应式的数据 
	销毁钩子函数中清除定时器、销毁对象函数等操作
	vuex的合理使用
	像那些只需要被展示不会被修改的数据可以通过Object.freeze方法来冻结,一旦被冻结的	对象就再也不能被修改了,提高了渲染速度
	SSR(服务端渲染)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值