前言
如果有个组件,不需要任何状态管理(在 vue2 中就是没有 data
),只需要接收 props
,而且还不需要监听任何传递给它的状态,即不需要监听 props
的变化,我们就可以把它定义成函数式组件来进行优化。
所谓函数式组件,是指其相较于传统组件:
- 它是没有状态的,即没有响应式数据的组件;
- 没有生命周期方法;
- vue 也不会为该组件创建实例,故而不会有
this
上下文。
举例
比如有个需求是要封装一个展示标题的组件,该组件只需要接收一个 prop ------ title
,即标题的文字。vue2 和 vue3 的函数式组件写法略有不同,下面分开举例。
vue2
传统组件
传统组件写法如下:
vue
<!-- src\components\Title.vue -->
<template>
<div class="title">{{ title }}</div>
</template>
<script>
export default {
name: 'Title',
props: {
title: {
type: String,
default: ''
}
}
}
</script>
<style scoped>
.title {
font-size: 30px;
}
</style>
函数式组件
在 vue2 中,函数式组件有 2 种写法。
写法一:.vue 文件
如果写成 .vue 文件,则需要在 <template>
添加 functional
,并且在 <template>
内使用 prop 时要通过 props
,比如 {{ props.title }}
:
vue
<!-- src\components\FunctionalTitle.vue -->
<template functional>
<div class="title">{{ props.title }}</div>
</template>
<script>
export default {
name: 'FunctionalTitle',
props: {
title: {
type: String,
default: ''
}
}
}
</script>
<style scoped>
.title {
font-size: 30px;
}
</style>
写法二:.js 文件
也可以直接写成函数,需要定义 functional
为 true
:
vue
// src\components\FunctionalTitle.js
export default {
name: 'FunctionalTitle',
functional: true,
props: {
title: {
type: String,
default: ''
}
},
render(h, ctx) {
return h('div', { style: { 'font-size': '30px' } }, ctx.props.title)
}
}
其中 render
函数的第 1 个参数 h
为渲染函数,第 2 个参数 ctx
打印如下,可以看到其中有个属性 props
,可以获取到传入的 title
:
父组件
父组件代码如下,点击"加载传统组件"按钮则循环生成 10000 个传统组件;点击"加载函数式组件"则循环生成 10000 个函数式组件:
vue
<!-- src\App.vue -->
<template>
<div id="app">
<button @click="num1 = 10000">加载传统组件</button>
<button @click="num2 = 10000">加载函数式组件</button>
<div class="box">
<div>
<div v-for="item in num1" :key="item">
<Title :title="'标题-' + item" />
</div>
</div>
<div>
<div v-for="item in num2" :key="item">
<FunctionalTitle :title="'标题-' + item" />
</div>
</div>
</div>
</div>
</template>
<script>
import Title from './components/Title.vue'
// import FunctionalTitle from './components/FunctionalTitle.vue'
import FunctionalTitle from './components/FunctionalTitle.js'
export default {
components: {
Title,
FunctionalTitle
},
data() {
return {
num1: 0,
num2: 0
}
}
}
</script>
<style>
.box {
display: flex;
}
</style>
vue3
传统组件
vue3 中传统组件写法如下:
vue
<!-- src\components\Title.vue -->
<script lang="ts" setup>
const { title = '' } = defineProps<{ title?: string }>()
</script>
<template>
<div class="title">{{ title }}</div>
</template>
<style scoped>
.title {
font-size: 30px;
}
</style>
函数式组件
vue3 中,函数式组件可以直接在父组件通过函数定义:
vue
<!-- src\App.vue -->
<script setup lang="ts">
import Title from './components/Title.vue'
import { h, ref } from 'vue'
import type { FunctionalComponent } from 'vue'
const num1 = ref(0)
const num2 = ref(0)
// 创建函数式组件
type FComponentProps = {
title: string
}
const FunctionalTitle: FunctionalComponent<FComponentProps> = (props, ctx) => {
return h('div', { style: { 'font-size': '30px' } }, props.title)
}
</script>
<template>
<button @click="num1 = 10000">加载传统组件</button>
<button @click="num2 = 10000">加载函数式组件</button>
<div class="box">
<div>
<div v-for="item in num1" :key="item">
<Title :title="'标题-' + item" />
</div>
</div>
<div>
<div v-for="item in num2" :key="item">
<FunctionalTitle :title="'标题-' + item" />
</div>
</div>
</div>
</template>
<style scoped>
.box {
display: flex;
}
</style>
定义函数式组件的函数参数中,如果打印查看第 2 个参数 ctx
,结果如下:
性能对比
以 vue2 项目为例进行测试,调出谷歌浏览器的"性能"面板,点击"录制",然后分别点击"加载传统组件"和"加载函数式组件"按钮,结果如下。
传统组件
加载 10000 个传统组件,js 的运行时间为 189 毫秒,内存从 61.2 MB 增加到了 100 MB,增加了近 40 MB:
函数式组件
而加载 10000 个函数式组件,js 的运行时间为 114 毫秒,内存从 100 MB 增加到了 125 MB,增加了 25 MB:
如果在 App.vue 添加生命周期函数,将实例 this
传给 window.vm
:
javascript
mounted() {
window.vm = this
}
然后将点击 2 个按钮的 click 事件改为如下所示,即只各自渲染 1 个传统组件和函数式组件:
html
<button @click="num1 = 1">加载传统组件</button>
<button @click="num2 = 1">加载函数式组件</button>
在控制台打印查看 vm
,可以看到 vue 只为传统组件创建了实例 VueComponent,故而传统组件要比函数式组件的渲染速度更快,占用内存更少:
在 vm
的 _vnode
属性上,可以看到对于传统组件,它对应的 tag
是 vue-component-2-Title
:
而函数式组件对应的 tag
直接就是个 div
:
结论
通过对比得出结论,函数式组件相比传统组件,其优势在于:
- 渲染速度更快;
- 渲染开销更低。
但是劣势就是只适用于比较简单的 ui 元素,所以什么时候应用函数式组件进行优化,还是需要根据具体场景进行权衡。