先搞懂,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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。