代码结构分散,对同一个数据的不同操作分散到了不同的options选项中。
对同一个数据的操作在同一个函数中完成。
setup()
<script setup><script>
参数:
props
:父组件传递过来的属性
context
:
返回值:
- 可以在
template
模板中使用 - 通过
setup
函数返回值替代data
注意:
setup
里的数据不是默认响应式的,需要引入响应式函数对目标数据进行包裹
ref
函数,主要对简单类型数据进行包裹,也可以处理复杂类型数据。需要通过ref.value
获取到数据,但是在template
下不需要,会进行自动解包。
<template>
<div class="app">
<h2>{{ info.age }}</h2>
</div>
</template>
<script>
import {ref} from 'vue'
export default{
setup(){
let info = ref({'age':18})
return {
info
}
}
}
- 引入
reactive
函数,将复杂类型的数据包装为响应式数据
使用ref
和reactive
处理数据之后,数据再次使用就会进行依赖收集,数据改变时所有收集的依赖都是对应进行对应的响应式操作。
const state = reactive({...})
<template>
<div class="app">
<!-- template中的数据会自动解包 -->
<h2>计数器{{ counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default{
setup(){
// 默认定义的数据不是响应式数据,需要用 ref 包裹
let counter = ref(10)
const increment = ()=>{
counter.value++
}
const decrement = ()=>{
counter.value--
}
return {
counter,
increment,
decrement,
}
}
}
</script>
抽离为单独的hook
hooks/useCounter.js
import {ref} from 'vue'
export default function useCounter(){
let counter = ref(10)
const increment = ()=>{
counter.value++
}
const decrement = ()=>{
counter.value--
}
return {
counter,
increment,
decrement,
}
}
在App.vue
中引入:
import useCounter from './hooks/useCounter'
export default{
setup(){
const {counter,increment,decrement} = useCounter()
return{
counter,
increment,
decrement,
}
}
或者还可以更简单:
import useCounter from './hooks/useCounter'
export default{
setup(){
return {
...useCounter()
}
}
单向数据流:由于组件传值是引用传值,所以子组件可以改变父组件的数据,从而影响其他同样使用了父组件数据的其他子组件。如果想要更改父组件数据,应该由子组件将改变数据的事情发送给父组件,让父组件去改变数据。
<template>
<div class="app">
<info :info="info" @changeInfoAge="changeInfoAge"></info>
</div>
</template>
<script>
import { reactive } from 'vue';
import Info from './components/Info.vue'
export default{
components:{
Info
},
setup(){
const info = reactive({
name:"flten",
age:20,
job:"worker",
})
const changeInfoAge = (payload)=>{
info.age = payload
}
return {
info,
changeInfoAge,
}
}
}
</script>
<template>
<div>
<h1>{{ info.age }}</h1>
<button @click="changeInfo">改变按钮</button>
</div>
</template>
<script>
export default {
props:{
info:{
type: Object,
default:()=>({})
}
},
emits:['changeInfoAge'],
setup(props, context){
const changeInfo = ()=>{
context.emit('changeInfoAge', 25)
}
return {
changeInfo
}
}
}
</script>
- 一般都使用
ref
,简单类型和复杂类型都可以处理;定义从服务器中获取的数据一般也使用ref
,获取数据会更方便,因为可以将服务器中的数据赋给ref
响应式数据的.value
属性 reactive
一般应用于自己定义的聚合数据(多个数据之间有关联),比如收集表单数据
通过readonly
可以从语法上避免违反单向数据流的原则
readonly
包裹后返回的数据也是响应式数据,但是不允许对数据进行修改。
注意:readonly
处理过的原对象是可以更改的。
- 比如
const info = readonly(obj)
,readonly
处理后得到的只读对象info
是不可更改的,但如果源对象obj
被修改了,那么readonly
返回的info
对象也会被修改。 - 实际情况下,多对
reactive
返回的数据再用readonly
进行包装。如果是对reactive
包裹后的数据A
进行了包装,得到了只读数据B
,B
不可被更改。但如果改变可变数据A
,也会影响只读数据B
原理:readonly
会返回原始对象的只读代理,是对一个proxy
的set
方法进行了劫持,保证其不被更改。
isProxy
:判断是否由reactive
或readonly
创建的proxy
isReactive
:判断是否由reactive
创建的proxy
;即便是由readonly
包裹了的reactive
返回的另一个proxy
,仍然判断为true
const info = reactive({
name:"flten",
age:20,
job:"worker",
})
const readonlyInfo = readonly(info)
console.log(isReactive(readonlyInfo)) //true
isReadonly
:判断对象是否由readonly 创建的只读proxy
toRaw
:返回由reactive
或readonly
代理的原始对象
shalloReactive
:不进行深层响应式转化,深层嵌套属性不被代理
shalloReadonly
:不进行深层只读转化,深层嵌套属性仍然可读可写
需求:在setup
中,对reactive
返回的响应式对象进行解构后,得到的数据不再是响应式数据。
const info = reactive({
name:"flten",
age:20,
job:"worker",
})
const {name ,age, job} = info
-
使用
toRefs
可以做到解构后得到的每一个数据都用ref
进行包装后再返回,保证解构后还是响应式。 -
如果只想对单个属性进行结构并用
ref
进行包装,则可以使用toRef
<template>
<div class="app">
<h1>{{ name }}, {{ age }},{{ job }}</h1>
<button @click="changeInfo">改变数据</button>
</div>
</template>
<script>
import { reactive, toRefs, toRef } from 'vue';
export default{
setup(){
const info = reactive({
name:"flten",
age:20,
job:"worker",
})
const {name ,age} = toRefs(info)
const job = toRef(info, 'job')
const changeInfo = ()=>{
info.name = 'wall'
info.job = 'leader'
}
return {
name,
age,
job,
changeInfo,
}
}
}
</script>
isRef
:判断值是否是一个Ref
对象
unref
:如果参数是一个ref
则直接返回ref
的值,相当于ref.value
,否则返回参数本身。在不确定接收到的参数是不是ref
对象的情况下可以使用,相当于const val = isRef(obj) ? obj.value : value
shallowRef
:创建一个浅层的 Ref 对象,即深层属性不是响应式。
triggerRef
:手动触发更新,想要对shallowRef
创建的浅层Ref 对象的某个深层嵌套属性进行响应式操作时,可以进行手动触发更新。
// 创建浅层 ref 对象
const info = shallowRef({age:20})
const changeInfo = ()=>{
// age不是响应式数据
info.value.age = 18
// 手动触发更新
triggerRef(info)
}
注意📢:computed
的返回值是一个ref
对象
import { reactive, computed } from 'vue';
export default{
setup(){
const info = reactive({
name:"flten",
age:20,
job:"worker",
})
const fullInfo = computed(()=>{
return `${info.name} is ${info.age} years old`
})
return {
fullInfo
}
}
computed中的 get 和 set 使用示例
<template>
<div class="app">
<h1>{{ fullInfo }}</h1>
<button @click="setFullInfo">修改值</button>
</div>
</template>
<script>
import { reactive, computed } from 'vue';
export default{
setup(){
const info = reactive({
name:"flten",
age:20,
job:"worker",
})
const fullInfo = computed({
get: ()=>{
return `${info.name} is ${info.age} years old`
},
set: (newValue)=>{
info.name = newValue
}
})
const setFullInfo = () => {
fullInfo.value = 'fltenwall'
}
return {
fullInfo,
setFullInfo,
}
}
}
</script>
- 使用
ref
对象绑定到元素的ref
属性上 - 在生命周期函数
onMounted
获取到元素对象
<template>
<div class="app">
<h1 ref="h1Ref">Hello</h1>
<button ref="btnRef">修改值</button>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
export default{
setup(){
const h1Ref = ref()
const btnRef = ref()
onMounted(()=>{
console.log(h1Ref.value)
console.log(btnRef.value)
})
return {
h1Ref,
btnRef,
}
}
}
</script>
子组件 Info.Vue
<template>
<div>
<h1>This is Info.</h1>
</div>
</template>
<script>
export default {
setup(){
return {
}
}
}
</script>
父组件
<template>
<div class="app">
<Info ref="infoRef"></Info>
</div>
</template>
<script>
import Info from './components/Info.vue';
import { ref, onMounted } from 'vue';
export default{
components:{
Info,
},
setup(){
const infoRef = ref()
onMounted(()=>{
console.log(infoRef.value)
})
return {
infoRef
}
}
}
</script>
用法:onName
注意:没有created
和beforeCreated
对应的onCreated
和onBeforeCreated
。原本在created
和beforeCreated
进行的操作可以在setup
中执行。
import { onBeforeMount, onMounted } from 'vue';
export default{
setup(){
onBeforeMount(()=>{})
onMounted(()=>{})
return {
onBeforeMount,
onMounted,
}
}
}
也可以注册多个同名生命周期函数,并会依次执行
onMounted(()=>{})
onMounted(()=>{})
onMounted(()=>{})
用途:组件间的数据共享,父子组件、非父子组件。
provide
提供数据,inject
注入数据
父组件
<template>
<div class="app">
<Info></Info>
<button @click="changeName">改变数据</button>
</div>
</template>
<script>
import Info from './components/Info.vue';
import { provide, ref } from 'vue';
export default{
components:{
Info
},
setup(){
const name = ref('fltenwall')
provide('name', name)
const changeName = ()=>{
name.value = 'flten'
}
return {
name,
changeName,
}
}
}
</script>
子组件
<template>
<div>
<h1>This is {{name}}.</h1>
</div>
</template>
<script>
import { inject } from 'vue'
export default {
setup(){
// 第二个参数为默认值
const name = inject('name', 'fltenwal')
return {
name
}
}
}
</script>
- 如果监听的是
reactive
数据,默认深度监听
const info = reactive({
name : 'fltenwall',
city: {
name: 'ShenZhen'
}
})
watch(info, (newValue, oldValue)=>{
console.log(newValue, oldValue)
},{
immediate: true
})
但如果监听获取到的是普通对象,则需要手动开启深度监听。
const message = ref('good')
const info = reactive({
city: {
name: 'ShenZhen'
}
})
watch(()=>({...info}), (newValue, oldValue)=>{
console.log(newValue, oldValue)
},{
immediate:true,
deep:true,
})
同时监听多个数据:
const name = ref('flten')
const age = ref(20)
watch([name ,age], (newValue, oldValue)=>{
console.log(newValue, oldValue)
})
- 传入的函数第一次会自动执行
- 自动收集函数内部所有依赖过的响应式数据,无须向
watch
指定 - 可以收集多个依赖,其中一个依赖数据改变时,就会自动执行回调
<template>
<div class="app">
<h1>{{counter}}</h1>
<button @click="counter++">增加计数</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default{
setup(){
const counter = ref(10)
watchEffect(()=>{
console.log(counter.value)
})
return {
counter,
}
}
}
</script>
设置停止监听:达到某个阈值停止监听
watchEffect会返回一个停止监听对象,调用它就可以停止监听
const stopWatchEffect= watchEffect(()=>{
console.log(counter.value)
if(counter.value > 15){
stopWatchEffect()
}
})
上面的函数在counter
的值达到 15 后就会停止监听
- 不再需要 components 选项,导入的组件直接引入就可以使用
- 不需要将声明的变量通过
return
出去,顶层声明的变量都会直接暴露给模板 - 建议将
<script setup>
放在模板前面
<script setup>
import { ref, watchEffect } from 'vue'
const counter = ref(10)
const stopWatchEffect= watchEffect(()=>{
console.log(counter.value)
if(counter.value > 15){
stopWatchEffect()
}
})
</script>
<template>
<div class="app">
<h1>{{counter}}</h1>
<button @click="counter++">增加计数</button>
</div>
</template>
无须引用,可以直接在组件中使用
父组件传递必须按照子组件定义的类型进行传递
const props = defineProps({
name : {
type: String,
default: '',
},
age : {
type: Number,
default: 0,
}
})
必须通过defineExpose暴露子组件的对象/函数给父组件,父组件才能通过ref
获取子组件的元素对象,然后调用这些暴露的函数和对象
子组件
const info = {
age : 19,
}
function childFn(){
console.log('childFn')
}
defineExpose({
childFn,
info,
})
父组件
<template>
<div class="app">
<Info :name="name" :age="age" ref="infoRef"></Info>
</div>
</template>
<script setup>
import Info from './components/Info.vue'
import { onMounted, ref } from 'vue'
const name = ref('flten1')
const age = ref(17)
const infoRef = ref()
onMounted(()=>{
infoRef.value.childFn()
console.log(infoRef.value.info)
})
</script>