vue的函数式组件
我们可以把函数式组件想象成组件里的一个函数,入参是渲染上下文(render context),返回值是渲染好的HTMLStateless(无状态):组件自身是没有状态的;Instanceless(无实例):组件自身没有实例,也就没有this。由于函数式徐建拥有拥有这两个特性,我们就可以把它用作高阶组件(High order components),所谓高阶,就是可以生成其他组件的组件。函数式组件没
vue的函数式组件
一、函数式组件的简介
1. 什么是函数式组件?
我们可以把函数式组件想象成组件里的一个函数,入参是渲染上下文(render context),返回值是渲染好的HTML
对于函数式组件,可以这样定义:
- Stateless(无状态):组件自身是没有状态的;
- Instanceless(无实例):组件自身没有实例,也就没有this。
由于函数式组件拥有这两个特性,我们就可以把它用作高阶组件(High order components),所谓高阶,就是可以生成其他组件的组件。
2. 函数式组件的特点
- 没有管理任何状态 ;
- 没有监听任何传递给它的状态 ;
- 没有生命周期方法 ;
- 只接收一些prop的函数;
3. 函数式组件的优点
渲染开销低,因为函数式组件只是函数。
4. 为什么要使用函数式组件
速度快。
因为函数式组件没有状态,所以我们不需要像vue的响应式系统一样需要经过额外的初始化。
函数式组件仍然会对响应的变化做出 响应式变化 ,比如新传入props。但是在组件本身中,它无法知道数据什么时候发生变化,因为它不维护自身状态。
对于大型应用程序,在使用 函数式组件 之后,我们会看到DOM的渲染和更新有很大的改进。
5. 函数式组件的适用场景
函数式组件可能不适用很多情况,因为使用JavaScript框架的目的是构建响应式的应用程序。
适用场景:
- 一个简单的展示组件,例如:button组件,pills,tags,cards,甚至整个页面都是静态文本,比如详情说明页面;
- “ 高阶组件 ” 用于接收一个组件作为参数,返回一个被包装过的组件;
- v-for 循环中的每个子组件。
二、函数式组件的使用
1. 如何声明函数式组件
渲染函数:将组件标记为 functional : true
export default {
name: 'funcBtn',
// 标记为函数式组件
functional: true,
// 必写,functional:true加上render才是函数式组件
render(createElement, context) {
return createElement('button', 'click me')
}
}
单文件组件:在template标签上 functional
<template functional>
<div>
<button>click me</button>
</div>
</template>
<script>
export default {
name: 'funcBtn'
}
</script>
2. context 参数
为了弥补缺少的实例,在render中提供了第二个参数作为上下文,组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:
- props:提供所有 prop 的对象;
- children:VNode 子节点的数组;
- slots:一个函数,返回了包含所有插槽的对象;
- scopedSlots:一个暴露传入的作用域插槽的对象,也以函数形式暴露普通插槽;
- data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件;
- parent:对父组件的引用;
- listeners: 一个包含了所有父组件为当前组件注册的事件监听器的对象,这是 data.on 的一个别名;
- injections:如果使用了 inject 选项,则该对象包含了应当被注入的 property。
现在创建一个父组件App.vue来引入上面的函数式组件:
<template>
<FuncBtn>click me</FuncBtn>
</template>
<script>
import FuncBtn from '../components/FuncBtn'
export default {
name: 'App',
components: {
FuncBtn
}
}
</script>
上面的 click me 就是 FuncBtn 的 children 属性,所以FuncBtn组件可以写为:
<script>
export default {
name: 'FuncBtn',
functional: true,
render (createElement, context) {
return createElement('button', context.children);
}
}
</script>
this.$slots.default => context.children
this.level => context.props.level
使用 es6参数 的解构,用 {children} 来代替 context :
<script>
export default {
name: 'FuncBtn',
functional: true,
render (createElement, {children) {
return createElement('button', children);
}
}
</script>
3. 事件定义
函数式组件没有实例,事件只能由父组件传递。下面在app.vue上定义一个最简单的click事件:
<template>
<FuncBtn @click="handler">click me</FuncBtn>
</template>
<script>
import FuncBtn from '../components/FuncBtn'
export default {
name: 'App',
components: {
FuncBtn
},
methods: {
handler () {
alert(1)
}
}
}
</script>
FuncBtn组件:
<script>
export default {
name: 'FuncBtn',
functional: true,
render (createElement, {props, listeners, children}) {
return createElement('button', {
attrs: props,
on: {
click: listeners.click
}
}, children)
}
}
</script>
页面效果:
点击按钮,执行点击事件:
简单写法:
vue中设计的api比较人性化,我们可以将props、listeners统一集中在data中。
FuncBtn组件:
<script>
export default {
name: 'FuncBtn',
functional: true,
render (createElement, {data, children}) {
// data属性:将所有attribute和事件监听器传下去,并将同名属性替换或智能合并
return createElement('button', data, children)
}
}
</script>
FuncBtn组件中所有的 attribute 和 事件监听器 都通过 data属性 传递下去了,以至于这些事件并不要求 .native 修饰符。
如果使用基于模板的函数式组件,那么还需要手动添加 attribute 和 监听器 。
可以使用 data.attrs 传递 HTML attribute ,使用 listeners 传递 事件监听器 。
FuncBtn组件:
<script>
<template functional>
<button v-bind="data.attrs" v-on="listeners">
<slot></slot>
</button>
</template>
export default {
name: 'FuncBtn'
}
</script>
效果也是一样的。
4. context 参数中 slots() 和 children 的对比
在上述示例中,我们都是通过 children 来获取父组件的插槽内容的,你可能想知道既然 children 可以获取到,为什么还同时需要 slots() 呢?slots().default 不是和 children 类似的吗?在一些场景中,是这样 —— 但如果是带有 具名插槽 的函数式组件呢?
父组件 App.vue:
<template>
<div>
<FuncText :text="text" @click="handler">
<template v-slot:foo>first</template>
<template>second</template>
</FuncText>
</div>
</template>
<script>
import FuncText from '../components/FuncText'
export default {
name: 'App',
components: {
FuncText
},
data () {
return {
text: 'click me'
}
},
methods: {
handler () {
alert(1)
}
}
}
</script>
子组件 FuncText.vue:
使用html模板,代码如下:
<template functional>
<div>
<p>
<slot name="foo"></slot>
</p>
<p>
<slot></slot>
</p>
<button v-bind="data.attrs" v-on="listeners">{{props.text}}</button>
</div>
</template>
为了对比出 slots() 和 children 差别,我们这里使用render渲染函数来完成子组件:
<script>
export default {
name: 'FuncText',
functional: true,
props: {
text: {
type: String,
default: ''
}
},
render (createElement, {props, data, slots, children}) {
return createElement('div', {}, [createElement('p', slots().foo),
createElement('p', slots().default),
createElement('button', data, props.text)])
}
}
</script>
对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。同时拥有 children 和 slots() ,可以选择让组件感知某个插槽机制,还是简单地通过传递 children,移交给其它组件去处理。
总结
更多推荐
所有评论(0)