【vue设计与实现】组件的实现原理 4 - setup函数的作用与实现 组件事件与emit的实现
admin
2024-01-19 14:39:05
0

组件的setup函数时Vue.js3新增的组件选项,setup函数主要用来配合组合式API,为用户提供一个地方,用于建立组合逻辑、创建响应式数据、创建通用函数、注册生命周期钩子等能力
在组件的整个生命周期中,setup函数只会在挂载时执行一次,其返回值可以有两种情况:

  1. 返回一个函数,该函数将作为组件的render函数:
    const Comp = {setup(){// setup 函数可以返回一个函数,该函数将作为组件的渲染函数return ()=>{return { type:'div', children:'hello' }}}	
    }
    
    这种方式常用于组件不是以模板来表达其渲染内容的情况。如果组件以模板来表达其渲染的内容,那么setup函数不可以再返回函数,否则会与模板编译生成的渲染函数产生冲突
  2. 返回一个对象,该对象中包含的数据将暴露给模板使用
    const Comp = {setup(){const count = ref(0)// 返回一个对象,对象中的数据会暴露到渲染函数中return {count}},return ()=>{// 通过this可以访问setup暴露出来的响应式数据return { type:'div', children:`count is: ${this.count}`}}	
    }
    

另外,setup函数接收两个参数,第一个参数是props,第二个参数也是个对象,通常称为setupContext,如下面的代码所示:

const Comp = {props:{foo: String},setup(props, setupContext){props.foo //访问传入的props数据// setupContext中包含与组件接口相关的重要数据const {slots, emit, attrs, expose} = setupContext// ...}
}

其中props为外部为组件传递的props数据对象,setupContext保存着与组件接口相关的数据和方法

通常情况下,不建议将setup与Vue.js2的其他选项混合使用,因为在Vue.js3的场景下,更加提倡组合式API,setup函数就是为组合式API而生的,混合使用会带来语义和理解上的负担

接下来,就围绕上述的这些呢你尝试实现setup组件选项,如下面代码所示:

function mountComponent(vnode, container, anchor){const componentOptions = vnode.type// 从组件选项中取出setup函数let { render, data, setup, /* 省略其他选项*/ } = componentOptionsbeforeCreate && beforeCreate()const state = data ? reactive(data()):nullconst [props, attrs] = resolveProps(propsOption, vnode.props)const instance = {state,props: shallowReactive(props),isMounted: false,subTree: null}// setupContext,暂时实现attrsconst setupContext = { attrs }// 调用setup函数const setupResult = setup(shallowReadOnly(instance.props),setupContext)// setupState用来存储由setup返回的数据let setupState = null// 如果 setup函数的返回值是函数,则将其作为渲染函数if(typeof setupResult === 'function'){// 报告冲突if(render) console.error('setup 函数返回渲染函数,render选项将被忽略')// 将 setupResult 作为渲染函数render = setupResult}else {// 如果setup的返回值不是函数,则作为数据状态赋值给setupStatesetupState = setupContext}vnode.component = instance// 创建渲染上下文对象,本质上是组件实例的代理const renderContext = new Proxy(instance, {get(t,k,r){// 取得组件自身状态与props数据const {state, props } = t// 先尝试读取自身状态数据if(state && k in state){return state[k]}else if(k in props){ // 如果组件自身没有改数据,则尝试从props中读取return props[k]}else{console.error('不存在')}},set(t,k,v,r){const {state, props} = tif(state && k in state){state[k] = v}else if(k in props){ props[k] = v}else{console.error('不存在')}}})// 省略部分代码}

上面是setup函数的最小实现,要注意的是:

  1. setupContext是一个对象
  2. 检测setup函数的返回值类型来决定如何处理。
  3. 渲染上下文renderContext应该正确地处理setupState,因为setup函数返回的数据状态也应该暴露到渲染环境

emit用来发射组件的自定义事件,如下面代码:

const MyComponent = {name: 'MyComponent',setup(props,{emit}){// 发射change时间,并传递给事件处理函数两个参数emit('change',1,2)return()=>{return //...}}
}

当使用该组件时,可以监听由emit函数发射的自定义事件


上面这段对应的虚拟DOM是

const CompVNode = {type: MyComponent,props:{onChange: handler}}

可以看到,自定义事件change被编译成名为onChange的属性,并存储在props数据对象中,这实际上是一种约定。
在具体的实现上,发射自定义事件的本质就是根据事件名称去props数据对象中寻找对应的时间处理函数并执行,如下面代码所示:

function mountComponent(vnode, container, anchor){// 省略部分代码const instance = {state,props: shallowReactive(props),isMounted: false,subTree: null}// 定义emit函数,接收两个参数// event:事件名称// payload: 传递给事件处理函数的参数function emit(event, ...payload){const eventName = `on${event[0].toUpperCase()+event.slice(1)}`// 根据处理后的事件名称去props中寻找对应的时间处理函数const handler = instance.props[eventName]if(handler){// 调用时间处理函数并传递参数handler(...payload)}else{console.error('事件不存在')}}// 将emit函数添加到setupContext中,用户可以通过setupContext取得emit函数const setupContext = {attrs, emit}
}

这里要额外注意的是,在介绍props时提到,任何没有显式声明未props的属性都会存储到attrs中,也就是说,任何事件类型的props,即onXxx类的属性,都不会出现在props中,也就无法根据事件名称在instance.props中找到对应的事件处理函数。
为了解决这个问题,需要再解析props数据的时候对事件类型的props做特殊处理,如下面代码所示:

function resolveProps(options, propsData){const props = {}const attrs = {}for(const key in propsData){// 以字符串on开头的props, 无论是否显式地声明,都将其添加到props数据中,而不是添加到attrs中if(key in options || key.startsWith('on')){props[key] = propsData[key]}else{attrs[key] = propsData[key]}}return [props, attrs]
}

相关内容

热门资讯

恩施五天四晚怎么玩最值?恩施大... 恩施土家族苗族自治州,位于湖北省西南部,地处武陵山脉与大巴山脉交汇处,是一片被北纬30度线穿过的神秘...
吕宇峰成都探访川菜美食文化 “食在中国,味在四川,魂在成都。”川菜作为中国八大菜系之一,以其“一菜一格,百菜百味”的独特魅力享誉...
2025济南西站停车费,济南西... 2025济南西站停车费 停车 30 分钟内 1.5 元,超时后超出部分以 30 分钟为单位(不足 ...
山河海岱和合共美 2025泰山... 齐鲁网·闪电新闻9月10日讯 9月8日,以“山河海岱 和合共美”为主题的2025泰山国际友好景区推介...
天津十大经典名菜,哪款是你的最... 煎烹大虾,一道色香味俱全的地方名肴,属于天津菜。由于采用“煎烹”技法,可使主料直接与炒锅接触受热。保...
这花花期贼短,做好却能吃一年!... ⚬若春秋是吃韭菜的“黄金季”,那时下的韭菜花,便是大自然递来的“百搭料理”。 此时的韭菜花苞鲜绿饱满...
开渔季吃海鲜?这样吃才安全! 8月5日至9月16日,我国东海、黄渤海、南海海域将先后进入“开渔季”,千帆竞发向海逐“鲜”,一大批海...
原创 路... 导读:路边买凉菜时,这3种尽量少买,摊主都不给孩子吃,别不懂了! 很多人遇到路边卖卤菜的,往往随手称...
组图丨泰山云雾锁峰峦 静谧景致... 齐鲁网·闪电新闻9月10日讯 初秋的泰山,少了盛夏的暑气蒸腾,多了份清旷舒爽。晨雾轻笼山腰,将苍劲的...
权威发布!金旅假日蝉联内蒙古口... 近日,内蒙古自治区文化和旅游厅联合国内知名旅游服务评估平台,发布《2025 年度内蒙古旅行社服务质量...
一景通5A景区票务系统 景区多... 一景通5A景区票务管理系统是专为5A级景区设计的综合性数字化解决方案,以“全域协同、智能决策、生态可...
中国影都 XR未来影院 享跨越... 中国影都 XR未来影院 ——享跨越时空奇幻之旅 化身考古学者,穿越到公元前2600年的古埃及;手举...
上海留学机构有哪些 上海作为国际化大都市,留学需求旺盛,留学中介机构众多。选择合适的中介能帮助学生高效规划申请路径、提升...
十堰市“彩带飞扬姐妹团”走进竹... 湖北日报客户端讯(通讯员李家林、杨海金)9月9日,秋高气爽。十堰市“彩带飞扬姐妹团”一行8人循着丰收...
站在安徽“古南岳”之巅,才懂何... 每一步攀登都是与自然的对话,每一眼风景都是岁月的馈赠。 还记得那次登上天柱山顶,看到云海在脚下翻滚时...
原创 赵... 前言 哇哦!猜猜看,今天娱乐圈又爆了什么大新闻?不是绯闻,不是八卦,而是我们最爱的赵丽颖女神晒出了与...
2025中国杏花村国际酒业博览... 9月9日,2025中国杏花村国际酒业博览会新闻发布会在太原举行,本届酒博会由中国酒业协会主办,以“遥...
北美奶茶局:新茶饮“烧钱”开店 文|红餐网 郑颖 8月1日,喜茶位于美国加州库比蒂诺Main Str的门店正式营业。随着这家开在苹...
新疆手抓饭油香扑鼻!羊肉胡萝卜... 本文围绕新疆手抓饭展开,全面介绍这道经典美食。先点明其油香扑鼻的核心特色,阐述以羊肉、胡萝卜与米饭为...
咸甜麻辣一口入魂:手把手教你做... 怪味花生是一道经典的中式零食,核心在于将咸、甜、麻、辣、香等多种风味融合于一体,口感外酥里脆。以下是...