vue3中的setup()函数基本使用详解
作者:前端青山
在 Vue3 中,setup 函数是一个新引入的概念,它代替了之前版本中的 data、computed、methods 等选项,用于设置组件的初始状态和逻辑。setup 函数的引入使得组件的逻辑更加清晰和灵活,本文将主要介绍Setup的基本用法和少量原理
- 更灵活的组织逻辑:setup 函数可以将相关逻辑按照功能进行组织,使得组件更加清晰和易于维护。不再受到 Options API 中选项的限制,可以更自由地组织代码。
- 逻辑复用:可以将逻辑抽取为可复用的函数,并在 setup 函数中进行调用,实现逻辑的复用,避免了在 Options API 中通过 mixins 或混入对象实现逻辑复用时可能出现的问题。
- 更好的类型推断:由于 setup 函数本身是一个普通的 JavaScript 函数,可以更好地与 TypeScript 配合,提供更好的类型推断和代码提示。
- 更好的响应式处理:setup 函数中可以使用 ref、reactive 等函数创建响应式数据,可以更方便地处理组件的状态,实现数据的动态更新。
- 更细粒度的生命周期钩子:setup 函数中可以使用 onMounted、onUpdated、onUnmounted 等函数注册组件的生命周期钩子,可以更细粒度地控制组件的生命周期行为。
- 更好的代码组织:setup 函数将组件的逻辑集中在一个地方,使得代码更易读、易维护,并且可以更清晰地看到组件的整体逻辑。
setup()函数
setup()
钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
- 需要在非单文件组件中使用组合式 API 时。
- 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
其他情况下,都应优先使用 <script setup> 语法。
1.1 基本使用
我们可以使用响应式 API 来声明响应式的状态,在 setup()
函数中返回的对象会暴露给模板和组件实例。其它的选项也可以通过组件实例来获取 setup()
暴露的属性。
<script> import { ref } from 'vue' export default { setup() { const count = ref(0) // 返回值会暴露给模板和其他的选项式 API 钩子 return { count } }, mounted() { console.log(this.count) // 0 } } </script> <template> <button @click="count++">{{ count }}</button> </template>
请注意在模板中访问从 setup
返回的 ref 时,它会自动浅层解包,因此你无须再在模板中为它写 .value
。当通过 this
访问时也会同样如此解包。
setup()
自身并不含对组件实例的访问权,即在setup()
中访问this
会是undefined
。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>组合式API</title> </head> <body> <div id="app"> {{ count }} <button @click="add">加1</button> </div> </body> <script src="../lib/vue.global.js"></script> <script> const { ref, onMounted } = Vue Vue.createApp({ setup () { const count = ref(0) const add = () => { count.value += 1 } onMounted(() => { console.log(1111) }) return { count, add } }, data () { return { count: 10 } }, methods: { add () { this.count += 10 } }, mounted () { console.log('2222') } }).mount('#app') </script> </html>
生命周期先执行 组合式API 后执行选项式API,其余以组合式API为优先
1.2 访问 Prop
setup
函数的第一个参数是组件的 props
。和标准的组件一致,一个 setup
函数的 props
是响应式的,并且会在传入新的 props 时同步更新。
{ props: { title: String, count: Number }, setup(props) { console.log(props.title) console.log(props.count) } }
请注意如果你解构了
props
对象,解构出的变量将会丢失响应性。因此我们推荐通过props.xxx
的形式来使用其中的 props。
如果你确实需要解构 props
对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么你可以使用 toRefs() 和 toRef() 这两个工具函数:
{ setup(props) { // 将 `props` 转为一个其中全是 ref 的对象,然后解构 const { title } = toRefs(props) // `title` 是一个追踪着 `props.title` 的 ref console.log(title.value) // 或者,将 `props` 的单个属性转为一个 ref const title = toRef(props, 'title') } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>组合式API</title> </head> <body> <div id="app"> <button @click="num++">加1</button> {{ num }} <my-root :num="num"></my-root> </div> </body> <script src="../lib/vue.global.js"></script> <template id="root"> <div>{{ num }} -- {{ test }}</div> </template> <script> const { ref, onMounted, computed } = Vue const Root = { props: ['num'], template: '#root', // setup (props) { // 千万不要对 props 解构 // console.log('111') // return { // test: computed(() => props.num) // 继续保持响应式 // } // } setup ({ num }) { console.log(num) return { test: computed(() => num) // 失去了响应式 - test的值不会发生改变 } } } Vue.createApp({ setup () { const num = ref(10000) return { num } }, components: { MyRoot: Root } }).mount('#app') </script> </html>
1.3 Setup的上下文
传入 setup
函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup
中可能会用到的值:
{ setup(props, context) { // 透传 Attributes(非响应式的对象,等价于 $attrs) console.log(context.attrs) // 插槽(非响应式的对象,等价于 $slots) console.log(context.slots) // 触发事件(函数,等价于 $emit) console.log(context.emit) // 暴露公共属性(函数) console.log(context.expose) } }
该上下文对象是非响应式的,可以安全地解构:
{ setup(props, { attrs, slots, emit, expose }) { ... } }
attrs
和 slots
都是有状态的对象,它们总是会随着组件自身的更新而更新。这意味着你应当避免解构它们,并始终通过 attrs.x
或 slots.x
的形式使用其中的属性。此外还需注意,和 props
不同,attrs
和 slots
的属性都不是响应式的。如果你想要基于 attrs
或 slots
的改变来执行副作用,那么你应该在 onBeforeUpdate
生命周期钩子中编写相关逻辑。
expose
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose
函数暴露出的内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>setup上下文对象</title> </head> <body> <div id="app"> <my-com ref="comref" class="myBox" style="color: red" id="box" msg="hello msg" @my-event="getData"> <template #header> header </template> <div>content</div> <template #footer> footer </template> </my-com> </div> </body> <template id="com"> <div> <h1>子组件</h1> <button @click="sendData">发送数据</button> <slot name="header"></slot> <slot></slot> <slot name="footer"></slot> </div> </template> <script src="../lib/vue.global.js"></script> <script> const { createApp, ref, onMounted } = Vue const Com = { template: '#com', setup (props, context) { // attrs 获取透传过来的值 // slots 如果使用了插槽 // emit 子组件给父组件传值 // expose 子组件暴露给父组件可以调用的属性和方法 ---- options API ref获取子组件的实例 console.log(props) console.log(context.attrs) // ref 不在透传之列 console.log(context.slots) const sendData = () => { // 子组件给父组件传值 context.emit('my-event', 1000) } // 自定义的属性和方法,供给父组件使用 const a = ref(1) const b = ref(2) const c = ref(3) const fn = () => { a.value = 100 } // 暴露出去的是对象 context.expose({ a, b, fn }) return { sendData } } } Vue.createApp({ setup () { const getData = (val) => { // 接收子组件的值 console.log('666', val) } const comref = ref() // comref 就是模版中ref="comref" onMounted(() => { console.log('com', comref.value) // {} console.log('a', comref.value.a) // 1 console.log('b', comref.value.b) // 2 console.log('c', comref.value.c) // undefined 因为没有暴露 comref.value.fn() console.log('a', comref.value.a) // 100 }) return { getData, comref } }, components: { MyCom: Com } }).mount('#app') </script> </html>
在父组件通过ref获取子组件的实例的属性和方法的需求中,需要注意:
1.如果子组件是 选项式API组件,基本不需要做任何操作
2.如果子组件是 组合式API组件,需要通过 context.expose 暴露给父组件需要使用的属性和方法
3.如果父组件使用 选项式API, 可以通过 this.$refs.refName 访问到子组件想要你看到的属性和方法
4.如果父组件使用 组合式API,需要在setup中先创建 refName,然后再访问子组件想要你看到的属性和方法(const refName = ref() refName.value.X)
1.4 与渲染函数一起使用
setup
也可以返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:
{ setup() { const count = ref(0) return () => h('div', count.value) } }
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题
我们可以通过调用 expose() 解决这个问题:
{ setup(props, { expose }) { const count = ref(0) const increment = () => ++count.value expose({ increment }) return () => h('div', count.value) } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>渲染函数</title> </head> <body> <div id="app"> <button @click="add">加1</button> <my-child ref="child"></my-child> </div> </body> <script src="../lib/vue.global.js"></script> <script> const { h, ref } = Vue const Child = { // 写法1: // template: `<div>child</div>` // 写法2: // render () { // return [ // h('div', 'child!') // ] // } // 写法3 setup (props, { expose }) { const count = ref(10) const increment = () => { count.value += 1 } expose({ increment }) // 返回一个函数 函数返回 渲染函数的结果 return () => h('div', 'child!!' + count.value) } } Vue.createApp({ components: { MyChild: Child }, setup () { const child = ref() const add = () => { child.value.increment() } return { child, add } } }).mount('#app') </script> </html>
到此这篇关于vue3中的setup()函数详解的文章就介绍到这了,更多相关vue3 setup()函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!