Code前端首页关于Code前端联系我们

先搞懂,Vue2里functional component是啥?

terry 6小时前 阅读数 8 #Vue
文章标签 Vue2 函数式组件

不少刚接触Vue2的同学,看到代码里出现 functional: true 或者 <template functional>,总会有点懵——这“函数式组件”到底是干啥的?和普通组件有啥不一样?啥时候用它能解决实际问题?今天咱们就把Vue2里functional component的知识点拆碎了讲,从概念到实战一次搞明白。

Vue里的组件分“有状态组件”和“函数式组件”。函数式组件是一种无状态、无实例的轻量组件,它没有自己的 data,也没有生命周期钩子(像 createdmounted 这些都用不了),甚至连 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>,父组件传 avatarUrlnickname,组件只负责把这俩数据拼成DOM,这种场景用函数式组件就很合适——不需要 data,也不需要生命周期,纯渲染。

性能敏感的列表渲染

如果页面有长列表(比如几百条数据的表格),每个列表项用普通组件的话,每个组件都要初始化实例、走生命周期,性能会有损耗,换成函数式组件,因为没有实例开销,渲染速度能快不少。

作为“容器型组件”处理插槽、逻辑分发

有些组件本身不渲染自己的DOM,只负责把插槽内容加工后再输出(比如布局组件、权限控制组件),比如做一个 <AuthButton>,根据用户权限决定显示“普通按钮”还是“管理员按钮”,内部用函数式组件来判断props里的权限,然后渲染对应的插槽内容,逻辑更轻量。

怎么写一个Vue2的functional component?

选项式API写法单文件组件(.vue)写法两种,咱们分别看:

写法1:选项式API(JS里定义组件)

核心是 functional: true + render 函数。render 函数接收两个参数:h(createElement,用来创建虚拟DOM)和 context(上下文对象,包含 propschildrenslotslisteners 等)。

步骤:

  1. 声明 functional: true
  2. 定义 props(函数式组件的 props 必须显式声明,不然拿不到!);
  3. render 里用 hcontext 生成DOM。

示例:做一个带样式的文本组件 <StyledText>,接收 colortext 两个 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
状态管理 可以有 datacomputedwatch 没有 data,只能靠 props 传值
生命周期钩子 createdmounted 等全周期 没有任何生命周期钩子
性能消耗 实例初始化+生命周期执行,稍重 无实例,渲染更快,性能更优
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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门