1、初识setup
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点注意!!)
<template>
<div>姓名:{{ name }} 年龄:{{ age }}</div>
<button @click="sayHello" >点一点我试试</button>
</template>
<script>
export default {
name: 'App',
setup() {
let name = '呆瓜'
let age = 18
function sayHello() {
console.log(`大家好,我叫${name},我今年刚满${age}岁啦`)
}
return {
name,
age,
sayHello
}
},
}
</script>
- 若返回一个渲染函数:则可以自定义渲染内容。
<template>
<!-- 模板中内容不起效果-->
<div>姓名:{{ name }} 年龄:{{ age }}</div>
<button @click="sayHello" >点一点我试试</button>
</template>
<script>
import {h} from 'vue'
export default {
name: 'App',
setup() {
let name = '呆瓜'
let age = 18
function sayHello() {
console.log(`大家好,我叫${name},我今年刚满${age}岁啦`)
}
//模板写的东西不会起效果,而是以渲染函数的内容为主,页面只有-我今年刚满18啦
return () => h('h1','我今年刚满18啦')
},
}
</script>
备注:
- 不要与vue2混用,Vue2.x配置(data、method、computed...)中可以访问setup中的属性、方法;
- 但在setup中不能访问到Vue2.x配置(data、methods、computed...);
- 如果有重名,谁写在后面,值就是谁的;
- setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性。(返回一个promise实例,需要Suspense和异步组件。(Vue3筑基—下篇 - 掘金 (juejin.cn))里面有Suspense的示例用法)
注意点
- setup执行的事件,在beforeCreate之前执行一次,this是undefined
- setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象
{
attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于vue2的this.$attrs。
slots:收到的插槽内容,相当于vue2的this.$slots。
emit:分发自定义事件的函数,相当于vue2的this.$emit。
}
//App.vue
<template>
<Demo @hello="sayHello" msg="你好啊" name="小明" >
<template v-slot:qwe >
<span>修仙进行中</span>
</template>
</Demo>
</template>
<script>
import Demo from './components/Demo.vue'
export default {
name: 'App',
components:{
Demo
},
setup(){
function sayHello(value){
console.log(`你好,触发了hello事件,传递的参数是${value}`);
}
return {
sayHello
}
}
}
//Demo.vue
<template>
<div>
<h3>姓名:{{ name }} 年龄:{{ person.age }}</h3>
<h3>{{ name }} {{ msg }}</h3>
<slot name="qwe" ></slot>
<button @click="test" >测试出发demo组件的hello事件</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Demo',
props:['msg','name'],
// emits:['hello'],
setup(props,context) {
//主要观察props和context的组成部分
console.log(props,context);
//数据
let person = {
age: 19,
hobby:'唱跳rap篮球'
}
function test(){
context.emit('hello',88888)
}
return {
person,
test
}
},
}
</script>
2、ref的基本使用
- 作用:定义一个响应式数据
- 语法:const xxx = ref(initValue)
创建一个包含响应式数据的引用对象(reference对象)
js中操作数据:xxx.value
模板中读取数据:不需要value,直接:<div>{{xxx}}</div> - 备注:
接收的数据可以式:基本类型、也可以式对象类型
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的
对象类型的数据:内容“求助”了vue3.0中的一个新函数-reactive
<template>
<!-- vue3组件模板中可以没有根标签 -->
<div>姓名:{{ name }} 年龄:{{ age }}</div>
<h4>工作种类:{{ job.type }}</h4>
<h4>薪水:{{ job.salary }}</h4>
<button @click="sayHello" >修改数据</button>
</template>
<script>
// import {h} from 'vue'
import { ref } from 'vue'
export default {
name: 'App',
setup() {
//数据
let name = ref('呆瓜') //RefImpl{get/set value}
let age = ref(18) //RefImpl{get/set value}
let job = ref({
type:'筑基导师',
salary:'10000灵石'
}) //RefImpl{proxy value}
//方法
function sayHello() {
console.log(name);
name.value = '玉面狐狸'
age.value = 28
job.value.type='摆烂专家'
console.log(job);
job.value.salary = '20000灵石'
}
return {
name,
age,
job,
sayHello
}
},
}
</script>
3、reactive函数
- 作用:定义一个对象类型的响应式数据(基本类型不要用它,用ref函数)
- 语法:const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(proxy对象)
- reactive定义的响应式数据是“深层次的”
- 内部基于es6的Proxy实现,通过代理对象操作源对象内部数据进行操作。
<template>
<!-- vue3组件模板中可以没有根标签 -->
<div>姓名:{{ person.name }} 年龄:{{ person.age }}</div>
<h4>工作种类:{{ person.job.type }}</h4>
<h4>薪水:{{ person.job.salary }}</h4>
<h4>爱好:{{ person.hobby }}</h4>
<button @click="sayHello" >修改数据</button>
</template>
<script>
// import {h} from 'vue'
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
//数据
let person = reactive({
name:'呆瓜',
age:18,
job:{
type:'筑基导师',
salary:'10000灵石'
},
hobby:['吃饭','睡觉','打豆豆'],
})
//方法
function sayHello() {
person.name = '玉面狐狸'
person.age = 28
person.job.type='摆烂专家'
person.job.salary = '20000灵石'
person.hobby[0]='学习'
}
return {
person,
sayHello
}
},
}
</script>
4、Vue中的响应式原理
vue2.x的响应式
实现原理:
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let person = {
name: '张三',
age: '18'
}
let p = {}
//模拟vue2响应式
Object.defineProperty(p, 'name', {
get() {
//读取数据
return person.name
},
set(value) {
console.log(value);
//修改数据
console.log('修改数据,更新页面');
person.name = value
}
})
Object.defineProperty(p, 'age', {
configurable: true,
get() {
//读取数据
return person.age
},
set(value) {
console.log(value);
//修改数据
console.log('修改数据,更新页面');
person.age = value
}
})
</script>
</body>
</html>
存在问题:
新增属性、删除属性,界面不会更新。需要通过this.$set()才能进行新增。或Vue.set();删除通过this.$delete()或者Vue.delete()
直接通过下标修改数组,界面不会自动更新。
vue3.0的响应式
实现原理:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等
- 通过Reflect(反射):对被代理对象(源对象)的属性进行操作。Reflect提供了很多方法,使用该方式可以更直接统一的管理对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//window.Reflect 是 JavaScript 中的一个内置对象,它提供了一组静态方法,用于对对象进行反射操作。反射是指在运行时检查、获取和修改对象的属性和行为。
// Reflect 对象的方法可以用于执行常见的对象操作,例如获取属性值、设置属性值、调用函数等。与传统的对象操作方法相比,Reflect 方法提供了一种更统一、更直观的方式来执行这些操作。
// 以下是一些 Reflect 方法的示例:
// Reflect.get(target, property):用于获取目标对象的指定属性的值。
// Reflect.set(target, property, value):用于设置目标对象的指定属性的值。
// Reflect.has(target, property):用于检查目标对象是否具有指定的属性。
// Reflect.deleteProperty(target, property):用于删除目标对象的指定属性。
// Reflect.apply(func, thisArg, args):用于调用函数并指定函数的上下文和参数。
// Reflect.construct(target, args):用于创建一个目标对象的实例,并传递参数给构造函数。
let person = {
name: '张三',
age: '20',
}
const p = new Proxy(person, {
get(target, key) {
console.log(`${key}被读取了`);
// return target[key]
return Reflect.get(target, key)
},
//set修改或者追加某个属性时调用
//set可以对属性进行拦截、验证、自定义行为等操作,比如你可以去告诉页面,某个属性被修改了然后更新页面。
set(target, key, value) {
console.log(`${key}被修改了,更新界面去了`);
// target[key] = value
Reflect.set(target, key, value)
},
deleteProperty(target, key) {
console.log(`${key}被删除了,更新界面去了`);
// return delete target[key]
return Reflect.deleteProperty(target, key)
}
})
console.log(p);
</script>
</body>
</html>
5、reactive对比ref
- 数据类型:
- ref主要用于定义基本数据类型,如数字、字符串等
- reactive主要定义对象(数组)类型数据,内部会自动通过reactive转为代理对象。
- 备注:ref也可以通过定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
- 语法:
- ref定义的数据:操作数据要加
.value,从模板读取数据时不需要加。 - reactive会自动将嵌套的属性转换为响应式对象,所有不需要加
.value
- 实现原理:
- ref通过
Object.defineProperty()的get和set实现响应式(数据劫持) - reactive通过
Proxy来实现响应式(数据劫持),并提供Reflect操作源对象内部的数据。
6、computed函数
跟vue2的computed配置功能一致
<template>
<div>
<h2>某个人的信息</h2>
姓氏:<input type="text" v-model="person.firstName" /><br />
名字:<input type="text" v-model="person.lastName" /><br />
全名:{{ fullName }} <br />
修改全名: <input type="text" v-model="person.fullName" />
</div>
</template>
<script>
import { computed, reactive } from "vue";
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Demo",
// emits:['hello'],
setup() {
let person = reactive({
firstName: "呆瓜",
lastName: 20,
});
//简写,没有考虑被修改的情况
// person.fullName = computed(()=>{
// return person.firstName+person.lastName
// })
//完整写法(读和写)
person.fullName = computed({
get() {
return person.firstName + person.lastName;
},
set(value) {
const arr = value.split("-");
person.firstName = arr[0] || "";
person.lastName = arr[1] || "";
},
});
return {
person,
};
},
};
</script>
7、watch函数
- 与vue2.x中的watch配置功能一致
- 两个小坑:
- 1、监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
- 2、监视reactive定义的响应式数据中某个属性时:deep配置有效。
<template>
<div>
<h2>求和:{{ sum }}</h2>
<h3>{{ msg }}</h3>
<button @click="sum++" >增加数据</button>
<button @click="msg +='!'">添加感叹号</button>
<hr>
<h3>姓名:{{ person.name }}</h3>
<h3>年龄:{{ person.age }}</h3>
<h3>薪资:{{ person.job.salary }}石</h3>
<button @click="person.name += '~'" >修改姓名</button>
<button @click="person.age++">修改年龄</button>
<button @click="person.job.salary++" >修改薪资</button>
</div>
</template>
<script>
import { reactive, ref,watch } from "vue";
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Demo",
// emits:['hello'],
setup() {
let sum = ref(0)
let msg = ref('不是吧不是吧')
let person = reactive({
name:'李四',
age:20,
job:{
salary:1000,
}
})
//情况一:监视ref所对应的一个响应式数据
// watch(sum,(newValue,oldValue)=>{
// console.log('sum的值变化了',newValue,oldValue)
// },{immediate:true})
// watch(msg,(newValue,oldValue)=>{
// console.log('msg的值变化了',newValue,oldValue)
// },{immediate:true})
//情况二:监视多个响应式数据
// watch([sum,msg],(newValue,oldValue)=>{
// console.log('sum或者msg的值变化了',newValue,oldValue)
// },{immediate:true})
//情况三:监视reactive所对应的全部响应式数据
//注意1、此处无法正确获取到oldValue,会跟newValue一致
//注意2、强制开启了深度监视(deep配置项无效)
// watch(person,(newValue,oldValue)=>{
// console.log('person的值变化了',newValue,oldValue)
// },{deep:false})//deep配置无效
//情况四:监视reactive所定义一个响应式数据中的某个属性
// watch(()=>person.age,(newValue,oldValue)=>{
// console.log('age的值变化了',newValue,oldValue)
// },{immediate:true})
//情况五:监视reactive所定义的一个对象中的某些属性
// watch([()=>person.name,()=>person.job.salary],(newValue,oldValue)=>{
// console.log('person的值变化了',newValue,oldValue)
// })
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的值变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive定义的对象中的某个属性,所以deep配置有效
return {
msg,
sum,
person
};
},
};
</script>
如果使用ref定义了对象,watch监视也可以这样写
<template>
<div>
<h2>求和:{{ sum }}</h2>
<button @click="sum++" >增加数据</button>
<hr>
<h3>姓名:{{ person.name }}</h3>
<h3>年龄:{{ person.age }}</h3>
<h3>薪资:{{ person.job.salary }}石</h3>
<button @click="person.name += '~'" >修改姓名</button>
<button @click="person.age++">修改年龄</button>
<button @click="person.job.salary++" >修改薪资</button>
</div>
</template>
<script>
import { ref,watch } from "vue";
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Demo",
// emits:['hello'],
setup() {
let sum = ref(0)
let person = ref({
name:'李四',
age:20,
job:{
salary:1000,
}
})
console.log(person);
//不使用.value进行监视,是因为sum是基本数据类型
watch(sum,(newValue,oldValue)=>{
console.log('sum的值变化了',newValue,oldValue)
})
//监视对象,注意使用的是ref!!!,可以使用.value进行监视
watch(person.value,(newValue,oldValue)=>{
console.log('person的值变化了',newValue,oldValue)
})
//ref定义的对象 也可以使用deep配置项,同样有效果
watch(person,(newValue,oldValue)=>{
console.log('person的值变化了',newValue,oldValue)
},{deep:true})
return {
sum,
person
};
},
};
</script>
8、watchEffect函数
watchEffect是Vue 3中的一个新特性,它用于监听响应式数据的变化并执行相应的副作用函数。当被监听的响应式数据发生变化时,watchEffect会立即执行传入的副作用函数。
与watch方法不同的是,watchEffect没有指定具体要监听的数据,而是会自动追踪响应式数据的依赖,并在依赖变化时重新运行副作用函数。
需要注意的是,watchEffect会在组件渲染时立即执行一次副作用函数,并在组件卸载时自动停止监听。如果需要停止监听,也可以通过返回一个清理函数来实现。
- watch:既要指明监视的属性,也要指明监视的回调
- watcheffect:不用指明监视的哪个属性,监视的回调中用到哪个属性,就监视哪个属性
- watchEffect有点像computed:
但是computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值。
而watchEffect更注重的是过程、逻辑(回调函数的函数体),所以不用写返回值。(面试可以聊)
<template>
<div>
<h2>求和:{{ sum }}</h2>
<button @click="sum++" >增加数据</button>
<hr>
<h3>姓名:{{ person.name }}</h3>
<h3>年龄:{{ person.age }}</h3>
<h3>薪资:{{ person.job.salary }}石</h3>
<button @click="person.name += '~'" >修改姓名</button>
<button @click="person.age++">修改年龄</button>
<button @click="person.job.salary++" >修改薪资</button>
</div>
</template>
<script>
import { ref,watch,watchEffect } from "vue";
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Demo",
// emits:['hello'],
setup() {
let sum = ref(0)
let person = ref({
name:'李四',
age:20,
job:{
salary:1000,
}
})
//不使用.value进行监视,是因为sum是基本数据类型
watch(sum,(newValue,oldValue)=>{
console.log('sum的值变化了',newValue,oldValue)
})
watchEffect(()=>{
let x = sum.value
let x2 = person.value.job.salary
console.log('watchEffect所指定的回调执行了',x,x2);
})
return {
sum,
person
};
},
};
</script>