分享VUE3编写组件高级技巧,优雅!

7,255 阅读3分钟

在这里,主要分享一些平时写VUE组件,会用到一些技巧,会让代码得到很大的简化,可以节省很多脑力体力。

1、v-bind=“$attrs”

这是首推的一个技巧写法,特别在拓展开源组件时,无缝使用开源组件各种props值时,简直不要太爽。

比如element-ui组件中的select组件,就有一个让人痛恨的点,就是options数据无法配置,必须得手动引入option组件才行,如下:

 <el-select v-model="value" placeholder="Select" size="large">  
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />  
</el-select>

身为一个优秀前端前端佬,这哪里能忍!

技巧随之用来,我们就可以使用上面这个,创建一个自定义select组件,既能享用原组件的各种配置属性和事件 (P.S. 如果需要组件上使用自定义事件,比如change事件,属性上定义为’onChange': ()=>{}。),也可以自定义一些功能,创建一个customSelect.vue:

<el-select v-model="selectedValue" v-bind="attts">
    <el-option v-for="(item) in customOptions" v-bind="item"/>
</el-select>

这样在动态引用这个组件,就能使用自定义的customOptions这个属性。

上面例子主要说明,v-bind="$attr"的好处。但还是得多说一句,上面例子中的一些缺点。

  1. 无法直接使用el-select对外暴露的方法;
  2. 无法直接使用el-select的slot分发;

然后需要注意一个点,得在customSelect.vue组件中,设置inheritAttrs为false,防止数据在组件上一层层透传下去。

2、improt { h } from vue

h为vue中的渲染函数,主要用来创建虚拟 DOM 节点 (vnode)。对应参数,可以戳这里,看官方详细正宗介绍。

对应这个连接中,有很多渲染函数的介绍。这系列有一个很大的特点,那就是用的魔怔了,就会一不小心把VUE变成“React”,损失掉VUE框架中的一些优点。

自由度非常高。仅仅针对这个H函数举例,还是援用上面的例子,实现如下(代码片段):

<scirpt>
import {defineComponent, h} from 'vue'
import {ElSelect, ElOption} from 'element-plus'

export default definComponent({
    name:'DynamicSelect',
    props:{
        options:{
            type:Array,
            required:true,
            default:() => []
        }
    },
    setup(props) {
        return () = >
        h(ElSelect, () => 
            props.options.map(options =>            
                h(ElOption, {
                   key:option.value,
                   label:option.label,
                   value:option.value,
                })
           )
        )
    }
})
<script>

足够清爽,简单。

3、render

render,用于编程式地创建组件虚拟 DOM 树的函数。解释链接,可以戳这里

废话不多说,直接以上面的例子,用render方式撸一遍。

<!-- <template>
    <div>1</div>
<template> -->

<scirpt>
import {defineComponent, h} from 'vue'
import {ElSelect, ElOption} from 'element-plus'

export default definComponent({
    name:'DynamicSelect',
    props:{
        options:{
            type:Array,
            required:true,
            default:() => []
        }
    },
    render(_ctx) {
        return () = >
        h(ElSelect, () => 
            _ctx.options.map(options =>            
                h(ElOption, {
                   key:option.value,
                   label:option.label,
                   value:option.value,
                })
           )
        )
    }
})
<script>

不能说实现的方式跟上面相似,简直说是一模一样。主要在于render做了template要做的事,但相比较template少一层解析,理论上会比template更高效。

需要注意一点,这里放出官网的描述:

如果一个组件中同时存在 render 和 template,则 render 将具有更高的优先级

正常理解的话,是render渲染出的vnode会高于template解析出的vnode,同时存在,render会覆盖掉template。

但在经过VUE的v3.3.4版本中操作,template会覆盖掉render。所以这个优先级,猜测可能是render会优先解析,具体得翻源码,待理解后继续更新。

4、getCurrentInstance

这个是获取当前组件实例的方法,属于核弹级别的方法,也属于VUE3官网文档中翻不到的东西。

但鲁迅说的好,路走的多了,那就成路了。如果社区用的人多了,那么它就有可能提上去!

image.png

言归正传,那么拿了这个组件的实例,能干什么呢?

那可干的事情,就可多可多了。

比如改个,调个组件方法,这都算小儿科,完全不用担心这里readOnly,那里ReadonlyReactiveHandler

猛一点,直接硬插,换个上下文。

再猛的,先假设:组件实例 === 组件,组件 === VUE,VUE === YYX写的,然后你写了一点代码+VUE,是不是由此可得,你的代码 》 VUE ,进而证明 你 》 YYX。嗯?

93B1A00879A9B67271080936B8A2D89CE1D69417_size242_w423_h220.gif

5、extends

先来一段官方的介绍:

从实现角度来看,extends 几乎和 mixins 相同。通过 extends 指定的组件将会当作第一个 mixin 来处理。

然而,extends 和 mixins 表达的是不同的目标。mixins 选项基本用于组合功能,而 extends 则一般更关注继承关系。

缺点上,第1节有提一个,但还有一个不算是缺点的缺点,相同属性和方法会直接覆盖被继承的组件(钩子函数不会被覆盖),主要在于是否熟悉被继承的组件中的逻辑。用的好就很好,用的不行,就真的很不行。

如果还是用上面的例子作为例子,实现方法如下:

<scirpt>
import {defineComponent, createVNode, render, getCurrentInstance } from 'vue'
import {ElSelect, ElOption} from 'element-plus'

export default definComponent({
    name:'DynamicSelect',
    extends:ElSelect,
    props:{
        options:{
            type:Array,
            required:true,
            default:() => []
        }
    },
    setup(props) {
        return ElSelect.setup(props, context)
    },
    mounted(){
        const curInstance = getCurrentInstance()
        const container = doucment.createElement('div')
        
        this.$props.options.forEach(options => {
            const vNode = createVNode(ElOption,{
                key:option.value,
               label:option.label,
               value:option.value,
            })
        })
        
        const currrentProvides = curInstance?.provides
        if(currrentProvides){
            // 将ELSelect的Provides,传入到ElOption中
            reflect.set(curInstance?.appContext,'provides',{...currrentProvides})
        }
        vNode.appContext = curInstance?.appContext
        render(vNode,container)
        this.$el.appendChild(container)
    }
})
<script>

但这种,确实是为了实现那个例子而写的代码。有些可以作为参考。

暂时分享这些,欢迎前端佬们拍砖。