何为函数式组件
当一个组件不需要状态(即响应式数据)、不需要任何生命周期场景、只接受一些props来显示组件时,我们可以将其标记为函数式组件。因为函数式组件只是函数,所以渲染开销会低很多。
functional: true,
如何获取props
函数式组件中是无法使用响应式数据,和生命周期,其中this指向unfinished
为了弥补缺少的实例,render 函数提供第二个参数context作为上下文。context包括如下字段:
- props:提供所有 prop 的对象
- slots: 一个函数,返回了包含所有插槽(非作用域)的对象
- scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
- data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
- parent:对父组件的引用
- listeners: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
- injections: (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性。
- children: VNode 子节点的数组,包含了所有的非作用域插槽和非具名插槽。
在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。
props 与 slots的示例
props
- 提供所有 prop 的对象
- 接受的是已经注册的特性,未注册的特性不会储存在这里
-
slots()
-
示例代码
父组件代码
<template> <base-props :level="level" :b="b"> 标题 </base-props> </template> <script> import baseProps from "./components/base-props" export default { name: "App", components: { baseProps, }, data() { return { level: "1", b:"b", }; } } </script>
函数式组件示例代码
<script> export default { functional: true, props:{ level:{ type:String, required:true, } }, render(h,context){ const {props, slots,} = context; console.log("props ↓ ") console.log(props) console.log('props ↑') console.log("slots() ↓ ") console.log(slots()) console.log('slots() ↑') const tag = 'h' + props.level return( <tag> {slots().default} </tag> ) } } </script>
scopedSlots
scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
示例代码
父组件代码
<template> <div> <base-slots> <template #header="slotProps"> <div>{{slotProps.text}}</div> </template> <template #default> <i>我是默认插槽</i> </template> </base-slots> </div> </template> <script> import baseSlots from "./components/base-slots" export default { name: "App", components: { baseSlots, }, } </script>
函数式组件示例代码
<script> export default { functional: true, render(h,context){ const {props, slots,data,scopedSlots} = context; console.log("slots() ↓ ") console.log(slots()) console.log('slots() ↑') console.log("scopedSlots ↓ ") console.log(scopedSlots) console.log('scopedSlots ↑') return( <div> <div> 使用具名插槽: {slots().default} </div> <div> 利用scopedSlots使用具名插槽: {scopedSlots.default()} </div> <div> 利用scopedSlots使用作用域插槽: {scopedSlots.header({ text:"我是作用插槽" })} </div> </div> ) } } </script>
listeners
listeners: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
示例
父组件示例代码
<template> <div> <base-listeners @click="handleEvent"></base-listeners> </div> </template> <script> import baseListeners from "./components/baseListeners" export default { name: "App", components: { baseListeners, }, methods:{ handleEvent(){ console.log("触发") } } } </script>
函数式组件示例代码
<script> export default { functional: true, methods:{ }, render(h,context){ const {props, slots,data,scopedSlots,parent,listeners} = context; console.log("listeners ↓ ") console.log(listeners) console.log('listeners ↑') return( <div on-click={listeners.click}> listeners </div> ) } } </script>
children
VNode 子节点的数组,包含了所有的非作用域插槽和非具名插槽。
- 当父组件引入的子组件的模板代码下有具名插槽default时,default插槽其余不是插槽的节点不会渲染
- 当父组件引入的子组件的模板代码下没有具名插槽default时,具名插槽与作用域插槽外的节点会被放进具名插槽default中渲染
- 总结:当父组件引入的子组件的模板代码下有具名插槽default时会选择具名插槽内的节点,而忽略其他非插槽内的节点
示例代码
父组件中的代码 ```vue><base-children> <template #default> <i>我是默认插槽</i> </template> <template #header> <i>我是header插槽</i> </template> <div>div</div> <p>p</p> </base-children>
函数式组件示例代码 ```vue <script> export default { functional: true, methods:{ }, render(h,context){ const {props, slots,scopedSlots,data,parent,listeners,injections,children} = context; console.log("children ↓ ") console.log(children) console.log('children ↑') return( <div> {children} </div> ) } } </script>
slots()与children的区别
区别
- VNode 子节点的数组,包含了所有的非作用域插槽和非具名插槽。
- 第一种情况:当父组件引入的子组件的模板代码下有具名插槽default时,default插槽其余不是插槽的节点不会渲染
- 第二种情况:当父组件引入的子组件的模板代码下没有具名插槽default时,具名插槽与作用域插槽外的节点会被放进具名插槽default中渲染
总结:当父组件引入的子组件的模板代码下有具名插槽default时会选择具名插槽内的节点,而忽略其他非插槽内的节点
子组件中的代码
两种情况下子组件代码不变
<script> export default { functional: true, methods:{ }, render(h,context){ const {props, slots,scopedSlots,data,parent,listeners,injections,children} = context; console.log("slots() ↓ ") console.log(slots()) console.log('slots() ↑') console.log("children ↓ ") console.log(children) console.log('children ↑') return( <div> {children} </div> ) } } </script>
第一种情况示例
父组件中的代码 ```vue
<base-children> <template #default> <i>我是默认插槽</i> </template> <template #header> <i>我是header插槽</i> </template> <div>div</div> <p>p</p> </base-children>
**子组件 base-children 下有子元素标明是具名插槽中default,但是 `div` `p` 元素并没有标明具名插槽,此时会优先选择拥有具名插槽default下的元素,而忽略掉 `div` 与 `p` 元素 且这俩元素不会被渲染,这俩元素会被放到children中**<br />**children与slots()的有区别 **<br />控制台打印效果<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1376603/1615607152198-92c1673f-4ac4-41d4-87f5-a05bc7160dda.png#align=left&display=inline&height=395&margin=%5Bobject%20Object%5D&name=image.png&originHeight=395&originWidth=819&size=28729&status=done&style=none&width=819) <a name="plRXX"></a> #### 第二种情况示例 父组件中的代码 ```vue <template> <div> <base-children> <template> <i>我是默认插槽</i> </template> <template #header> <i>我是header插槽</i> </template> <div>div</div> <p>p</p> </base-children> </div> </template> <script> import baseChildren from "./components/baseChildren" export default { name: "App", components: { baseChildren, }, } </script>
子组件 base-children 下 没有子元素标明是具名插槽中default,那吗会将
i
与div
与p
一起放到默认插槽default中
children与slots()无区别
控制台打印效果