一次Vant tabs源码解析

4,451 阅读4分钟

前言:在使用ui框架时,总是少不了对ui框架的好奇心,一个优秀的ui框架总能让人用的得心应手,在这 respect 框架的开发者们

1.找到源码的位置

import {Tabs,Tab} from 'vant'
import 'vant/lib/index.less'

这是我们最熟悉的按需引入代码步骤了,那么在引入的过程中,会自动解析到node_modules中的 _vant@2.12.4@vant的es文件夹中,那么重点关注 tabs和tab这两个文件夹

2.梳理tabs结构

在tabs的index.js中,可以看到如下代码:

这里体现了两个重要的函数 createComponentbem

1.createComponent

作用:为 vant 创建特定语法的组件

function install(Vue) { 
 var name = this.name;  Vue.component(name, this);  
 Vue.component(camelize("-" + name), this);
} 

export function createComponent(name) {  
    return function (sfc) {    
        if (isFunction(sfc)) {      
            sfc = transformFunctionComponent(sfc); // 转化成函数式组件    
        }    
        if (!sfc.functional) {      
            sfc.mixins = sfc.mixins || [];     
            sfc.mixins.push(SlotsMixin);    
        }    
        sfc.name = name;    
        sfc.install = install; // 这里的install实际上就是vant实现按需导入的关键    
        return sfc;  
  };}

2.bem

作用:为 vant 生成特定的类名,注意绿色部分,注释已经写得很明白了,无需去关注太多的功能实现,只需要知道传进来生成的是什么就行

3.  render 函数

 在 tabs --> index.js中,我们可以很清晰地看到,官方并没有使用我们日常在单文件组件书写的<template>来书写页面Dom结构,而是采用了render函数进行渲染

对于render函数,官方是这么解释的:

"Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器"

可以发现,render函数的性能要比我们平日书写的<template>性能要好,这也难怪vant会使用其编写Dom结构了。

在了解Vue render函数之前,我强烈推荐你阅读这篇文档:渲染器 ,这能够使你对Vue渲染器的设计,以及实现有着更深刻的了解。(感谢大神的辛勤付出)

接下来直接移步官方文档之 createElement参数 , 对需要传入的参数、格式有所了解后,来看看tabs中render的代码:

【题外话:第一眼看到的时候,我直接靓仔语塞,这也太多太复杂了。。。但是,我从来就不是一个轻易就放弃的人,那就继续研究下去!当然我不建议直接看,我的思路是从实际渲染的DOM入手,一点一点去理解它生成的过程。】

3. tabs DOM结构

<van-tabs v-model="active">      
    <van-tab title="标签 1">内容 1</van-tab>      
    <van-tab title="标签 2">内容 2</van-tab>      
    <van-tab title="标签 3">内容 3</van-tab>      
    <van-tab title="标签 4">内容 4</van-tab> 
</van-tabs>

生成的DOM结构如下:

从 tabs渲染出来的Dom结构我们可以看到,大体分为两大DOM部分,分别为wrapcontent,它们分别对应了导航区(也就是在tab中绑定的title)和内容区,然后最后会由一个大的div进行包裹。

1.wrap的生成过程

在wrap中,我们可以看到最外层的DOM为 van-tabs__nav ,那么我们从render中,看看这个Dom是如何生成的。

如果你已经学习过之前的文档,那么你应该知道h函数是用来生成一个VNode节点的,相当于createElement函数,如图,我们可以知道Nav是通过遍历children(这里指van-tab) ,然后将children中所绑定的属性赋值给Title。那么问题来了,Title是什么类型?

通过引入的title.js进行追踪,不难发现:

oh,原来Title是个组件!首先看看render,可以发现this.genText()就是我们在vant-tab上绑定的title值,经过一番处理,最终会生成 <div><span>标签1</span></div>的DOM结构!

那么最后,这个Nav所代表的,就是我们想要的导航栏,也就是我们在vant-tab上所绑定的title值。如图,我们可以看到,它最终会被一个wrap所包裹住。

2. content的生成过程

其实看懂wrap的生成过程,content也是同理,我们通过引入的content.js,不难看到

content也是个组件,但是它最终渲染出来的DOM只是<div class='van-tabs__content'></div>

不免思考,那所写的内容哪去了呢?别忘了,这个标题叫做 【tabs 的DOM结构】,剩下的内容渲染,就在tab --> index.js 中进行~

4.mixins

在源码中,发现vant大量使用mixins,在以前的单文件组件开发中,我却很少用到,不免又勾起了我的好奇心,于是乎,先阅读了一下官方文档 --> vue混入

在content.js中,可以看到vant使用了TouchMixin

找到TouchMixin,如下:

可以看到vant在这个mixin中,定义了我们常见的滑动函数,这就大幅度地提高了复用性。不禁引发一阵思考:以前我在封装函数的时候,会考虑用大量的js文件进行封装,一旦多了起来,会变难以维护。在单个页面需要对多个函数进行调用时,其实可以将这些通用的函数,放入到mixin中,有了mixin后,其实更方便了~

5.总结

其实tab谁都会做,但要做到像ui框架这种复用性极强,定义的API又很人性化,还需要仔细打磨,考虑多种情况才行。前人栽树,我在乘凉时,也想看看前人是如何 “栽树” 的~