一.组合式 API 也就是composition-api
(1) vue2 组件的局限性
1. 组件越大,可续性越差
2. 相同的代码逻辑很难在多个组件中复用
(2) 提供以下几个函数
1. setup :组合api 的方法都是写在这里面的
2. ref : 定义响应式数据 字符串 bool
3. reactive :定义响应式数据 对象
4. watchEffet :监听数据变化
5. watch :监听数据变化
6. computed :计算属性
7. toRefs :解构响应式对象数据
8. 生命周期的hooks
(2.1) setup
1. setup 函数会在 beforeCreate、created 之前执行,setup的生命周期 在 beforeCreate、created 之后执行
2. setup 有2个参数
props 的父组件传递过来的参数
ctx 上下文对象
ctx.attrs
ctx.slots
ctx.parent
ctx.root
ctx.emit
ctx.refs
3. 在 setup() 函数中无法访问到 this【没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。】
4. 执行 setup 时,组件实例尚未被创建,你只能访问以下 property【props,attrs, slots, emit 】,你将无法访问以下组件选项【data,computed ,methods】
<template>
<div class="setup">
</div>
</template>
<script>
export default {
setup() {
console.log('setup')
},
beforeCreate() {
// console.log("beforeCreate");
},
created() {
// console.log("created");
}
};
</script>
(2.2) ref ,reactive 定义响应式数据
ref 【定义字符串,num,bool,数组】
reactive【定义对象】
<template>
<div class="setup">
<h2>1.ref ,reactive 定义响应式数据</h2>
<div>{{title}}{{userInfos.username}}---{{userInfos.age}}</div>
<br />
<button @click="getUserName">获取用户名称</button>
<div>双向数据绑定 影响ref,reactive</div>
<br />
<input v-model="title" />
<input v-model="userInfos.username" />
</div>
</template>
<script>
import { ref, reactive } from "vue";
export default {
setup() {
var title = ref("用户信息:");
const repositories = ref([]);// 定义数组
var userInfos = reactive({
username: "张三",
age: 20
});
// 定义方法
var getUserName = () => {
// 获取reactive的数据
console.log("username:", userInfos.username);
//获取ref 的数据
console.log("title:",title.value);
// 修改reactive的数据
userInfos.username = "张艺";
//修改ref 的数据
title.value = "改变用户信息:";
};
return {
title,
userInfos,
getUserName
};
}
};
</script>
(2.3) toRefs 解构响应式对象数据
<h2>2.toRefs 解构响应式对象数据</h2>
{{worksName}}
<input v-model="worksName" />
setup() {
// 2.toRefs 解构响应式对象数据
var worksInfos = reactive({
worksName: "张三",
worksDesi: "到好多好好的"
});
return {
...toRefs(worksInfos) //...worksInfos 这样的话可以显示数据,但是不能双向绑定数据
};
}
(2.4) computed 计算属性 【只能在setup】
<input v-model="firstName" />
<input v-model="lastName" />
<div>{{fullName}}</div>
setup() {
//3.computed :计算属性
var userInfos2 = reactive({
firstName: "王",
lastName: "一搏"
});
var fullName = computed(() => {
return userInfos2.firstName + userInfos2.lastName;
});
return {
...toRefs(userInfos2),
fullName
};
(2.5) readonly "深层"的只读代理 (少用)
将响应式 数据(ref,reactive定义的数据),转正非响应式的数据(也就是原始数据)
readonly 传入一个对象(响应式对象),返回一个原始对象 只能读取不能修改。
var obj={
username:"李小英"
}
obj 就是原始数据, 可以渲染在页面中,但是不能双向绑定
var userInfos = reactive({
username: "张三",
age: 20
});
userInfos 响应式数据,
可以把响应式数据 转成非响应式数据,
const copy = readonly(userInfos) //copy.username 只能读取,不能修改
(2.6) watchEffet 监听数据变化
在响应式地跟踪其依赖项时,立即运行一个函数,并在更改依赖项时重新运行它。
<h2>4.watchEffet 监听数据变化 --侦听器</h2>
<div>{{data.count}}</div>
<button @click="stop">手动关闭侦听器</button>
const data = reactive({
count: 1,
num: 1
});
const stop = watchEffect(() => {
console.log("侦听器:", data.count);
});
setInterval(() => {
data.count++;
}, 1000);
(2.7) watch , watch与watchEffect区别
1.watch与watchEffect区别:
const data = reactive({
count: 10,
num: 1
});
1. 懒执行,也就是说只有侦听的值变更时才执行回调
watchEffect(() => {
console.log("侦听器:", data.count);// 可以输出1
});
watch(data, () => {
console.log("watch 监听:", data.count);// 输出2
});
watch 只有监听到值(data.count)发生变化 才执行,watchEffect都可以执行
2. 更明确哪些状态的改变会触发侦听器重新运行
watchEffect 可以监听具体的对象【如:data.count】,watch 只能监听ref或是reactive定义的对象【如: data】
3. 访问侦听状态变化前后的值
watch(numVal, (newVal, oldVal) => {
console.log("numVal新值跟旧值的变化:", newVal, oldVal);
});
案例:
<h2>5.watch 监听数据变化 --侦听器</h2>
<div>{{data.count}}</div>
<div>watch 双向数据绑定,前后数据</div>
{{numVal}}
<input v-model="numVal" />
<input v-model=data.count"/>
const data = reactive({
count: 10,
num: 1
});
// (1).简单的引用
// 要监听对象data ,但是不能监听对象里的值data.count
watch(data, () => {
console.log("watch 监听:", data.count);
});
setInterval(() => {
data.count++;
}, 1000);
var numVal = ref("1");
// (2).watch 的两个参数,代表新的值和旧的值
watch(numVal, (newVal, oldVal) => {
console.log("numVal新值跟旧值的变化:", newVal, oldVal);
});
//(3). watch 多个值,返回的也是多个值的数组
watch([numVal, data], (newValue, oldValue) => {
console.log("old", oldValue); //["13",Proxy {count: "144", num: 1}]
console.log("new", newValue); //["134",Proxy {count: "144", num: 1}]
});
// (4).使用 getter 的写法 watch reactive 对象中的一项
watch([numVal, () => data.count], (newValue, oldValue) => {
console.log("old", oldValue); //old (2) ["1", 10]
console.log("new", newValue); //new (2) ["1", "103"]
console.log("updated:" + numVal.value + data.count); //updated:1103
});
(2.8) 生命周期
生命周期的vue3更新:
在vue3中对生命周期钩子的命名进行一些简单的命名更新,并且在setup函数中提供了新的生命周期函数钩子。
1. 命名改变:
vue3对beforeDestroy 和destroy进行命名的修改,分别修改为beforeUnmount,unmounted。这个是命名的改变,回调时机上并没有发生任何的改变。
2. setup函数中提供了新的生命周期函数钩子
都是 setup () 内部调用生命周期钩子,没有beforeCreate,created ,其他都是一样
(2.9)Provide / Inject
不管组件层次 结构多深,都可以使用这个Provide(传递参数) / Inject (接受参数)
1.非组合api 写法
app.vue
<template>
<div>
<div>祖父组件:{{location}}</div>
<button @click="fn">改变location的值</button>
<provides></provides>
</div>
</template>
<script>
import provides from "@/views/provide.vue";
export default {
name: "App",
components: {
provides
},
data() {
return {
location: "传递祖父的参数"
};
},
methods: {
fn() {
this.location = "改变值";
}
},
provide() {
return {
location: this.location,
geolocation: {
longitude: 90,
latitude: 135
}
};
}
};
</script>
provides:
<template>
<div>
<h2>Provide / Inject</h2>
<loaction></loaction>
</div>
</template>
<script>
import loaction from "../components/loaction.vue";
export default {
components: {
loaction
}
};
</script>
location.vue:
<template>
<div>
<h5>孙组件接受参数</h5>
<div>祖父组件定义provide,孙组件inject接受:{{location}}</div>
</div>
</template>
<script>
export default {
inject: ["location", "geolocation"]
};
</script>
点击fn,不能改变location.vue的值,就是没有响应的效果
没有办法 双向绑定
2. 组合api (重点)
<template>
<div>
<div>祖父组件:{{location}}</div>
<button @click="fn">改变location的值</button>
<br />
<br />
<div>双向数据绑定:</div>
姓名 {{username}}:
<input v-model="username" />
<provides></provides>
</div>
</template>
<script>
import { ref, provide, reactive, toRefs } from "vue";
import provides from "@/views/provide.vue";
export default {
name: "App",
components: {
provides
},
setup() {
let location = ref("传递祖父的参数");
var userInfos = reactive({
username: "张三",
age: 20
});
let fn = () => {
location.value = "改变值";
};
provide("location", location);
provide("userInfos", userInfos);
return {
location,
fn,
...toRefs(userInfos)
};
}
};
</script>
provides:
<template>
<div>
<h2>Provide / Inject</h2>
<loaction></loaction>
</div>
</template>
<script>
import loaction from "../components/loaction.vue";
export default {
components: {
loaction
}
};
</script>
<style>
</style>
location.vue:
<template>
<div>
<h5>孙组件接受参数</h5>
<div>1.祖父组件定义provide,孙组件inject接受:{{location}}</div>
<p>用户信息: {{userInfos.username}}</p>
<br />
<br />
<div>2.provide inject实现父子组件传值的时候,子组件改变数据也会影响父组件</div>
<br />姓名:
<input v-model="userInfos.username" />
</div>
</template>
<script>
import { ref, inject } from "vue";
export default {
setup() {
let location = inject("location");
let userInfos = inject("userInfos");
return {
location,
userInfos
};
}
};
</script>
3. 总结:
1.祖父组件定义provide,孙组件inject接受
2.provide inject实现父子组件传值的时候,子组件改变数据也会影响父组件
4. 不想子组件改变父组件的数据
最后,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly
provide("userInfos", readonly(userInfos));
二.响应性
(1)比较vue2 和 vue3 响应式
1. vue2 的响应式:
核心:
对象:通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视、拦截)
数组:通过重写数组更新数组一系列更新元素的方法实现元素修改的劫持【简单的理解:普通数组变成响应数组,对一个数组的元素进行更改的话,它会对这个更改的方法进行重新编写】
object.defineProperty(data,'count',{
get(){},
set(){}
})
问题:
1). 对象直接新添加的属性或删除已有属性,界面不会自动更新
2). 直接通过下标替换元素或更新length,界面不会自动更新 arr[1] ={}
所以后面才有$set,去响应数据
<div>
<div v-for="(key,val,index) in user" :key="index">{{key}}:{{val}}</div>
<button @click="add">修改数据</button>
</div>
//因为vue2 是通过object.defineProperty 来操作数据,所以不能检测数据的变动(虽然打印出来的数据是更新的,但是界面的数据没更新)
export default {
data() {
return {
user: {
name: "王一博",
age: 20
}
};
},
methods: {
add() {
this.user.sex = "男";
console.log( this.user);
}
}
};
注意同样的代码在vue3 里面就可以。不旦数据更新,界面也更新。原因是vue3使用prory
2.vue3 的响应式:
核心:
1).通过proxy(代理):拦截对data任意属性的任意(13种)操作,包括属性值的读写,属性的添加,属性的删除等
2).通过reflect(反射):动态对被代理对象的相应属性进行特定的操作
(2) 什么是响应式
响应性是一种允许我们以声明式的方式去适应变化的一种编程范例。
简单的理解:对对象进行修改,对象数据会改变,界面上也会发生改变
(3)深入vue3响应性原理
就是把目标对象变成代理对象(prory),然后通过代理对象对数据进行操作,通过reflect在反射回来。
let user = {
name: "王一博",
age: 20,
words: {
adds: "g区18号",
tell: "1727283848744",
cars: ["宝马", "奥迪", "本田"]
}
};
// 把目标对象变成代理 对象
// 使用prory 从new Proxy开始接受2个参数
// 参数1:target目标对象----》 user
// 参数2:Handel 处理器对象,用来监视数据及数据操作----》{}
const proxyUser = new Proxy(user, {
// 获取目标对象的某个属性值
//target目标对象。property被获取的属性名。
get(target, prop) {
console.log("get方法调用了");
return Reflect.get(target, prop);
},
// 修改、添加目标对象的属性
//target目标对象。property被获取的属性名 value 值
set(target, prop, value) {
console.log("set方法调用了");
return Reflect.set(target, prop, value);
},
// 删除目标对象的某个属性值
//target目标对象。property被获取的属性名
deleteProperty(target, prop) {
console.log("delete方法调用了");
return Reflect.deleteProperty(target, prop);
}
});
// 通过代理对象获取目标对象中的某个属性值
console.log(proxyUser.name);
// 通过代理对象更新目标对象上的某个属性值
proxyUser.name = "肖战";
console.log("修改属性值:", user);
//通过代理对象向目标对象添加一个新的属性
proxyUser.sex = "男";
console.log("添加新属性:", user);
//通过代理对象删除目标对象的某个属性值
delete proxyUser.age;
console.log("删除age 后:", user);
//深度的添加属性值
proxyUser.words.name = "航天信息";
console.log("深度的添加属性值:", user);
(4)什么是prory
proxy 是一个对象,可以代理一个对象或函数,允许拦截被代理的对象或函数
(5) Reflect
三.有意思的Teleport
teleport 是vue3 内置组件,接受2个函数(to:指定将在其中移动 <teleport> 内容的目标元素 如:body,.home ,#home;disabled :禁止使用该功能)
请注意,这将移动实际的 DOM 节点,而不是被销毁和重新创建,并且它还将保持任何组件实例的活动状态。所有有状态的 HTML 元素 (即播放的视频) 都将保持其状态。
核心就是to的操作
<template>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</template>
<script>
import Vue from "vue";
var modalButton = {
template: `
<button @click="modalOpen = true">
打开弹窗
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a modal!
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
};
}
};
export default {
components: {
"modal-button": modalButton
},
created() {}
};
</script>
<style>
.modal {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>
四.VueConf 2021
1.视频