什么是函数式组件
函数式组件即无状态组件 ,没有data
、computed
、watch
,也没有生命周期方法,组件中也没有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:attrs
、emit
和 slots
。它们分别相当于实例的 $attrs
、$emit
和 $slots
这几个 property。
大多数常规组件的配置选项在函数式组件中都不可用。然而我们还是可以把 props
和 emits
作为 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 就是函数式组件。
- 高阶组件--用于接收一个组件作为参数,返回以一个被包装过的组件。