对Vue functional组件的尝试与小结
1/12/2021, 12:52:22 PM
1/12/2021, 1:08:57 PM
Vue组件由于具有自己的状态,计算开销相对较大。对于简单而大量重复的的组件,这会带来较大的性能开销。 而Vue函数式组件的出现,则让构建轻量级组件成为了可能。下面将粗略介绍Vue functional component。注意:这里并没有使用SFC。
示例
下面的代码片段摘自这个网站的部分源码,作用是将一个文字块渲染为简单的<p>
节点。
// https://github.com/zzs-web/website/blob/master/components/bml/Text.ts
import Vue from 'vue'
import md from '~/utils/markdown'
export default Vue.extend({
name: 'BmlText',
functional: true,
props: {
value: {
type: String,
required: true
}
},
render(h, ctx) {
return h(
'div',
Object.assign({}, ctx.data, {
class: [ctx.data.class],
domProps: {
innerHTML: md.utils
.escapeHtml(ctx.props.value)
.replace(/\n/g, '<br/>')
}
})
)
}
})
可以看到,唯一的魔术就在于这行functional: true
,其将这个组件显式声明为函数式组件。
从下面的定义文件(摘自Vue options.d.ts
)可见它和普通的组件区别主要是无状态:不能在其中访问this
、不能定义watcher
、computed
、data
和methods
。
export interface FunctionalComponentOptions<Props = DefaultProps, PropDefs = PropsDefinition<Props>> {
name?: string;
props?: PropDefs;
model?: {
prop?: string;
event?: string;
};
inject?: InjectOptions;
functional: boolean;
render?(this: undefined, createElement: CreateElement, context: RenderContext<Props>): VNode | VNode[];
}
那么,如何访问props
和其他的属性呢?继续分析代码:
export interface RenderContext<Props=DefaultProps> {
props: Props;
children: VNode[];
slots(): any;
data: VNodeData;
parent: Vue;
listeners: { [key: string]: Function | Function[] };
scopedSlots: { [key: string]: NormalizedScopedSlot };
injections: any
}
可以看到,通过render
的第二个参数context
,我们可以获取到props
等相关的属性。这些属性都是响应式的。
函数式组件的v-model
我们可以看到,函数式组件也支持定义model
。但是既然无法访问this
,我们也无法用this.$emit
更新数据。让我们看另一端代码:
// https://github.com/zzs-web/website/blob/master/components/editor/BasicEditor.ts
import Vue from 'vue'
export default Vue.extend({
name: 'SimpleEditor',
functional: true,
model: {
prop: 'value',
event: 'input'
},
props: {
value: {
type: String,
default: ''
}
},
render(h, ctx) {
const input = (e: any) => {
const handler = ctx.listeners.input as Function
handler(e.target.value)
}
return h(
'div',
{
class: 'px-2 fill-width'
},
[
h('textarea', {
domProps: { value: ctx.props.value },
on: { input },
class: 'z-editor-basic fill-width px-3'
})
]
)
}
})
我们可以直接使用ctx.listeners.EVENT_NAME
来访问父组件的事件处理函数,并直接将事件参数喂给它。同样,v-bind.sync
也可也这么实现。