• 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')
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    单文件组件:在template标签上 functional

    <template functional>
      <div>
        <button>click me</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'funcBtn'
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的 click me 就是 FuncBtn 的 children 属性,所以FuncBtn组件可以写为:

    <script>
    export default {
      name: 'FuncBtn',
      functional: true,
      render (createElement, context) {
        return createElement('button', context.children);
      }
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    FuncBtn组件:

    <script>
    export default {
      name: 'FuncBtn',
      functional: true,
      render (createElement, {props, listeners, children}) {
      	return createElement('button', {
    	  attrs: props,
    	  on: {
    		click: listeners.click
    	  }
    	}, children)
      }
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    页面效果:
    在这里插入图片描述
    点击按钮,执行点击事件:
    在这里插入图片描述
    简单写法

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    效果也是一样的。

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    子组件 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    为了对比出 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。同时拥有 childrenslots() ,可以选择让组件感知某个插槽机制,还是简单地通过传递 children,移交给其它组件去处理。


    总结

    参考链接: vue函数式组件
    官网地址: 函数式组件

  • 相关阅读:
    客户端负载均衡_负载均衡策略
    线性回归详解(代码实现+理论证明)
    U3d力扣基础刷题-2
    [每周一更]-(第23期):Docker 逻辑图及常用命令汇总
    Shell编程三剑客之awk
    【Unity】自定义Untiy天空
    如何利用 RPA 实现自动化获客?
    2024华为OD机考面试经验分享
    C# 继承
    MinGW、GCC、GNU和MSVC是什么?有什么区别?
  • 原文地址:https://blog.csdn.net/qq_38374286/article/details/126603282