函数式组件使用场景和原理?

什么是函数式组件

函数式组件即无状态组件 ,没有datacomputedwatch,也没有生命周期方法,组件中也没有this上下文,只有props传参。在开发中,有很多组件仅仅只用到了props和插槽,这部分组件就可以提炼为函数式组件。借用官网demo,最简单的函数式组件如下:

js 复制代码
Vue.component('my-component', {  
    functional: true,  
    // Props 是可选的  
    props: {    
        // ...  
    },  
    // 为了弥补缺少的实例,提供第二个参数作为上下文  
    render: function (createElement, context) {    
        // ...  
    }
})

函数式组件与普通组件的区别:

组件实例化过程大致分为四步,状态初始化 --> 模板编译 --> 生成VNode --> 转换为真实DOM。接下来对比普通组件与函数式组件常用配置项,比较下差异。

从上表中可以看出,普通组件与函数式组件最大的差别在于函数式组件没有独立作用域,没有响应式数据声明。

没有独立作用域,会有以下优点:

  • 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能比普通组件好。

  • 函数式组件结构比较简单,代码结构更清晰。

  • 没有组件实例化(new vnode.componentOptions.Ctor(options)),函数式组件获取VNode仅仅是普通函数调用。

    • 无公共属性、方法拷贝

    • 无生命周期钩子调用

  • 函数式组件直接挂载到父组件中,缩短首次渲染、diff更新路径。

    • 函数式组件在父组件生成VNode时,函数式组件render方法会被调用,生成VNode挂载到父组件children中,patch阶段可直接转换成真是DOM,普通组件则在createElm时,走组件初始化流程。

    • diff更新时,函数式组件调用render,直接创建普通VNode,而普通组件创建的VNode的是包含组件作用域的,diff操作时,还有额外调用updateChildComponent更新属性、自定义事件等,调用链路会比较长。

具体区别

  • 函数式组件需要在声明组件时指定 functional: true。
js 复制代码
//TempVar.js

export default {
    functional: true,
    render: (h, ctx) => {
        return ctx.scopedSlots.default && ctx.scopedSlots.default(ctx.props || {});
    }
}
js 复制代码
<TempVar 
 :var1 = "`hello ${name}`"
 :var2 = "destoryClock ? 'Hello Vue' :'Hello React">

 <template v-slot="{var1, var2}">
   {{var1}}{{var2}}
 </template>
</TempVar>

以上代码,使用 TempVar 传递了var1, var2 这两个变量,通过作用域插槽返回 var1 和 var2,这时 var1 和 var2 成为临时变量。之后可以在作用插槽中随便使用 var1 和 var2。

  • 不需要实例化,所以没有 this, this 通过 Render 函数的第二个参数 context 来代替。
js 复制代码
const FunctionalComponent = (props, context) => {
    // ... 
}

第二个参数 context 包含三个 property:attrsemitslots。它们分别相当于实例的 $attrs$emit$slots 这几个 property。

大多数常规组件的配置选项在函数式组件中都不可用。然而我们还是可以把 propsemits 作为 property 加入,以达到定义它们的目的:

js 复制代码
FunctionalComponent.props = ['value'];
FunctionalComponent.emits = ['click'];

如果这个 props 选项没有被定义,那么被传入函数的 props 对象就会像 attrs 一样会包含所有 attribute。除非指定了 props 选项,否则每个 prop 的名字将不会基于驼峰命名法被一般化处理。

函数式组件可以像普通组件一样被注册和消费。如果你将一个函数作为第一个参数传入 h,它将会被当作一个函数式组件来对待。

js 复制代码
import { h } from 'vue';
const DynamicHeading = (props, context) => {
    return h(`h${props.level}`, context.attrs, context.slots);
}
DynamicHeading.props = ['level'];
export default DynamicHeading;
  • 没有声明周期钩子函数,不能使用计算属性 watch 。

  • 不能通过 $emit 对外暴露事件,调用事件只能通过 context.listener.click 的方式调用外部传入的事件,因为函数式组件是没有实例化的,所以在外部通过 ref 去引用组件时,实际引用的是 HTMLElement 。

  • 函数式组件的 props 可以不用显示声明,所以没有在 props 里声明的属性会被自动隐式解析为 prop,而普通组件所有未声明的属性都解析到 $atters 里,并且自动挂载到组件根元素上(可以通过 inheritAttrs属性禁止)。

使用场景

  • 简单展示组件,作为容器组件使用,例如 Router-View 就是函数式组件。
  • 高阶组件--用于接收一个组件作为参数,返回以一个被包装过的组件。
相关推荐
浪裡遊18 小时前
MUI组件库与主题系统全面指南
开发语言·前端·javascript·vue.js·react.js·前端框架·node.js
DiXinWang18 小时前
关闭谷歌浏览器提示“若要接收后续 Google Chrome 更新,您需使用 Windows 10 或更高版本”的方法
前端·chrome
CoderYanger18 小时前
前端基础——HTML练习项目:填写简历信息
前端·css·职场和发展·html
muyouking1118 小时前
深入理解 HTML `<label>` 的 `for` 属性:提升表单可访问性与用户体验
前端·html·ux
IT_陈寒19 小时前
Java性能调优:从GC日志分析到实战优化的5个关键技巧,让你的应用快如闪电!
前端·人工智能·后端
Mintopia19 小时前
🚀 Next.js API 压力测试:一场前端与后端的“极限拉扯”
前端·后端·全栈
Mintopia19 小时前
🛡️ 对抗性攻击与防御:WebAI模型的安全加固技术
前端·javascript·aigc
庙堂龙吟奈我何19 小时前
qiankun知识点
前端
SoaringHeart19 小时前
Flutter封装:原生路由管理极简封装 AppNavigator
前端·flutter
menu19 小时前
AI给我的建议
前端