vue中你不知道但却很实用的黑科技(代码片段)

baimeishaoxia baimeishaoxia     2022-12-24     528

关键词:

最近数月一直投身于 iView 的开源工作中,完成了大大小小 30 多个 UI 组件,在 Vue 组件化开发中积累了不少经验。其中也有很多带有技巧性和黑科技的组件,这些特性有的是 Vue 文档中提到但却容易被忽略的,有的更是没有写在文档里,今天就说说 Vue 组件的高级玩法。

写在前面

本文所讲内容大多在 iView 项目中使用,大家可以前往关注,并结合源代码来研究其中的奥妙。项目地址:
https://github.com/iview/iview

目录

  • 递归组件

  • 自定义组件使用 v-model

  • 使用$compile()在指定上下文中手动编译组件

  • 内联模板inline-template

  • 隐式创建 Vue 实例

递归组件

递归组件在文档中有介绍,只要给组件指定一个 name字段,就可以在该组件递归地调用自己,例如:

var iview = Vue.extend(
  name: ‘iview‘,
  template:
    ‘<div>‘ +
      // 递归地调用它自己
      ‘<iview></iview>‘ +
    ‘</div>‘
)

这种用法在业务中并不常见,在 iView 的级联选择组件中使用了该特性
https://github.com/iview/iview/tree/master/src/components/cascader
效果如下图所示:
技术图片

图中每一列是一个组件(caspanel.vue),一开始想到用 v-for来渲染列表,但后面发现扩展性极低,而且随着功能的丰富,实现起来很困难,处理的逻辑很多,于是改写成了递归组件:

<ul v-if="data && data.length" :class="[prefixCls + ‘-menu‘]">
    <Casitem
        v-for="item in data"
        :prefix-cls="prefixCls"
        :data.sync="item"
        :tmp-item="tmpItem"
        @click.stop="handleClickItem(item)"
        @mouseenter.stop="handleHoverItem(item)"></Casitem>
</ul><Caspanel v-if="sublist && sublist.length" :prefix-cls="prefixCls" :data.sync="sublist" :disabled="disabled" :trigger="trigger" :change-on-select="changeOnSelect"></Caspanel>

props 比较多,可以忽略,但其中关键的两个是datasublist,即当前列数据和子集的数据,因为预先不知道有多少下级,所以只需传递下级数据给组件本身,如果为空时,递归就结束了,Vue 这样设计的确很精妙。
注:该方法在 Vue 1.x 和 2.x 中都支持。

自定义组件使用 v-model

我们知道,v-model是在表单类元素上进行双向绑定时使用的,比如:

<template>
    <input type="text" v-model="data">
     data 
</template>
<script>
    export default 
        data () 
            return 
                data: ‘‘
            
        
    
</script>

这时data就是双向绑定的,输入的内容会实时显示在页面上。在 Vue 1.x 中,自定义组件可以使用 props 的.sync双向绑定,比如:

<my-component :data.sync="data"></my-component>

在 Vue 2.x 中,可以直接在自定义组件上使用 v-model了,比如:

<my-component v-model="data"></my-component>

在组件my-component中,通过this.$emit(‘input‘)就可以改变data的值了。
虽然 Vue 1.x 中无法这样使用,但是如果你的组件的模板外层是 inputselecttextarea等支持绑定 v-model 特性的元素,也是可以使用的,比如 my-component 的代码是:

<template>
    <input type="text">
</template>

那也可以使用上面2.x的写法。

使用$compile()在指定上下文中手动编译组件

注:该方法是在 Vue 1.x 中的使用介绍,官方文档并没有给出该方法的任何说明,不可过多依赖此方法。
使用$compile()方法,可以在任何一个指定的上下文(Vue实例)上手动编译组件,该方法在 iView 新发布的表格组件 Table 中有使用:
https://github.com/iview/iview/tree/master/src/components/table/cell.vue
由于表格的列配置是通过一个 Object 传入 props 的,因此不能像 slot 那样自动编译带有 Vue 代码的部分,因为传入的都是字符串,比如:


    render (row) 
        return `<i-button>$row.name</i-button>`
    

render函数最终返回一个字符串,里面含有一个自定义组件 i-button,如果直接用 显示,i-button 是不会被编译的,那为了实现在单元格内支持渲染自定义组件,就用到了$compile()方法。
比如我们在组件的父级编译:

