vue 项目优化之函数式组件

前言

如果有个组件,不需要任何状态管理(在 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 文件

也可以直接写成函数,需要定义 functionaltrue

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 属性上,可以看到对于传统组件,它对应的 tagvue-component-2-Title

而函数式组件对应的 tag 直接就是个 div

结论

通过对比得出结论,函数式组件相比传统组件,其优势在于:

  1. 渲染速度更快;
  2. 渲染开销更低。

但是劣势就是只适用于比较简单的 ui 元素,所以什么时候应用函数式组件进行优化,还是需要根据具体场景进行权衡。

相关推荐
FungLeo2 分钟前
Flutter 带 TTL 的多级缓存设计:内存+磁盘+网络三层实战
网络·flutter·缓存·性能优化
用户0595401744617 分钟前
把AI聊天机器人记忆存储测试从3小时压到5分钟,我用Pytest + Docker搭了一套自动化回归
前端·css
明月_清风18 分钟前
显存即正义:不同显存容量能训多大的模型?一文说清硬件边界与训练策略
前端·后端·ai编程
Sterting23 分钟前
第 9 节:本地存储 — 数据不丢的前端缓存
前端·javascript·缓存
IT_陈寒24 分钟前
Vite的HMR在我项目上突然失效,排查三天找到离谱原因
前端·人工智能·后端
人间凡尔赛32 分钟前
React Compiler 1.0 正式落地:告别 useMemo / useCallback,2026 前端性能优化的新范式
前端·性能优化·react
灵析表格1 小时前
灵析表格功能函数深度分析报告
前端·数据库·microsoft
Data_Journal1 小时前
掌握网页抓取中的分页:完整指南
java·服务器·前端
breeze jiang1 小时前
React useRef + Web Worker:避免大计算阻塞页面的通信方案
前端·javascript·react.js