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

被折叠的 条评论
为什么被折叠?



