2025-11-23 16:04:26
import { createApp, h } from '../dist/vue.esm.js'
function App() {
return h('div', 'hello world')
}
const app = createApp(App)
app.mount('#app')
这是函数式组件最简单的使用方式。
没有 setup,也没有组件选项对象,本身就是一个渲染函数:执行组件函数后,直接返回需要渲染的 vnode。
渲染流程
先重新梳理一下整体链路:
- 通过
createApp创建- 通过
createRenderer创建渲染器:packages/runtime-core/src/renderer.ts - 通过
createApp创建应用对象:packages/runtime-core/src/apiCreateApp.ts - 通过
h函数创建根组件对应的vnode - 执行
render进入渲染流程
- 通过
- 通过
mount挂载到页面
对于普通组件来说,type 通常是一个对象;对于函数式组件来说,type 就是函数本身。
所以核心要处理三件事:
- 创建 vnode 时识别函数式组件
- 渲染组件根节点时直接执行函数
- 调用函数时传入
props、attrs、slots和emit
实现
1. 标记函数式组件
export function createVNode(type, props?, children = null) {
// ...
else if (isFunction(type)) {
shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT
}
const vnode = {
// ...
}
normalizeChildren(vnode, children)
return vnode
}
createVNode 会根据 type 判断 vnode 类型。
当 type 是函数时,将它标记为 ShapeFlags.FUNCTIONAL_COMPONENT。
后续 patch 处理组件时,仍然会走组件的挂载流程,只是在渲染根节点时需要区分有状态组件和函数式组件。
2. 渲染函数式组件
export function renderComponentRoot(instance) {
const { vnode } = instance
// 有状态组件
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
setCurrentRenderingInstance(instance)
const subTree = instance.render.call(instance.proxy)
unsetCurrentRenderingInstance()
return subTree
}
else {
// 函数式组件
return vnode.type()
}
}
有状态组件的渲染入口是 instance.render。
函数式组件没有 render 选项,也不需要通过代理对象访问状态,所以可以直接执行 vnode.type()。
这里的 vnode.type 就是组件函数本身:
function App() {
return h('div', 'hello world')
}
执行后的返回值就是组件的 subTree,后续继续交给 patch 处理。
3. 支持 props attrs emits
函数式组件也需要接收外部传入的数据。
不过它对 props 有一个特殊规则:
- 没有声明
props时,所有传入属性都会作为props - 声明了
props时,根据声明结果区分props和attrs
function App(props, ctx) {
return h('div', props.count)
}
App.props = ['count']
const app = createApp(App, {
count: 1,
msg: 'hello world'
})
在这个例子中:
count会进入propsmsg会进入attrs
props 的设置逻辑在 packages/runtime-core/src/componentProps.ts -> setFullProps 中处理:
function setFullProps(instance, rawProps, props, attrs) {
const { propsOptions, vnode } = instance
const isFunctionalComponent
= vnode.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT
const hasProps = Object.keys(propsOptions).length > 0
if (rawProps) {
for (const key in rawProps) {
const value = rawProps[key]
if (hasOwn(propsOptions, key) || (isFunctionalComponent && !hasProps)) {
props[key] = value
}
else {
attrs[key] = value
}
}
}
}
判断条件里多了函数式组件的处理:
isFunctionalComponent && !hasProps
这表示:如果当前是函数式组件,并且组件没有声明 props,那么传入的属性都放到 props 上。
最后,在渲染函数式组件时,把这些数据作为参数传进去:
export function renderComponentRoot(instance) {
const { vnode } = instance
// 有状态组件
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// ...
}
else {
// 函数式组件
return vnode.type(instance.props, {
get attrs() {
return instance.attrs
},
slots: instance.slots,
emit: instance.emit
})
}
}
这样函数式组件就可以通过两个参数拿到数据:
function App(props, ctx) {
return h('div', props.count)
}
- 第一个参数是
props - 第二个参数是上下文对象,里面包含
attrs、slots和emit
优势
函数式组件没有状态管理、生命周期、实例属性和响应式数据的处理。
因此,它更适合用在无状态的 UI 组件,或者高频渲染但逻辑很轻的组件中。
但:
- 函数式组件没有
this上下文 props的处理方式和普通组件也有区别