// 代码片段
const template = this.render(this.row);    // 通过上面的render函数得到字符串
const div = document.createElement(‘div‘);
div.innerHTML = template;
this.$parent.$compile(div);    // 在父级上下文编译组件
this.$el.appendChild(cell);    // 将编译后的html插入当前组件

这样一来, i-button就被编译了。
在某些时候使用$compile()确实能带来益处,不过也会遇到很多问题值得思考:

  • 这样编译容易把作用域搞混,所以要知道是在哪个Vue实例上编译的;

  • 手动编译后,也需要在合适的时候使用$destroy()手动销毁;

  • 有时候容易重复编译,所以要记得保存当前编译实例的id,这里可以通过 Vue 组件的_uid来唯一标识(每个Vue实例都会有一个递增的id,可以通过this._uid获取)

另外,Vue 1.x 文档也有提到另一个$mount()方法,可以实现类似的效果,在 Vue 2.x 文档中,有 Vue.compile()方法,用于在render函数中编译模板字符串,读者可以结合来看。

内联模板inline-template

内联模板并不是什么新鲜东西,文档中也有说明,只是平时几乎用不到,所以也容易忽略。简短解说,就是把组件的 slot 当做这个组件的模板来使用,这样更为灵活:

<!-- 父组件: -->
<my-component inline-template>
     data 
</my-component>

<!-- 子组件 -->
<script>
    export default 
        data () 
            return 
                data: ‘‘
            
        
    
</script>

因为使用了 inline-template 内联模板,所以子组件不需要<template>来声明模板,这时它的模板直接是从 slot 来的 data ,而这个 data 所在的上下文,是子组件的,并不是父组件的,所以,在使用内联模板时,最容易产生的误区就是混淆作用域。

隐式创建 Vue 实例

在 webpack 中,我们都是用 .vue 单文件的模式来开发,每个文件即一个组件,在需要的地方通过 components: 来使用组件。
比如我们需要一个提示框组件,可能会在父级中这样写:

<template>
    <Message>这是提示标题</Message>
</template>
<script>
    import Message from ‘../components/message.vue‘;
    export default 
        components:  Message 
    
</script>

这样写没有任何问题,但从使用角度想,我们其实并不期望这样来用,反而原生的window.alert(‘这是提示标题‘)这样使用起来更灵活,那这时很多人可能就用原生 JS 拼字符串写一个函数了,这也没问题,不过如果你的提示框组件比较复杂,而且多处复用,这种方法还是不友好的,体现不到 Vue 的价值。
iView 在开发全局提示组件(Message)、通知提醒组件(Notice)、对话框组件(Modal)时,内部都是使用 Vue 来渲染,但却是 JS 来隐式地创建这些实例,这样我们就可以像Message.info(‘标题‘)这样使用,但其内部还是通过 Vue 来管理。相关代码地址:
https://github.com/iview/iview/tree/master/src/components/base/notification

下面我们来看一下具体实现:
技术图片
上图是最终效果图,这部分 .vue 代码比较简单,相信大家都能写出这样一个组件来,所以直接说创建实例的部分,先看下核心代码:

import Notification from ‘./notification.vue‘;
import Vue from ‘vue‘;
import  camelcaseToHyphen  from ‘../../../utils/assist‘;

Notification.newInstance = properties => 
    const _props = properties || ;

    let props = ‘‘;
    Object.keys(_props).forEach(prop => 
        props += ‘ :‘ + camelcaseToHyphen(prop) + ‘=‘ + prop;
    );

    const div = document.createElement(‘div‘);
    div.innerHTML = `<notification$props></notification>`;
    document.body.appendChild(div);

    const notification = new Vue(
        el: div,
        data: _props,
        components:  Notification 
    ).$children[0];

    return 
        notice (noticeProps) 
            notification.add(noticeProps);
        ,
        remove (key) 
            notification.close(key);
        ,
        component: notification,
        destroy () 
            document.body.removeChild(div);
        
    
;

export default Notification;

与上文介绍的$compile()不同的是,这种方法是在全局(body)直接使用 new Vue创建一个 Vue 实例,我们只需要在入口处对外暴露几个 API 即可:

import Notification from ‘../base/notification‘;

const prefixCls = ‘ivu-message‘;
const iconPrefixCls = ‘ivu-icon‘;
const prefixKey = ‘ivu_message_key_‘;

let defaultDuration = 1.5;
let top;
let messageInstance;
let key = 1;

