安装好vue3后,开始新的项目吧!
npm init vue@latest
1.组合式API
在以前vue2的项目里,使用的选项式API将各种不同逻辑的代码分散到像data、methods等不同的对象里。如果想看某一方面的逻辑,鼠标滚轮都要磨出火星了!
而升级到vue3之后组合式API就解决了这一难题。
1.1拉开序幕的setup
setup是所有组合式API表演的舞台。组件中所用到的数据、方法,均要配置在setup中。
//setup其实是一个函数
export default {
name : 'APP',
setup : function(){
}
}
//函数可简写
export default {
name : 'APP',
setup(){
}
}
//添加数据和方法
export default {
name : 'APP',
setup(){
//数据
let name = '张三'
let age = 18
//方法
function sayHello(){
alert("我是${name},我已经${age}岁了")
}
}
}
setup有返回值,有两种返回值:
(1)若返回对象,则对象中的属性、方法在模板中可以直接使用。(重点)
(2)若返回渲染函数,则可以自定义渲染的内容。(了解)
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="sayHello">说话</button>
</template>
<script>
export default {
name : 'APP',
setup(){
//数据
let name = '张三'
let age = 18
//方法
function sayHello(){
alert("我是${name},我已经${age}岁了")
}
//setup返回对象 return {name:name,age:age}
return {name,age,sayHello} //setup返回对象的简写形式(常用)
//返回渲染函数
//return () => h('h1','您好')
}
}
</script>
1.2 兼容vue2
在vue3中其实也可以继续用vue2的写法。
在vue2的配置中可以读取vue3的配置,但是vue3的配置中不能读取vue2的配置。
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<h1>性别:{{ sex }}</h1>
<button @click="sayHello">说话</button>
<button @click="sayWelcome">欢迎</button>
</template>
<script>
export default {
name : 'APP',
data(){
return {
sex:'男'
}
},
methods : {
sayWelcome(){
alert('欢迎')
}
},
setup(){
//数据
let name = '张三'
let age = 18
//方法
function sayHello(){
alert("我是${name},我已经${age}岁了")
}
return {name,age,sayHello}
}
}
</script>
但是不建议vue3与vue2的配置混用!
1.3 ref函数
在之前setup中写的数据,并不是响应式的。当修改数据时,在模板中的数据没有相应的变化。因此,需要通过一个函数ref来讲数据变成响应式的。
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="changeInfo">修改个人信息</button>
</template>
<script>
export default {
name : 'APP',
setup(){
//数据
let name = '张三'
let age = 18
//方法
function changeInfo(){
name = '李四'
age = 48
console.log(name,age) //数据修改了,但是没响应到模板中
}
return {name,age,changeInfo}
}
}
</script>
现在引入ref函数,将初始化的字面量用ref函数包裹起来。
<script>
import {ref} from 'vue'
export default {
name : 'APP',
setup(){
//数据
let name = ref('张三')
let age = ref(18)
//方法
function changeInfo(){
// name = '李四'
// age = 48
console.log(name,age) //数据修改了,但是没响应到模板中
}
return {name,age,changeInfo}
}
}
</script>
用ref函数包裹的数据实际上是RefImpl的引用实现对象。
修改数据的方式需要使用value来修改。但是在模板中不用.value访问数据,因为vue3自动解析RefImpl对象的值了。
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="changeInfo">修改个人信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name : 'APP',
setup(){
//数据
let name = ref('张三')
let age = ref(18)
//方法
function changeInfo(){
name.value = '李四'
age.value = 48
}
return {name,age,changeInfo}
}
}
</script>
当用ref函数包裹对象时,也需要value才能访问内部的值。
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="changeInfo">修改个人信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name : 'APP',
setup(){
//数据
let name = ref('张三')
let age = ref(18)
let job = ref({
type : '前端工程师',
salary : '30k'
})
//方法
function changeInfo(){
job.value.type = '李四'
job.value.salary = '60k'
}
return {name,age,changeInfo}
}
}
</script>
但是,按道理RefImpl
内部的值应该也是RefImpl
的对象,但是实际上我们发现内部用的是Proxy
对象。其实ref包裹对象类型数据时,借用了reactive
函数。
总结:
- 作用:定义一个响应式的数据
- 语法:const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(ref对象)
- js中操作数据:xxx.value 模板中读取数据:不需要.value,直接
<div>{{xxx}}<div>
- 接收的数据可以是基本类型,也可以是对象类型。
- 基本类型的数据的响应式是靠
Object.defineProperty()
的get与set完成的。
1.4 reactive函数
在vue中,reactive
函数(响应)的作用定义一个对象类型
的响应式数据。(基本类型不用它,要用ref
函数)
<script>
import {ref, reactive} from 'vue'
export default {
name : 'APP',
setup(){
//数据
let number = reactive(666)
//方法
function changeInfo(){
console.log(number)
}
return {name,age,changeInfo}
}
}
</script>
若将reactive
直接用于基本数据类型就会报错。
因此
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="changeInfo">修改个人信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name : 'APP',
setup(){
//数据
let name = ref('张三')
let age = ref(18)
let job = reactive({
type : '前端工程师',
salary : '30k'
})
//方法
function changeInfo(){
console.log(job)
}
return {name,age,changeInfo}
}
}
</script>
可以看到,reactive
将对象类型的数据封装为Proxy
对象。
使用reactive的好处:
支持修改深层属性
支持修改数组
<script>
import {ref} from 'vue'
export default {
name : 'APP',
setup(){
//数据
let name = ref('张三')
let age = ref(18)
let job = reactive({
type : '前端工程师',
salary : '30k' ,
a:{
b:{
c:666
}
}
})
let hobby = reactive(['抽烟','喝酒','烫头'])
//方法
function changeInfo(){
job.a.b.c = 999 //reactive支持修改深层属性
hobby[0] = '学习' //reactive支持修改数组
}
return {name,age,changeInfo}
}
}
</script>
reactive与ref的对比
(1)ref访问属性需要使用value,reactive访问属性不需要使用value
(2)ref定义基本数据类型,reactive定义对象类型
(3)ref的响应式原理是通过Object.defineProperty()
实现的,reactive通过Proxy对象实现的。
//数据
let name = ref('张三')
let age = ref(18)
//方法
function changeInfo(){
name.value = '李四'
age.value = 44
}
//数据
let person= reactive({name:'张三',age:18})
//方法
function changeInfo(){
person.name = '李四'
person.age = 44
}
1.5 setup的注意点
(1)setup函数的执行比beforeCreate函数要早
(2)setup函数里的this是undefined,这意味着setup里不能用this
(3)setup只能收到两个参数,分别是props
用来接受父组件传给该组件的属性,以及context
用来保存上下文信息。
<script>
import {ref} from 'vue'
export default {
name : 'APP',
beforeCreate(){
console.log('---beforeCreate---')
},
setup(){
console.log('---setup---',this)
return {}
}
}
</script>
props传输
<script>
export default {
name : 'APP',
props: ['msg','school'], //必须声明props才能接受属性
beforeCreate(){
console.log('---beforeCreate---')
},
setup(props){ //通过props可以访问父组件传过来的值
console.log(props)
console.log(props.msg)
console.log(props.school)
return {}
}
}
</script>
上下文context
<script>
export default {
name : 'APP',
props: ['msg','school'],
beforeCreate(){
console.log('---beforeCreate---')
},
setup(props,context){
console.log(context)
return {}
}
}
</script>
context里面是一个expose
对象,它包含attrs
、emit
、slots
对象。
其中,attrs
对象接收遗漏的props中未声明的属性。(相当于vue2中的$attrs)
emit
对象接收分发的自定义事件。(相当于vue2中的$emit)
slots
对象接收传入包裹的slot中未声明的DOM节点。(相当于vue2中 的$slots)
//App.vue
<template>
<Demo @hello="handleHello"></Demo>
</template>
<script>
import {Demo} from './compnents/Demo'
export default {
name : 'App',
setup(){
//方法
function handleHello(value){
alert('触发了Hello事件,我收到的参数是${value}')
}
return {handleHello}
}
}
</script>
//Demo.vue
<template>
<button @click="test">测试触发一下Demo组件的hello事件</button>
</template>
<script>
import {ref} from 'vue'
export default {
name : 'App',
props:['msg'],
emits:['hello'], //vue3中应该声明将触发的hello事件,否则会有警告
setup(props,context){
function test(){
context.emit('hello',666) //不能用this,必须用context触发外部事件
}
return {test}
}
}
</script>
2. computed计算属性
在vue2中,计算属性是由其他属性派生出来的属性。在vue3中,计算属性写在setup中,并且需要再引入
<!-- vue2写法 -->
<template>
姓: <input type="text" v-model="person.firstName">
<br>
名: <input type="text" v-model="person.lastName">
<br>
<span>全名: {{ fullName}}</span>
</template>
<script>
import {reactive} from 'vue'
export default {
name : 'App',
//计算属性
computed: {
fullName(){
return this.person.firstName + '-' + this.person.lastName
}
},
setup(props,context){
let person = reactive({
firstName : '张',
lastName : '三'
})
return {person}
}
}
</script>
vue3写法
<!-- vue3写法 -->
<template>
姓: <input type="text" v-model="person.firstName">
<br>
名: <input type="text" v-model="person.lastName">
<br>
<span>全名: {{ fullName}}</span>
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name : 'App',
setup(props,context){
let person = reactive({
firstName : '张',
lastName : '三'
})
//计算属性,由于fullName是person派生出的属性,建议放在person对象里新增
person.fullName = computed(()=>{
return person.firstName + "-" + person.lastName
}
)
return {person}
}
}
</script>
上述代码中,computed是简写形式,只能读,不能修改。因此需要改成下面的完整形式
<!-- vue3写法 -->
<template>
姓: <input type="text" v-model="person.firstName">
<br>
名: <input type="text" v-model="person.lastName">
<br>
全名: <input type="text" v-model="person.fullName">
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name : 'App',
setup(props,context){
let person = reactive({
firstName : '张',
lastName : '三'
})
//计算属性,完整形式,同时考虑读写
person.fullName = computed({
get(){
return person.firstName + "-" + person.lastName
},
set(value){
const nameAttr = value.split('-')
person.firstName = nameAttr[0]
person.lastName= nameAttr[1]
}
})
return {person}
}
}
</script>
3. watch监视函数
在vue3中watch监视函数的用法如下:
<template>
<span>{{sum}}</span>
<button @click="sum++">点我加1</button>
</template>
<script>
import {ref,watch} from 'vue'
export default {
name : 'App',
setup(props,context){
let sum = ref(0)
let person = reactive({
name: '张三',
age: 18,
job:{
salary:20
}
})
//情况一:监听ref所定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
})
//情况二:监视ref所定义的多个响应式数据,第一个参数配置成数组
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
})
//情况三:希望可以立马执行watch的监视,而不用等变化后,
//解决方式:第三个参数可以填配置信息,
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true,deep:true})
//情况四:监听reactive所定义的一个响应式数据
//1.注意vue3这里有个bug,无法监控到oldValue的值
//2.注意vue3中reactive绑定了深度监视,配置deep无法关闭深层监视
watch(person,(newValue,oldValue)=>{
console.log('person变了',newValue,oldValue)
},{deep:false})
//情况五:监听reactive对象中的某个属性,需要写匿名函数
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person变了',newValue,oldValue)
})
//情况六:监听reactive对象中的某些属性,需要封装在数组中
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的name和age变了',newValue,oldValue)
})
//特殊七:若监视的reactive对象中的某个对象,则必须开启deep深层监视的配置。
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变了',newValue,oldValue)
},{deep:false})
return {sum}
}
}
</script>
watchEffect函数
watchEffect
函数可以智能判断属性的变化,不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性。
类似于computed函数,不过watchEffect更注重过程,所以不用写返回值。
<script>
import {ref,watch} from 'vue'
export default {
name : 'App',
setup(props,context){
let sum = ref(0)
let person = reactive({
name: '张三',
age: 18,
job:{
salary:20
}
})
watchEffect(()=>{
const x1 = sum.value //用到哪个属性,就监测哪个
console.log('watchEffect执行的回调执行了')
})
return {sum}
}
}
</script>
4. vue3里的声明周期
//待续…