vue3.0 setup的理解(转载有出处)
- vue3中的setup有什么用?
setup的设计是为了使用组合式api - 为什么不用之前的组件的选项
data、computed、methods、watch 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。而通过setup可以将该部分抽离成函数,让其他开发者就不用关心该部分逻辑了. - setup的在vue生命周期的位置
setup位于created 和beforeCreated只前,用于代替created 和beforeCreated,但是在setup函数里不能访问到this,另外setup内可以通过以下hook操作整个生命周期
onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,onErrorCaptured,onRenderTracked,onRenderTriggered
- setup可以接收哪些参数?
setup可接受props,context
,其中props
由于是响应式数据,不能直接解构赋值,context
不是响应式数据,可以直接解构赋值;setup必须返回一个对象,一旦return,就可以像vue2.x的方式使用该属性
1 2 3 4 5 6 7 8 9 | props:[ 'test' ] setup(props,context){ //const {test} = props //错 const {test} = toRefs(props) //对 const { attrs, slots, emit }= context //对 return { test } } |
- 优先级,如果data,props,setup都有一个同名属性,setup返回的该属性优先级最高,以执行以下代码为例,将显示:
test from son's setup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //father.vue ... <custField :test= "test" /> setup(){ const test = ref( 'test from father' ) return { test } } ... //son.vue <template> <div class = "custField" > 优先级测试 <h1>{{ test }}</h1> </div> </template> <script> import { toRefs } from "vue" ; export default { props: [ "test" ], data() { return { test: "test from son's data" , }; }, setup(props) { let test = toRefs(props); test = "test from son's setup" ; return { test }; }, }; </script> |
执行结果如下

结果
- 如上代码所示,若要在setup内执行ref,toRefs,toRef,computed,watch,watchEffect等函数,需要通过import的方式从vue中引入后才能使用,eg:
import { toRefs, ref, onMounted, nextTick } from "vue";
- 如何在setup中拿到ref对应的子组件,并执行其的函数,场景如下:使用antd的form表单的验证,在vue2.x方案时可以在methods中通过this时需要使用
this.$refs.ruleForm.validate()
,而在setup中拿不到this,应该从{ref}入手,看下面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //... <a-form ref= "ruleForm" :model= "form" :rules= "rules" > <a-form-item ref= "name" label= "Activity name" name= "name" > <a-input v-model:value= "form.name" /> </a-form-item> <a-form-item :wrapper-col= "{ span: 14, offset: 4 }" > <a-button type= "primary" @click= "onSubmit" > 验证</a-button> <a-button style= "margin-left: 10px" @click= "resetForm" > 重置</a-button> </a-form-item> </a-form> //...vue2.x methods: { onSubmit() { this .$refs.ruleForm .validate() .then(() => { console.log( 'values' , this .form); }) . catch (error => { console.log( 'error' , error); }); }, resetForm() { this .$refs.ruleForm.resetFields(); }, }, //..vue3 setup(){ //1.设置一个 <a-form // ref="ruleForm" // :model="form" // :rules="rules" //> ref同名属性,并使用ref(null)包装 const ruleForm=ref( null ) //通过ref或reactive包裹起来让其成为响应式数据 //2.一旦后面return {ruleForm},vue3会自动绑定ref="ruleForm"的组件 //设定方法,但是要通过ruleForm.value才能拿到组件 const onSubmit=()=>{ ruleForm.value //通过ref包裹的数据需要使用.value来取得相应的值 .validate() //,而reactive包裹的数据不需要通过.value来取得相应的值 .then(() => { console.log( "values" , form); }) . catch ((error) => { console.log( "error" , error); }); } const resetForm = () => { console.log( "resetForm" ); ruleForm.value.resetFields(); }; //3.setup必须返回一个对象,把vue在生命周期需要调用的方法,属性暴露出去 return { ruleForm, //Q:为什么上面要用.value的形式,A:这里会自动解绑 onSubmit, resetForm } } |
- 目前"ant-design-vue": "^2.0.0-rc.8",与"vue": "^3.0.0",在使用a-form里的submit事件时,若需要校验,会使得校验无论如何都不能走then,只能走catch,需要将a-form的submit事件改为按钮执行方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | //以下代码上述版本会导致点击提交表单时数据验证通不过 <a-form ref= "ruleFormRef" :model= "formData" :rules= "rules" :label-col= "labelCol" :wrapper-col= "wrapperCol" @submit= 'onSubmit' > //.... <a-form-item :wrapper-col= "{ span: 14, offset: 4 }" > <a-button type= "primary" html-type= 'submit' > Create </a-button> </a-form-item> </a-form> //... setup(){ const ruleFormRef = ref( null ) const onSubmit= () => { ruleFormRef.value .validate() .then(() => { console.log( 'sucess' ) //上述版本无论如何都不会执行then,只会走catch }) . catch ((error) => { console.log( "error" , error); }); } return { onSubmit } } |
- 截止2021/1/14,最新版vite与最新版antd有冲突,得改成vuecli;
冲突版本:vite"vite": "^1.0.0-rc.1"与"ant-design-vue": "^2.0.0-rc.8", - 如何调用子组件内setup内的方法?
i. 子组件在setup写好方法method
,并通过return
暴露出去
ii. 父组件调用子组件时为其添加ref
属性
iii. 父组件setup内拿到ii添加的ref
属性property
,再通过property.value.method()
调用
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <template> // 渲染从父级接受到的值 <div>Son: {{ valueRef }}</div> </template> <script lang= "ts" > import { defineComponent, ref } from 'vue' export default defineComponent({ name: 'Son' , setup() { const valueRef = ref( '' ) // 该函数可以接受父级传递一个参数,并修改valueRef的值 const acceptValue = (value: string) => (valueRef.value = value) return { acceptValue, valueRef } } }) </script> |
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <template> <div>sonRef</div> <button @click= "sendValue" >send</button> // 这里ref接受的字符串,要setup返回的ref类型的变量同名 <Son ref= "sonRef" /> </template> <script lang= "ts" > import { defineComponent, ref } from 'vue' import Son from '@/components/Son.vue' export default defineComponent({ name: 'Demo' , components: { Son }, setup() { // 如果ref初始值是一个空,可以用于接受一个实例 // vue3中获取实例的方式和vue2略有不同 const sonRef = ref() const sendValue = () => { // 可以拿到son组件实例,并调用其setup返回的所有信息 console.log(sonRef.value) // 通过调用son组件实例的方法,向其传递数据 sonRef.value.acceptValue( '123456' ) } return { sonRef, sendValue } } }) </script> |
defineComponent
是便于typescript推断类型的组件构造函数,可以传入name,data,setup,methods等参数,如果只有setup,则可以直接传入setup方法;但是注意:若要在setup中使用props属性,props为必输,所以方法二访问不到props属性
//方法一,传入其他属性
1 2 3 4 5 6 7 8 | export default defineComponent({ name: 'xxx' , props:[ 'aaa' ] //若要在setup中使用props,必输props setup(props,context){ const aaa = ref(aaa) return {aaa} } }) |
//方法二,直接传入setup函数 export default defineComponent((props,context)=>{ //...你的代码 ,但是注意这里props拿不到实际数据 })
- vue3如何setup函数如何实现多属性监听,如何实现深度监听?
i. 引入watch
,watch最后返回unwatch方法,在调用该方法将停止监听
ii. watch传入数组,注意,监听的是普通类型可直接输入,若是引用类型,则需要输入函数返回的值,例如要想同时监听data.form.c.c1
属性和ddd
属性
iii. 对于watch第三个传参deep
和immediate
都不陌生,而flush
的作用是决定callback的执行时机,有三个选项,pre
(默认),post
,sync
,分别对应watch在组件更新前,后,时执行callback.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | const ddd = ref( "wwww" ); const data = reactive({ form: { a: 1, b: 2, c: { c1: "c1" , c2: "c2" , }, }, haha: "haha" , }); const unwatch = watch( [ddd, () => data.form.c.c1], //传入数组 (newValue, oldValue) => { //结构的也是数组, //也可以写成([nowddd,nowC1],[preddd,preC1])=>{...} console.log(` new --->${newValue}`); console.log(`old--->${oldValue}`); console.log(newValue[0]); console.log(newValue); }, { deep: true } //第三个参数传入deep,immediate,flush属性 ); setTimeout(() => { ddd.value = "eee" ; }, 1000); setTimeout(() => { data.form.c.c1 = "2222" ; setTimeout(() => { unwatch(); //这里异步使用unwatch方法,后面的ddd.value = "ffff"将不被监听 }); }, 2000); setTimeout(() => { ddd.value = "ffff" ; }, 3000); |
- vue3的watchEffect有什么用?
i. 它是一个与侦听器,作用和watch差不多,但是不能拿到newValue
和oldValue
,下面是它的定义,传参effect函数
和option对象
,effect函数
又可传入onInvalidate函数
,option对象
可传入flush,onTrack,onTrigger
,flush
与watch的flush相同,onTrack,onTrigger
又可传入DebuggerEvent 函数
用于开发调试,返回与watch相同返回一个停止侦听的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function watchEffect( effect: (onInvalidate: InvalidateCbRegistrator) => void, options?: WatchEffectOptions ): StopHandle; interface WatchEffectOptions { flush?: "pre" | "post" | "sync" ; onTrack?: (event: DebuggerEvent) => void; onTrigger?: (event: DebuggerEvent) => void; } interface DebuggerEvent { effect: ReactiveEffect; target: any; type: OperationTypes; key: string | symbol | undefined; } type InvalidateCbRegistrator = (invalidate: () => void) => void; type StopHandle = () => void; |
ii. 传参的effect函数
会在组件beforeCreate
之前就执行一次,若该函数里使用到了某些数据,将监听该数据,当监听的数据发生变化时就会(若watchEffect
传入了onInvalidate函数
,则会先执行onInvalidate函数
后)再次执行effect函数
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | <template> <div class = "about" > <h1>This is an about page</h1> <h4>{{ count }}</h4> <h4>{{ test }}</h4> <button @click= "jump" >jump</button> </div> </template> <script lang= 'ts' > import { onMounted, ref, watchEffect, onBeforeMount } from "vue" ; import { useRoute, useRouter } from "vue-router" ; const fetchData = () => { return new Promise((resolve) => { setTimeout(() => { resolve( "success" ); }, 1000); }); }; export default { setup() { const test = ref( "test" ); const route = useRoute(); const router = useRouter(); const count = ref(0); const effect = async (onInvalidate) => { console.log( '监听route' +route.query); onInvalidate(() => { console.log( "执行onInvalidate" ); }); const res = await fetchData(); console.log(res); test.value = res; }; onBeforeMount(() => { console.log( "onBeforeMount" ); }); onMounted(() => { console.log( "onmounted" ); }); const unWachEffect = watchEffect(effect); useRoute(); setTimeout(() => { console.log( "5秒时间后注销WachEffect" ); unWachEffect(); }, 5000); setInterval(() => count.value++, 1000); //每一秒count自加1,因为watchEffect带有该参数,所以改变时会自动触发 const jump = () => { router.push(`?time=${ new Date().getTime()}`); }; return { count, jump, test }; }, beforeCreate() { console.log( "beforeCreate" ); }, }; </script> |
iii. onInvalidate函数
的执行时机,
(1). effect里的值改变时,会先于内部函数执行
(2). 侦听器被停止(组件unMounted也会关闭侦听器)
- 如何使用向vue2那样读取route和使用router?
使用import { useRoute, useRouter } from "vue-router";
如上面示例代码所示.
作者:Nelson_sylar
链接:https://www.jianshu.com/p/f47556e726de
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话