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

什么是函数式组件

函数式组件即无状态组件 ,没有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 就是函数式组件。
  • 高阶组件--用于接收一个组件作为参数,返回以一个被包装过的组件。
相关推荐
编程猪猪侠28 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞32 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架