setup()
基本使用
原理就是之前我们写的options API写成了API-composition-api
首先我们创建一个vue后缀的文件名,然后里面的内容是这样
注意:在setup()不能访问this
<script>
import { ref } from 'vue' //从vue里面把组合Api解构出来
export default {
data() { //这还是之前的optioons API
return {
title: 'hello'
}
},
setup() { //在这个函数里面的使用就是组合API
const counter = ref(0)
// 不能访问 this
// console.log(this.title)
return {
counter
}
},
mounted() {
console.log(this.counter)
}
}
</script>
<template>
<h1>{{counter}}</h1>
</template>
<style>
</style>
访问props
注意:在组合API ref或者reactive等等API的时候是具有响应式特点的,但是要是通过props从父组件中把这些具有响应是的数据传递给子元素,直接使用props.obj,这样的数据数据有响应式的,是指传递过来一个对象,这个对象有响应式,但是要是通过props传递过来的是一个值就没有响应式了,在子元素使用的时候是没有响应式特点的,要是想要让他具有响应式特点,可以通过toRef,或者toRefs进行传递
例子:在父元素
<script>
import { onMounted, reactive, ref, watchEffect,toRef } from 'vue'
import Child from './Child.vue'
export default {
setup() {
const title = ref('hello')
const obj = reactive({
x: 100,
y: 200
})
onMounted(() => {
setTimeout(() => { //计时器改变,看看子元素是否具有响应式
title.value = 'world'
obj.x = 1000
obj.y = 2000
}, 2000)
})
return {
title,
obj
}
},
components: {
Child
}
}
</script>
<template>
<Child :title="title" :obj="obj"></Child> //在这里通过自定义属性把父元素的值传递给子元素
</template>
在子组件中
<script>
import { toRef, toRefs } from 'vue'
export default {
props: ['title', 'obj'], //在这里接收父组件传递过来的值
setup(props) {
const {obj} = props //在这里直接2解构props里面传递过来的对象是具有响应式的
// const{title}=props//是里面的值就没有响应式
//const obj=props.obj 传递过来的是一个对象就具有响应式了
const x = toRef(props.obj, 'x')//之前写成const x = props.obj.x是没有响应式的,需要toRef来让他具有响应式
const title = toRef(props, 'title')// const title = props.title没有响应式
const { title: title2, obj: { value } } = toRefs(props)
const y = toRef(value, 'y')//通过toRefs可以直接把props里面的值都让他具有响应式,但是对象也用的时候需要对里面的值在进行toRef或者toRefs
return {
title,
title2,
x,
y,
obj
}
}
}
</script>
<template>
<h1>{{title}}</h1>
<h1>{{title2}}</h1>
<h1>{{x}}</h1>
<h1>{{y}}</h1>
{{obj}}
</template>
setup的上下文
1、attrs
: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
。
父子组件通信过程中,父组件把数据传递过来,如果子组件没有用props进行接收,就会出现在attrs中,而vm中没有
例如:在父组件传递过来的
在child组件中并没有用props接收,在setup里console.log(attrs);就是
注意一点,在属性前面加冒号与不加冒号的区别是,加冒号传递过来的是父组件里面的变量,不加冒号就是字符串obj,字符串title,这也就是父子通信,自定义属性加冒号的原因,不加冒号传递的是属性的字符串值
2、emit
: 分发自定义事件的函数, 相当于 this.$emit
。
在父元素中
在子组件中触发事件并且可以传值过去
3、slots
: 收到的插槽内容, 相当于 this.$slots
4.expose() 通过ref后代想要暴露给什么
例如,在父元素绑定子元素的ref
在子元素 setup函数中
那么在父元素去ref的时候只能拿到这个{x:100}
与渲染函数一起用
import { h } from 'vue'
export default {
setup() {
return () => h('div', {class: 'abc'}, h('a', {href: 'http://www.baidu.com'}, '百度'))
} //h函数第一个参数是根节点,第二个参数是属性,第三个参数是子节点
}
</script>
<template>
</template>
页面结构
组合API
import { ref, onMounted, reactive, computed, readonly, watchEffect, watch } from 'vue'
ref 让属性具有响应式
一般里面写的都是基本数据类型
例子:
setup() {
const counter = ref({
x: 100,
y: {
a: 300
}
})
onMounted(() => {
setTimeout(() => {
counter.value.x = 10000 //这里注意在setup函数里面用的时候必须.value
counter.value.y.a = 30000
}, 3000)
})
return {
counter //外面用的话必须return出去
}
}
之前在vue2中在组件或者dom挂载ref,可以通过this.$refs获取,但是在vue3中的setup中不能使用this,所以通过ref获取,刚开始定义一个变变量btn,const btn =ref(null) ,这个btn必须与写在子组件或者dom节点的上ref='btn'是一致的,因为setup刚开始属于create,这时候是null,当dom节点挂载完成之后,写在组件或者子组件里的ref就会把null覆盖
例子
父组件
父组件
<template>
<ChildVue ref="childRef" /> //这里的childRef必须与setup函数里面的一致
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onMounted } from 'vue';
import ChildVue from './Child.vue';
const childRef = ref(null);
console.log(childRef.value); // undefined。因为这里相当于create还没有挂载dom与组件实例
onMounted(() => {
console.log(childRef.value); // Proxy {…}
// 调用子组件方法
childRef.value.foo(); // foo //只能获取到子组件的foo方法,因为子组件只暴露这个方法
});
</script>
<style>
</style>
子组件
<template>child demo</template>
<script setup lang="ts">
const foo = () => {
console.log("foo");
}
defineExpose({ //子组件只暴露出去这个方法
foo
});
</script>
<style>
</style>
reactive
同样也是为了让数据具有响应式,一般写的都是复杂数据类型,
const count = ref(0) const obj = reactive({ count }) obj.count++ console.log(obj.count) count.value = 100 console.log(obj.count) obj.count = 200 console.log(count.value) const books = reactive([ref('JavaScript学习指南')]) books[0].value = 'html学习指南' console.log(books[0].value) const map = reactive(new Map([['count', ref(0)]])) console.log(map.get('count').value) map.set('count.value', 300) console.log(map.get('count').value)
computed() 计算属性
const count = ref(0)
const addOne = computed(() => count.value + 1) //里面写一个回调,当里面的值改变的时候就会触发
console.log(addOne.value)
count.value++
console.log(addOne.value)
readonly()只读属性
const original = reactive({ count: 0 })
const copy = readonly(original)
original.count = 100 //但是赋值的变了,这个只读的也会变成100
// copy.count = 200 //因为是只读的所以不可以改
console.log(copy.count) //100
watchEffect()
有个特性,与mobx里面的autorun一样,在最开始就会执行
const count = ref(0)
const title = ref('line1')
const stop = watchEffect(() => console.log(count.value + title.value)) // 总是要执行一次
stop()
count.value = 100
count.value = 100 //因为两次改变的值是一样的,所以就会执行一次
title.value = 'line2'
watch() 监听
const state = reactive({count: 0})
watch(
() => state.count, //变成了两个参数,第一个参数是函数,返回的是监听的值
(count, prevCount) => {
console.log(count)
console.log(prevCount)
}
)
state.count++
watch与watcheffect区别:
- 执行时机:watchEffect是立即执行的,在页面加载时会主动执行一次,来收集依赖;而watch是惰性地执行副作用,它不会立即执行,但可以配置 immediate,使其主动触发
- 参数不同:watchEffect只需要传递一个回调函数,不需要传递侦听的数据,它会在页面加载时主动执行一次,来收集依赖;而watch至少要有两个参数(第三个参数是配置项),第一个参数是侦听的数据,第二个参数是回调函数
结果不同:watchEffect获取不到更改前的值;而watch可以同时获取更改前和更改后的值
类型判断
import { ref, isRef, readonly, computed, reactive, unref, toRef, toRefs, isProxy, isReactive, isReadonly } from 'vue'
isRef 判断是不是ref类型
注意通过computed处理的ref类型也是ref类型
const count = ref(0)
const count2 = 0
const count3 = readonly(count)
const count4 = computed(() => count.valut + 2)
const count5 = reactive({x: 0})
console.log(isRef(count)) //1
console.log(isRef(count2))//0
console.log(isRef(count3))//0
console.log(isRef(count4))//1
console.log(isRef(count5))//0
unref() 不是ref类型的
unref(参数)
isReactive()
检查一个对象是否是由 reactive
或者 readonly
方法创建的代理。
const msg = readonly(ref(2))
const msg1 = reactive({a:2})
const msg2 = ref(2)
const msg3 = {}
console.log(isProxy(msg))//1
console.log(isProxy(msg1))//1
console.log(isProxy(msg2))//0
console.log(isProxy(msg3))//0
isReadonly() 判断是不是readonly类型
const msg = readonly(reactive({}))
const msg2 = ref(0)
console.log(isReadonly(msg))//1
console.log(isReadonly(msg2))//0
在setup函数的生命周期
- `beforeCreate` -> 使用 `setup()`
- `created` -> 使用 `setup()`
- `beforeMount` -> `onBeforeMount`
- `mounted` -> `onMounted`
- `beforeUpdate` -> `onBeforeUpdate`
- `updated` -> `onUpdated`
- `beforeDestroy` -> `onBeforeUnmount`
- `destroyed` -> `onUnmounted`
在setup函数中的provide与inject
在上层组件中
export default {
components: {
Child
},
setup() {
const title = ref('hello') //通过这种方式传递是具有响应式的
provide('title',title) //第一个是变量名,第二个是传的参数,要是传多个的时候title可以传递一个对象,到时候再取值,要是想传递多个变量就需要定义多个provide了
}
}}
在下层组件inject接收的时候
export default {
setup() {
//const title = inject('title', 'hello') r如果provide没有传值的时候第二个参数是默认值
const title = inject('title') 通过inject拿到在provide定义的'title'变量
return {
title
}
}
}
setup函数的语法糖
只需要在<script setup></script>标签写上setup就可以了
例子1:
<script setup>
import { ref, provide } from 'vue'
import Child from './Child.vue'
//之前是下面这种写法,加上标签之后就没注释的写法,不用return出数据,不用components写子组件了
// export default {
// components: {
// Child
// },
// setup() { //
// const title = ref('hello')
// // provide('title')
// }
// }
const title = ref('hello')
provide('title', title)
</script>
<template>
<Child></Child>
</template>
<style>
</style>
当有参数,例如props的写法
<script>
import { toRef, toRefs } from 'vue'
export default {
props: ['title', 'obj'],
setup(props){
const obj=props.obj
console.log(props)
const {title:title4}=props
const x = toRef(props.obj, 'x')
return {
x,
obj,
title4
// }
}
}
}
</script>
<template>
<h1>{{x}}</h1>
{{obj.x}}
{{title4}}
</template>
用语法糖的写法,不需要return,不需要export default,但是注意接收参数的写法props,用到defineProps(),可以写一个数组也可以写一个对象
<script setup>
import { toRef, toRefs } from 'vue'
// props: ['title', 'obj'],
const props = defineProps(['title', 'obj'])
//const obj = props
const obj=props.obj
console.log(props)
const {title:title4}=props
const x = toRef(props.obj, 'x')
// return {
// //title,
// // title2,
// x,
// obj,
// title4
// }
</script>
<template>
<h1>{{x}}</h1>
{{obj.x}}
{{title4}}
</template>
context中的参数的时候
defineProps
和 defineEmits
在 <script setup>
中必须使用 defineProps
和 defineEmits
API 来声明 props
和 emits
,它们具备完整的类型推断并且在 <script setup>
中是直接可用的:
emit用defineEmits
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup code
</script>
-
defineProps
和defineEmits
都是只在<script setup>
中才能使用的编译器宏。他们不需要导入且会随着<script setup>
处理过程一同被编译掉。 -
defineProps
和defineEmits
在选项传入后,会提供恰当的类型推断。 -
传入到
defineProps
和defineEmits
的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内。
使用 <script setup>
的组件是默认关闭的,也即通过模板 ref 或者 $parent
链获取到的组件的公开实例,不会暴露任何在 <script setup>
中声明的绑定。
defineExpose
为了在 <script setup>
组件中明确要暴露出去的属性,使用 defineExpose
编译器宏:
expose()的写法
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
useSlots
和 useAttrs
在 <script setup>
使用 slots
和 attrs
的情况应该是很罕见的,因为可以在模板中通过 $slots
和 $attrs
来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlots
和 useAttrs
两个辅助函数:
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
useSlots
和 useAttrs
是真实的运行时函数,它会返回与 setupContext.slots
和 setupContext.attrs
等价的值,同样也能在普通的组合式 API 中使用