今天在看Vue源码的时候,看见了一个新的名词, 函数式组件。自学Vue也有一些时间了,但是在我的印象中,好像并没有听说过这个名词,于是乎google了一下一圈,发现还是Vue的官方文档写得清楚明白。
之前创建的锚点标题组件是比较简单,没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数。在这样的场景下,我们可以将组件标记为
functional,这意味它无状态 (没有响应式数据),也没有实例 (没有this上下文)。
即是说,如果你的组件比较简单,没有进行任何的状态管理,没有生命周期,只接收一些props参数,那么,我们可以将这个组件标记为 functional,该组件既无状态,也无实例(即this),这样的一个组件,我们便称之为函数式组件。
在js文件中,我们可以像下面这样创建函数式组件。
Vue.Component("functional-com",{
functional:true, // 标记为函数式组件
props:[],
render(h,context){
...
}
})
如果在.vue的单文件组件中,则我们可以这样做
<template functional>
<div>{{props.name}}</div>
</template>
在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则
props选项是必须的。在 2.3.0 或以上的版本中,你可以省略props选项,所有组件上的 attribute 都会被自动隐式解析为 prop。
组件需要的一切都是通过
context参数传递,它是一个包括如下字段的对象:
props:提供所有 prop 的对象children:VNode 子节点的数组slots:一个函数,返回了包含所有插槽的对象scopedSlots:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。data:传递给组件的整个数据对象,作为createElement的第二个参数传入组件parent:对父组件的引用listeners:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是data.on的一个别名。injections:(2.3.0+) 如果使用了inject选项,则该对象包含了应当被注入的 property。
在单组件.vue中,上述变量可以直接访问到。
因为函数式组件只是函数,所以渲染开销也低很多。
在作为包装组件时它们也同样非常有用。比如,当你需要做这些时:
- 程序化地在多个组件中选择一个来代为渲染;
- 在将
children、props、data传递给子组件之前操作它们。
上面提到了函数式组件的两个优点,一是渲染开销的低,二是可对子组件进行增强。
以下代码是element-ui中 divider 组件的源码,我们可以看见也是使用了函数式组件。
<template functional>
<div
v-bind="data.attrs"
v-on="listeners"
:class="[data.staticClass, 'el-divider', `el-divider--${props.direction}`]"
>
<div
v-if="slots().default && props.direction !== 'vertical'"
:class="['el-divider__text', `is-${props.contentPosition}`]"
>
<slot />
</div>
</div>
</template>
<script>
export default {
name: 'ElDivider',
props: {
direction: {
type: String,
default: 'horizontal',
validator(val) {
return ['horizontal', 'vertical'].indexOf(val) !== -1;
}
},
contentPosition: {
type: String,
default: 'center',
validator(val) {
return ['left', 'center', 'right'].indexOf(val) !== -1;
}
}
}
};
</script>
不仅仅是可以降低渲染开销,还可以增强子组件的功能,比如下面的这样一个支持防抖的组件。
<script>
/**
* 防抖函数
* @param {Funtion} target 需要进行防抖处理的函数
* @param {number} delay 延时时间,ms
* @returns 处理后的函数
*/
function debounce(target, delay) {
if (typeof target !== "function") {
throw TypeError("the first argument must be a Function");
}
if (typeof delay !== "number" || delay < 0) {
delay = 500;
}
let id;
// 此处不能使用箭头函数,否则 argument 失效
return function () {
clearTimeout(id);
id = setTimeout(() => {
target(...arguments);
}, delay);
};
}
export default {
name: "debounce-com",
functional: true,
props: {
time: {
type: Number,
default: 500,
},
event: {
type: String,
default: "click",
},
},
render(h, context) {
let { time, event } = context.props;
const childList = context.slots().default;
const vnode = childList[0] || null;
if (vnode === null) {
console.error("<debounce-com>必须拥有子元素");
return null;
}
const func = vnode.data.on[event];
if (func === undefined || func === null) {
console.error(`you must give me the ${event} method`);
}
vnode.data.on[event] = debounce(func, time);
return vnode;
},
};
</script>
在该组件中,我们可以获取到子组件所注册的事件,并进行增强,即进行防抖。同理,我们最终也可以使用相同的方式进行节流组件的编写。
Debounce组件全部代码见下面:
没啥可总结的,简单的笔记,如果文中有什么不对的地方欢迎指正,也欢迎大家评论区交流。