先搞懂,Vue2里functional component是啥?
不少刚接触Vue2的同学,看到代码里出现 functional: true 或者 <template functional>,总会有点懵——这“函数式组件”到底是干啥的?和普通组件有啥不一样?啥时候用它能解决实际问题?今天咱们就把Vue2里functional component的知识点拆碎了讲,从概念到实战一次搞明白。
Vue里的组件分“有状态组件”和“函数式组件”。函数式组件是一种无状态、无实例的轻量组件,它没有自己的 data,也没有生命周期钩子(像 created、mounted 这些都用不了),甚至连 this 上下文都没有——完全是个“纯函数”,接收输入(props、上下文信息),输出虚拟DOM。  
为啥叫“函数式”?因为它的逻辑像函数一样:输入确定,输出就确定,没有副作用(比如不会自己修改内部状态),在Vue2里,定义函数式组件有两种方式:
- 用选项式API时,在组件选项里加 functional: true,然后写render函数;
- 用单文件组件(.vue)时,在 <template>标签上加functional属性,变成<template functional>,模板里通过$props、$slots这些来拿数据。
举个极简例子(选项式写法):
Vue.component('MyFunctional', {
  functional: true,
  props: {
    msg: String
  },
  render(h, context) {
    // context包含props、children、slots、listeners等
    return h('div', context.props.msg)
  }
})
这种组件没有自己的实例,渲染时更高效,因为少了Vue实例的初始化开销。
哪些场景适合用functional component?
不是所有组件都得用函数式,得看场景是否匹配它“无状态、纯渲染”的特点:
纯展示型组件,只做UI渲染
比如列表里的每一项(TodoItem)、图标组件(Icon)、静态文案组件,这些组件不需要自己管理状态,只需要接收父组件传的props,然后渲染DOM。
举个场景:做一个“用户头像+昵称”的展示组件 <UserAvatarName>,父组件传 avatarUrl 和 nickname,组件只负责把这俩数据拼成DOM,这种场景用函数式组件就很合适——不需要 data,也不需要生命周期,纯渲染。  
性能敏感的列表渲染
如果页面有长列表(比如几百条数据的表格),每个列表项用普通组件的话,每个组件都要初始化实例、走生命周期,性能会有损耗,换成函数式组件,因为没有实例开销,渲染速度能快不少。
作为“容器型组件”处理插槽、逻辑分发
有些组件本身不渲染自己的DOM,只负责把插槽内容加工后再输出(比如布局组件、权限控制组件),比如做一个 <AuthButton>,根据用户权限决定显示“普通按钮”还是“管理员按钮”,内部用函数式组件来判断props里的权限,然后渲染对应的插槽内容,逻辑更轻量。  
怎么写一个Vue2的functional component?
分选项式API写法和单文件组件(.vue)写法两种,咱们分别看:
写法1:选项式API(JS里定义组件)
核心是 functional: true + render 函数。render 函数接收两个参数:h(createElement,用来创建虚拟DOM)和 context(上下文对象,包含 props、children、slots、listeners 等)。  
步骤:
- 声明 functional: true;
- 定义 props(函数式组件的props必须显式声明,不然拿不到!);
- 在 render里用h和context生成DOM。
示例:做一个带样式的文本组件 <StyledText>,接收 color 和 text 两个 props,渲染带颜色的 span:  
Vue.component('StyledText', {
  functional: true,
  props: {
    color: {
      type: String,
      default: '#333'
    },
    text: String
  },
  render(h, context) {
    return h(
      'span', 
      { 
        style: { color: context.props.color } 
      }, 
      context.props.text
    )
  }
})
写法2:单文件组件(.vue)+ <template functional>
这种写法更贴近日常开发(很多项目用单文件组件),在 <template> 上加 functional 后,模板里不能用 this,但可以用 $props(接收的 props)、$slots(插槽)、$attrs(未被 props 接收的属性)、$listeners(父组件绑定的事件)。  
示例:用单文件写上面的 <StyledText>:  
<template functional>
  <span :style="{ color: $props.color }">
    {{ $props.text }}
  </span>
