目录
1. shallowReactive 和 shallowRef
简介
本文基于b站尚硅谷vue教程尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili编写,以作个人学习记录。
一、创建项目
1.使用vue-clli创建
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue -V
## 安装或升级@vue/cli
npm install -g @vue/cli
## 创建项目 选择vue3
vue create projectName
## 进入项目所在文件夹,启动项目
npm run serve
2.使用Vite创建
## 创建工程
npm init @vitejs/app projectName
## 进入工程目录
cd projectName
## 安装依赖
npm install
## 运行项目
npm run dev
二、Composition API(组合式API)
1.setup函数
①在vue3中数据、方法等均要配置在setup中
②setup有两种返回值:
I.返回一个对象,包含需要使用的数据和方法,模板能直接使用这些数据和方法
II.返回一个渲染函数(较少使用)
③setup()早于beforeCreate()执行,其有两个参数:(props,context)
- props:值为对象,包含父组件传过来的,且本组件有接收(同vue2中接收props)的属性
- context:值为对象,包含attrs、slots、emit
- attrs等同于this.$attrs,用以获取父组件传递的但本组件未在props中声明的属性
- slots等同于this.$slots,用以获取插槽
- emit等同于this.$emit,用以触发自定义事件(自定义事件需要在新的配置项emits声明,否则控制台会报警告)
*注意点:
1.尽量不要与vue2混用,vue2可以访问到setup的数据、方法,反之则不行
2.setup不能是async函数,否则返回的是一个promise,模板无法获取return的对象中的属性(后期也可以返回一个Promise实例,但需要Surspense和异步组件的配合)
<template>
<h4>姓名:{{ name }}</h4>
<h4>学校:{{ info.school }}</h4>
<h4>年级:{{ info.grade }}</h4>
<h4>爱好:{{ hobbies }}</h4>
<button @click="sayHello">点我</button>
</template>
<script>
export default {
name: "Setup",
setup() {
let name = "trytou";
let hobbies = ["抽烟", "喝酒", "烫头"];
let info = {
school: "菜头市第一小学",
grade: "一年级",
};
function sayHello(){
alert("hello")
}
return{
name,
info,
hobbies,
sayHello,
}
},
};
</script>
2.ref函数和reactive函数
ref函数和reactive函数都可以定义一个响应式的数据,需要引入才能使用
使用方式:const xx=ref(value) 或 const xx=reactive(value)
1.ref函数
- ref函数既可以接收基本数据类型,也可以接收引用数据类型(数组或对象)
- 对于基本数据类型,响应式依然时通过Object.defineProperty()实现
- 对于对象类型数据,响应式则靠vue3中的一个新函数——reactive函数实现
- 在js中操作ref定义的响应式数据时要通过xxx.value获取,模板中则不需要
2.reactive函数
- reactive函数只能接收引用数据类型的数据(数组或对象)
- reactive函数返回一个proxy对象
- reactive函数定义的数据是深层次的,其内部的属性可以直接修改,(在vue2中通过直接赋值的方式来修改data中的数组的某个元素是无效的)
<template>
<h4>姓名:{{name}}</h4>
<h4>年龄:{{age}}</h4>
<h4>学校:{{info.school}}</h4>
<h4>年级:{{info.grade}}</h4>
<h4>爱好:{{hobbies}}</h4>
<button @click="sayHello">点我</button>
<button @click="changeHobby">点我换爱好</button>
<button @click="changeSchool">点我换学校</button>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
name:'Ref_Reactive',
setup(){
//ref函数定义一个响应式数据
let name=ref('trytou')
let age=ref(20)
let info=reactive({
school:'菜头市第一小学',
grade:'一年级'
})
let hobbies=reactive(['抽烟','喝酒','烫头'])
function sayHello(){
alert('hello trytou');
}
function changeHobby(){
hobbies[0]='打球'
}
function changeSchool(){
info.school='反斗花园幼儿园'
}
return{
name,
age,
info,
hobbies,
sayHello,
changeHobby,
changeSchool
}
}
}
</script>
3.模拟vue3实现响应式
vue3中通过Proxy(代理)和Reflect(反射)来实现响应式
<script>
let person = {
name: 'trytou',
age: 20
}
const p = new Proxy(person, {
get(target, prop) {
return Reflect.get(target,prop)
},
set(target, prop, value) {
return Reflect.set(target,prop,value)
},
deleteProperty(target,prop){
return Reflect.deleteProperty(target,prop)
}
})
p.name='Chan'
console.log(p);
</script>
4.computed函数
- computed接收一个函数或者一个对象,需要引入才能使用
import {computed} from 'vue'
export default {
name: "Computed",
setup() {
let person = reactive({
firstName: "try",
lastName:'tou',
})
//计算属性-简写
// person.fullName=computed(()=>person.name+'-'+person.age)
//计算属性-完整写法
person.fullName=computed({
get(){
return person.firstName+'-'+person.lastName
},
set(value){
person.firstName=value.split('-')[0]
person.lastName=value.split('-')[1]
}
})
return{
person,
}
},
};
5.watch函数
watch函数中有较多的坑,监视reactive定义的数据时要注意
- 情况一:监视ref定义的一个响应式数据
watch(num,(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
- 情况二:监视ref定义的多个响应式数据
watch([num,msg],(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
-
情况三:监视reactive定义的一个响应式数据 (默认deep:true,且无法设置为false)
watch(person,(newValue,oldValue)=>{
//此处 oldValue == newValue , 无法获取正常的 oldValue
console.log(newValue,oldValue);
},{deep:false})
-
情况四:监视reactive定义的某个响应式数据中的某个属性
watch(()=>person.age,(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
-
情况五:监视reactive定义的某一个响应式数据中的多个属性
watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
-
情况六:监视reactive定义的深层次的响应式数据
watch(()=>person.jobs,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:true})//需要设置deep为true
6.watchEffect函数
watchEffect函数类似computed,但前者无需写返回值,且不必指明监视哪个属性
//类似computed属性,检测回调函数中用到的数据发生变化即会重新执行回调
watchEffect(()=>{
console.log(person.age);
})
7.生命周期
在vue3中有两种使用生命周期钩子的方式
- 第一种方式是通过配置对象的形式,与vue2中生命周期钩子使用方式一致,但有两个更换了名字:
- beforDestroy更名为beforeUnmount
- destroyed更名为unmounted
- 第二种方式是通过组合API的形式,除了setup之外的钩子都应在setup内部调用,与vue2生命周期钩子对应关系如下:
- 因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。 - setup() ==> beforeCreate() 和 created()
- onBeforeMount() ==> beforeMount()
-
onMounted ==> mounted()
-
onBeforeUpdate ==> beforeUpdate()
-
onUpdated ==> updated()
-
onBeforeUnmount() ==> beforeDestroy()
-
onUnmounted() ==> destroy()()
- 因为
-
两种方式可以同时使用(但尽量不要这么做),组合式API的生命周期钩子会比配置对象形式的钩子先执行
8.自定义hook函数
- hook本质上是一个函数,作用类似于mixin
在usePoint.js中定义一个hook并暴露出去
import {onMounted,onUnmounted, reactive} from 'vue';
export function usePoint() {
let point = reactive({
x: '',
y: ''
})
let getPoint = function (event) {
point.x = event.pageX
point.y = event.pageY
}
onMounted(() => {
window.addEventListener('click', getPoint)
})
onUnmounted(() => {
window.removeEventListener('click', getPoint)
})
return point
}
在组件中引入hook便可直接使用,无需再次引入hook中引用的组合式api
<template>
<h4>点击任意位置获取当前鼠标的坐标</h4>
<span>X轴坐标:{{point.x}}</span><br>
<span>Y轴坐标:{{point.y}}</span>
</template>
<script>
import {usePoint} from '../hooks/usePoint';
export default {
name:'Hook',
setup(){
let point=usePoint()
return {
point
}
}
}
</script>
9.toRef 和 toRefs
- toRef 和 toRefs 都是函数,后者可以批量创建多个ref
- toRef可以为一个响应式对象中的某个属性创建一个ref,且这个ref的值会指向源响应式对象中的那个属性
- toRefs可以为一个响应式对象中的每一个最外层的属性创建ref
<template>
<h4>{{person}}</h4>
<hr>
<h4>my name is {{name}}</h4>
<button @click="name='Chan'">rename</button>
<h4>salary: {{jobs.job1.salary}}k</h4>
<button @click="jobs.job1.salary++">涨薪</button>
<button @click="salary++">涨薪</button>
<h4>age: {{age}}</h4>
<button @click="age++">长大了</button>
</template>
<script>
import { reactive, toRef ,toRefs} from 'vue'
export default {
name:'ToRef',
setup(){
let person=reactive({
name:'trytou',
age:20,
jobs:{
job1:{
salary:8
}
}
})
/salary的值发生变化,person.jobs.job1.salary的值也会发生变化
const salary=toRef(person.jobs.job1,'salary')
console.log(salary);/
console.log(toRefs(person));
return{
person,
salary,
...toRefs(person)
}
}
}
</script>
三.其它 Composition API
1. shallowReactive 和 shallowRef
//shallowReactive 与 reactive的区别在于shallowReactive只处理对象最外层属性的响应式(浅响应式)
let sReaPerson=shallowReactive(person)
//shallowRef与ref的区别在于 shallowRef 不会将.value变成响应式(proxy),而仍是一个普通的对象
let sRefPerson=shallowRef(person)
2.readonly 和 shallowReadonly
//readonly接收一个响应式数据并使其成为只读的(深只读)
let p1 = readonly(reactive(person));
let p3 = readonly(ref(num));
//shallowReadonly接收一个响应式数据并使其成为只读的(浅只读)
let p2 = shallowReadonly(reactive(person));
let p4 = shallowReadonly(ref(num));
3.toRaw 和 markRaw
//toRaw返回reactive或readonly代理的原始对象(ref无效)
console.log(toRaw(person));
console.log(toRaw(pet));
//markRaw标记一个对象,使其永远不会转换为 proxy,返回对象本身
let mr = markRaw({ age: 20 });
let kid = reactive(mr);
4.customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收
track
和trigger
函数作为参数,并且应该返回一个带有get
和set
的对象。
使用自定义ref实现防抖效果:
<template>
<input type="text" v-model="msg" />
<h4>{{ msg }}</h4>
</template>
<script>
import { customRef, ref } from "@vue/reactivity";
export default {
name: "CustomRef",
setup() {
function myRef(initValue) {
let timer;
return customRef((track, trigger) => {
return {
get() {
track();
return initValue;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
initValue = newValue;
trigger();
}, 500);
},
};
});
}
let msg = myRef("hello");
return {
msg,
};
},
};
</script>
5.provide 和 inject
- 作用:可以用来实现祖孙组件之间的通信
- 在祖组件中使用provide(),后代组件使用inject()接收
祖组件
...
setup(){
let msg=ref('love u')
provide('msg',msg)
...
}
后代组件
...
setup(){
let msg=inject('msg')
...
}
6.响应式数据的判断
...
setup(){
let p1=ref(0)
let p2=reactive({})
let p3=readonly({})
console.log(isRef(p1));//判断一个值是否为一个 ref 对象
console.log(isReactive(p2));//判断一个对象是否是由 reactive 创建的响应式代理
console.log(isReadonly(p3));//判断一个对象是否是由 readonly 创建的只读代理
console.log(isProxy(p2));//判断一个对象是否是由 reactive 或者 readonly 方法创建的代理
console.log(isProxy(p3));
}
...
四.新的组件
1.Fragment
- 在vue3中组件无需根标签,内部会自动将模板中的内容包含在一个Fragment中
2.Teleport
- teleport可以让组件移动到指定位置,在下面的例子中Son组件有一个Dialog组件,如果我们想让这个Dialog组件位于body下可以通过teleport中的to属性来指定
祖组件
<template>
<div class="cur">
<h3>我是祖先</h3>
<Child></Child>
</div>
</template>
<script>
import Child from './17_child.vue';
export default {
name: "TeleportComponent",
components:{Child}
};
</script>
<style>
div{
padding:20px
}
</style>
子组件
<template>
<div class="child">
<h4>我是Child组件</h4>
<Son></Son>
</div>
</template>
<script>
import Son from './17_son.vue';
export default {
name:'Child',
components:{Son}
}
</script>
<style>
.child{
background-color: rgb(47, 145, 184);
}
</style>
孙组件
<template>
<div class="son">
<h5>我是Son组件</h5>
<Dialog></Dialog>
</div>
</template>
<script>
import Dialog from './17_Dialog.vue';
export default {
name:'Son',
components:{Dialog},
setup(){
}
}
</script>
<style>
.son{
background: rgb(236, 80, 80);
}
</style>
Dialog组件
<template>
<button @click="isShow = true">开启弹窗</button>
<teleport to="body">
<div class="mask" v-if="isShow">
<div class="dialog">
<h3>我是弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
</template>
<script>
import { ref } from "vue";
export default {
name: "Dialog",
setup() {
let isShow = ref(false);
return {
isShow,
};
},
};
</script>
<style>
.dialog {
position: absolute;
width: 440px;
height: 300px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background-color: pink;
z-index: 999;
}
.mask {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
}
</style>
可以看到teleport包含的结构出现在<body>下
参考
尚硅谷vue3教程:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili
vue3官方文档:Vue.js
vite官方文档:Vite中文网