[TOC]

何为函数式组件

当一个组件不需要状态(即响应式数据)、不需要任何生命周期场景、只接受一些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 的对象
  • 接受的是已经注册的特性,未注册的特性不会储存在这里
  • 未注册的特性会保存在data中

    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>
    

    控制台打印效果
    image.png

    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>
    

    控制台打印效果
    image.png

    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 >

    函数式组件示例代码
    ```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>
    

    控制台打印效果
    image.png

    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 下有子元素标明是具名插槽中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,那吗会将 idivp 一起放到默认插槽default中
      children与slots()无区别
      控制台打印效果
      image.png