</template>
<script>
export default {
  props: {
    color: {
      type: String,
      default: '#333'
    },
    text: String
  }
}
</script>
注意:单文件的函数式组件,模板里的变量都要通过 $props、$slots 这些来访问,不能像普通组件那样用 this.xxx~  
functional component和普通组件有啥核心区别?
很多同学刚开始分不清,咱们从实例、状态、生命周期、性能这几个维度对比:
| 对比维度 | 普通组件 | 函数式组件 | 
|---|---|---|
| 组件实例 | 有自己的实例( this指向实例) | 无实例,没有 this | 
| 状态管理 | 可以有 data、computed、watch | 没有 data,只能靠props传值 | 
| 生命周期钩子 | 有 created、mounted等全周期 | 没有任何生命周期钩子 | 
| 性能消耗 | 实例初始化+生命周期执行,稍重 | 无实例,渲染更快,性能更优 | 
| props接收 | 支持隐式(没声明也能通过 $attrs拿) | 必须显式声明 props,否则拿不到 | 
举个直观的例子:普通组件里可以写 mounted() { console.log('组件挂载了') },但函数式组件里写 mounted 会直接报错,因为它根本没有这个钩子~  
用functional component要避开哪些坑?
函数式组件因为“无实例、纯函数”的特性,用的时候有几个容易踩的坑,提前避坑:
props 必须“显式声明”
普通组件如果没在 props 里声明某个属性,还能通过 this.$attrs 拿到,但函数式组件必须在 props 里声明,否则 context.props 或 $props 里拿不到这个属性。  
比如父组件给函数式组件传了 extraInfo,但组件没在 props 里声明,那 context.props.extraInfo undefined,只能去 context.attrs 里找(但 attrs 是未被 props 接收的属性,所以最好还是显式声明 props)。  
不能用 this,事件处理要找 context.listeners
普通组件里触发事件用 this.$emit('xxx'),但函数式组件没有 this,所以要通过 context.listeners 来触发事件。  
示例:父组件给函数式组件绑了 @click="handleClick",函数式组件里要触发点击事件,得这么写:  
render(h, context) {
  return h('button', {
    on: {
      click: () => context.listeners.click() // 触发父组件的click事件
    }
  }, '点我')
}
如果是单文件+<template functional>,模板里可以用 $listeners:  
<template functional> <button @click="$listeners.click">点我</button> </template>
插槽的使用要注意 $slots 的结构
函数式组件里访问插槽,要通过 context.slots()(选项式)或 $slots(单文件 functional 模板),比如默认插槽是 context.slots().default(选项式),单文件里是 $slots.default。  
举个例子:父组件给函数式组件传了插槽内容,组件要渲染插槽:
选项式写法:  
render(h, context) {
  return h('div', [
    context.slots().default // 渲染默认插槽内容
  ])
}
单文件写法:
<template functional>
  <div>
    <slot></slot> <!-- 等价于 $slots.default -->
  </div>
</template>
结合Vue生态,functional component能和哪些技术结合?
函数式组件不是孤立的,在Vue生态里能和很多技术配合,解决更复杂的问题:
和Vuex配合,做“无状态展示组件”
Vuex里的 state 是全局状态,很多展示组件只需要“读”state,不需要“写”,比如一个 <TodoList> 组件,只负责把Vuex里的 todos 列表渲染成DOM,这种组件用函数式更合适——不需要自己管理状态,纯靠Vuex的 mapState 拿数据,然后渲染。  
和UI组件库结合,优化基础组件性能
像Element UI、Ant Design Vue这些UI库,很多基础组件(比如Icon、ButtonGroup)其实是函数式组件,因为这些组件不需要复杂的状态管理,纯展示+少量逻辑,用函数式能减少性能开销,让组件更轻量。
和JSX结合,写更灵活的渲染逻辑
Vue支持JSX(需要配置babel插件),函数式组件和JSX简直是绝配——因为函数式组件的 render 函数本质就是返回虚拟DOM,用JSX写结构更直观。  
Vue.component('JsxFunctional', {
  functional: true,
  props: {
    list: Array
  },
  render(h, context) {
    return (
      <ul>
        {context.props.list.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    )
  }
})
用JSX写循环、条件渲染比纯 h 函数更简洁,函数式组件+JSX能让代码可读性更高。  
现在Vue3都普及了,Vue2的functional component还有学习必要吗?
不少同学会纠结:Vue3都用Composition API了,学Vue2的函数式组件是不是过时了?其实有必要,原因有三:
存量Vue2项目还很多,维护离不开
国内很多公司的老项目还在用Vue2,遇到函数式组件的代码,不懂的话根本没法维护,比如接手一个三年前的Vue2项目,里面大量用functional component做性能优化,你得能看懂、能修改。
Vue3的“函数式组件”思想一脉相承(但实现变了)
Vue3里没有专门的 functional 选项了,而是通过“无渲染组件”(基于setup语法糖)来实现类似的效果,比如Vue3里写:  
<template>
  <div>{{ props.msg }}</div>
</template>
<script setup>
const props = defineProps(['msg'])
</script>
这种组件也是无状态、轻量的,和Vue2函数式组件的设计思想一致——理解了Vue2的函数式,学Vue3的无渲染组件更容易。
锻炼“纯函数式思维”,对理解响应式很有帮助
函数式组件要求“输入决定输出”,没有副作用,这种思维在前端开发里很重要,比如写React的函数式组件、Vue3的Composition API,都需要这种“纯函数”的思维方式,学Vue2的functional component,能帮你建立这种思维,以后学其他框架也更顺。
Vue2的functional component是个“轻量、无状态、纯渲染”的工具,适合用在展示型、性能敏感、容器型的场景里,虽然Vue3的实现方式变了,但核心思想没丢,如果现在要维护Vue2项目,或者想理解前端组件的“函数式思维”,把functional component吃透绝对不亏~ 下次遇到项目里的 <template functional> 或者 functional: true,就知道该怎么上手改代码啦!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
 code前端网
code前端网



 
		
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。