const iconTypes = 
    ‘info‘: ‘information-circled‘,
    ‘success‘: ‘checkmark-circled‘,
    ‘warning‘: ‘android-alert‘,
    ‘error‘: ‘close-circled‘,
    ‘loading‘: ‘load-c‘
;

function getMessageInstance () 
    messageInstance = messageInstance || Notification.newInstance(
        prefixCls: prefixCls,
        style: 
            top: `$toppx`
        
    );

    return messageInstance;


function notice (content, duration = defaultDuration, type, onClose) 
    if (!onClose) 
        onClose = function () 

        
    
    const iconType = iconTypes[type];

    // if loading
    const loadCls = type === ‘loading‘ ? ‘ ivu-load-loop‘ : ‘‘;

    let instance = getMessageInstance();

    instance.notice(
        key: `$prefixKey$key`,
        duration: duration,
        style: ,
        transitionName: ‘move-up‘,
        content: `
            <div class="$prefixCls-custom-content $prefixCls-$type">
                <i class="$iconPrefixCls $iconPrefixCls-$iconType$loadCls"></i>
                <span>$content</span>
            </div>
        `,
        onClose: onClose
    );

    // 用于手动消除
    return (function () 
        let target = key++;

        return function () 
            instance.remove(`$prefixKey$target`);
        
    )();


export default 
    info (content, duration, onClose) 
        return notice(content, duration, ‘info‘, onClose);
    ,
    success (content, duration, onClose) 
        return notice(content, duration, ‘success‘, onClose);
    ,
    warning (content, duration, onClose) 
        return notice(content, duration, ‘warning‘, onClose);
    ,
    error (content, duration, onClose) 
        return notice(content, duration, ‘error‘, onClose);
    ,
    loading (content, duration, onClose) 
        return notice(content, duration, ‘loading‘, onClose);
    ,
    config (options) 
        if (options.top) 
            top = options.top;
        
        if (options.duration) 
            defaultDuration = options.duration;
        
    ,
    destroy () 
        let instance = getMessageInstance();
        messageInstance = null;
        instance.destroy();
    

到这里组件已经可以通过Message.info()直接调用了,不过我们还可以在 Vue 上进行扩展:
Vue.prototype.$Message = Message;
这样我们可以直接用this.$Message.info()来调用,就不用 import Message 了。

后记

Vue 组件开发中有很多有意思的技巧,用好了会减少很多不必要的逻辑,用不好反而还弄巧成拙。在开发一个较复杂的组件时,一定要先对技术方案进行调研和设计,然后再编码。
iView 还有很多开发技巧和有意思的代码,后面有时间我们再继续探讨吧,最近发布的几个版本都有较大的更新,希望大家可以关注和推广 iView ?:

https://github.com/iview/iview

本文转载于:猿2048?https://www.mk2048.com/blog/blog.php?id=hh1hiaaib0j

几个你不知道却非常实用的javascriptapis(代码片段)

前言在本文中,将介绍一些鲜为人知但却非常有用的API,如:PageVisibilityAPIWebShareAPIBroadcastChannelAPIInternationalizationAPI我们将一起看看它们是什么,我们应该在哪里使用它们,以及如何使用它们。PageVisibilityAPI这... 查看详情

「译」foreach循环中你不知道的3件事(代码片段)

前言本文925字,阅读大约需要7分钟。总括:forEach循环中你不知道的3件事。原文地址:3thingsyoudidn’tknowabouttheforEachloopinJS公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍自弃者扶不起,自强者击不倒。正文你... 查看详情

js中你不知道的一些概念知识(代码片段)

DOM元素e的e.getAttribute(propName)和e.propName有什么区别和联系e.getAttribute(),是标准DOM操作文档元素属性的方法,具有通用性可在任意文档上使用,返回元素在源文件中设置的属性e.propName通常是在HTML文档中访问特定元素的... 查看详情

转载史上最全:tensorflow好玩的技术应用和你不知道的黑科技

【导读】TensorFlow在2015年年底一出现就受到了极大的关注,经过一年多的发展,已经成为了在机器学习、深度学习项目中最受欢迎的框架之一。自发布以来,TensorFlow不断在完善并增加新功能,直到在这次大会上发布了稳定版本的... 查看详情

说说java中你不知道switch关键字的奥秘(代码片段)

Switch语法switch作为Java内置关键字,却在项目中真正使用的比较少。关于switch,还是有那么一些奥秘的。要什么switch,我有if-else确实,项目中使用switch比较少的一个主要原因就在于它的作用能被if-else代替,况且switch对类型的限制... 查看详情

asp.netcore中间件应用实践中你不知道的那些事(代码片段)

一、概述这篇文章主要分享Endpoint终结点路由的中间件的应用场景及实践案例,不讲述其工作原理,如果需要了解工作原理的同学,可以点击查看以下两篇解读文章:Asp.NetCoreEndPoint终结点路由工作原理解读ASP.NETCORE管道模型及中... 查看详情

javascript中你不知道的5个json-使用技巧(代码片段)

开发中,经常使用 JSON.stringify(object) 来序列化对象,但除了第一个参数外,还有其它参数可用...格式化//默认的JSON.stringify(object)出来数据是一行字符串constuser=name:'JackieDYH',age:30,isAdmin:true,friends:['小可爱... 查看详情

转:lightgbm的黑科技--plot函数(代码片段)

本来想研究一下lightGBM的plotting相关的接口,发现网上已经有人做了,而且还挺不错的(lightGBM的黑科技--plot函数),就直接给转过来了#-*-coding:utf-8-*-#@Time:2018/6/11#@Author:Reynoldchenimportlightgbmaslgbimportnumpyasnpimportmatplotlib.pyplotaspltprin 查看详情

php代码审计中你不知道的牛叉技术点

一、前言php代码审计如字面意思,对php源代码进行审查,理解代码的逻辑,发现其中的安全漏洞。如审计代码中是否存在sql注入,则检查代码中sql语句到数据库的传输和调用过程。入门php代码审计实际并无什么门槛要求,只需要... 查看详情

vue.use内部那些你不知道的事儿(代码片段)

1.Vue.use的作用Vue.use的作用是注册全局插件强化Vue的功能 它也可以用来注册全局组件但是有一个条件注册的对象中必须提供 install 方法。如果插件是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数... 查看详情

保证你不知道的vue3技巧(代码片段)

微信搜索【大迁世界】,我会第一时间和你分享前端行业趋势,学习途径等等。本文GitHubhttps://github.com/qq449245884/xiaozhi已收录,有一线大厂面试完整考点、资料以及我的系列文章。VNodehooks在每个组件或HTML标签上,我们... 查看详情

vue32个修饰符,你不一定全知道!(代码片段)

点击上方 前端开发博客,关注公众号回复加群,加入前端群前言vue简洁好用体现在很多个地方,比如其内置了32+修饰符,可以很方便我们阻止冒泡、阻止默认事件、鼠标事件处理、系统键盘事件等等,让... 查看详情

你不知道的console.log替代品(代码片段)

💂个人网站:【海拥】【摸鱼小游戏】🤟风趣幽默的前端学习课程:👉28个案例趣学前端💅想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】💬免费且实用的计算机相关知识题库:👉... 查看详情

你不知道的docker命令的奇淫怪巧(代码片段)

你不知道的docker命令的奇淫怪巧Intro介绍并收录一些可能会用到的一些简单实用却很少有人用的docker命令danglingimagesbuild自己的docker镜像的时候,有时会遇到用一个甚至多个中间层镜像,这会一定程度上减少最终打包出来docker镜像... 查看详情

vue中v-show你不知道的用法createdcomputedmounted的执行顺序(代码片段)

我们都知道,v-show的值是一个布尔类型的。我通过这个值进行显示或者隐藏。但是有些时候,这个值是true还是false,我们需要去进行计算此时我们就可以使用v-show="XXX()"通过XXX()这个方法来返回true或者false<divclass="dem... 查看详情

事件和异常的传播·农场主的黑科技.(代码片段)

inBound事件的传播何为inBound事件以及ChannelInboundHandlerChannelRead事件的传播ChannelRead是典型的inbound事件,以他为例了解inbound事件的传播SimpleInBoundHandler处理器何为inBound事件以及ChannelInboundHandlerChannelHandler的继承关系ChannelInboundHandlerAdap... 查看详情

一个android沉浸式状态栏上的黑科技(代码片段)

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索郭霖即可关注,每个工作日都有文章更新。说起来,在不知不觉中,我竟然凑成了这沉浸式状态栏三部曲。其实最开始的时候,我主要... 查看详情

一个android沉浸式状态栏上的黑科技(代码片段)

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索郭霖即可关注,每个工作日都有文章更新。说起来,在不知不觉中,我竟然凑成了这沉浸式状态栏三部曲。其实最开始的时候,我主要... 查看详情