vant-weapp源码解析(1)

作为一个工作多年的老油条,最近我惊恐的发现一件事情,就是我自己真的好菜。以前自己还能手写个冒泡排序递归啥的,现在的自己只会百度和问ai了。

没想到工作越久,能力越差,想一个功能,经常要想很久,好像还不如刚毕业那会。工作这么多年,真的是荒废的不行。

最近痛定思痛,决定看看源码,就先从我最熟悉的vant-webapp开始看。

我们先从一个最简单的组件入手,看下vant-weapp的源码是什么样。

1.目录结构

vant-weapp的github地址是这个,youzan/vant-weapp: 轻量、可靠的小程序 UI 组件库

  • build文件夹,存放构建相关的命令
  • dist文件夹,构建好的,可以直接复制到小程序中使用的各个组件(es6版本)。
  • docs/markdown,日志、使用说明等文档介绍
  • example ,小程序案例
  • lib,构建好的,可以直接复制到小程序中使用的各个组件(es5版本)
  • packages,组件的源代码

我们需要关注的就是packages文件夹,这里我们以最简单组件van-divider为例,说明下vant是怎么写组件的。

2.van-divider

目录结构

cmd 复制代码
- demo(文件夹)
- test(文件夹)
- index.json
- index.less
- index.ts
- index.wxml
- index.wxs
- README.md

目录结构说明

  • demo,是一个组件,这个组件又引入了van-divider组件,里面包含了van-divider各种使用方法的案例。
  • test,应该是跟自动化测试有关的,现在先不管它。
  • index.json index.less index.ts index.wxml 对应的就是小程序自定义组件的json、css、js和wxml。只不过vant为了方便开发使用了ts和less。
  • README.mdvan-divider的使用说明

3.van-divider 用法

Divider 分割线 - Vant Weapp

3.1 基础用法

html 复制代码
<van-divider />

3.2 虚线

html 复制代码
<van-divider dashed />

3.3 添加文本

html 复制代码
<van-divider contentPosition="center">文本</van-divider>
<van-divider contentPosition="left">文本</van-divider>
<van-divider contentPosition="right">文本</van-divider>

4.源代码解释

要想看懂源代码,首先必须熟悉如何编写小程序的自定义组件。这是微信小程序自定义组件教程自定义组件 / 介绍

一个自定义组件由json、wxml、wxss、js组成

首先我们看下index.json文件

4.1 index.json

json 复制代码
{
  "component": true,
  "usingComponents": {}
}

在json中,component:true表明这是一个自定义组件,使用usingComponents声明需要引入的其他组件,这里为空{},表示没有子组件。

我们再看下index.ts

4.2 index.ts代码

javascript 复制代码
import { VantComponent } from '../common/component';

VantComponent({
  props: {
    dashed: Boolean,
    hairline: Boolean,
    contentPosition: String,
    fontSize: String,
    borderColor: String,
    textColor: String,
    customStyle: String,
  },
});

按照小程序文档说明,创建组件,需要使用Component()来注册组件的,并提供组件的属性和方法。代码大概如下所示

css 复制代码
Component({
  properties: {
    // 这里定义了innerText属性,属性值可以在组件使用时指定
    innerText: {
      type: String,
      value: 'default value',
    }
  },
  data: {
    // 这里是一些组件内部数据
    someData: {}
  },
  methods: {
    // 这里是一个自定义方法
    customMethod: function(){}
  }
})

但是在index.ts中并没有使用Component方法,而是使用了一个叫VantComponent方法。想一下我们就应该明白,VantComponent方法应该是对Component方法的一个封装。

查看VantComponent源代码。

因为VantComponent的参数跟小程序Component参数是不一样,所以代码首先是将VantComponent的参数vantOptions转换成了小程序参数options。然后给options添加了一些默认的属性,比如外部样式类、behaviors等。 最后再调用Component(options)创建小程序组件。

js 复制代码
// basic 是一个behavior,封装了小程序常用的几个方法。比如 setData、$emit 等,添加此 behavior 的组件,可以直接使用这些方法。
import { basic } from '../mixins/basic';
// VantComponent 参数的类型定义
import { VantComponentOptions } from 'definitions/index';


// mapKeys 用于将VantComponent参数中的属性转换成Component参数中的属性,比如将props转换成properties,将mixins转换成behaviors。VantComponent参数属性名没有直接使用小程序组件的属性名,而是使用vue的参数属性名,所以中间需要有个转换的过程。这样做,可能是为了兼顾下vue的开发者.
/**
 * 将 {props:{},mixins:[]} 转换成 {properties:{},behaviors:[]}
 */
function mapKeys(
  source: Record<string, any>,
  target: Record<string, any>,
  map: Record<string, any>
) {
  Object.keys(map).forEach((key) => {
    if (source[key]) {
      target[map[key]] = source[key];
    }
  });
}

function VantComponent<
  Data extends WechatMiniprogram.Component.DataOption,
  Props extends WechatMiniprogram.Component.PropertyOption,
  Methods extends WechatMiniprogram.Component.MethodOption
>(vantOptions: VantComponentOptions<Data, Props, Methods>): void {
  const options: WechatMiniprogram.Component.Options<Data, Props, Methods> = {};

  /**
  * 将vantOptions对象
  * {
  *   props:{},
  *   mixins:[],
  * }
  * 转换成options对象 
  * {
  *   properties:{},
  *   behaviors:[]
  * }
  */
  mapKeys(vantOptions, options, {
    data: 'data',
    props: 'properties',
    watch: 'observers',
    mixins: 'behaviors',
    methods: 'methods',
    beforeCreate: 'created',
    created: 'attached',
    mounted: 'ready',
    destroyed: 'detached',
    classes: 'externalClasses',
  });

  // 给组件添加外部样式externalClasses,默认值为['custom-class'],这样所有的组件都可以使用custom-class这个外部样式类,来继承外部样式
  options.externalClasses = options.externalClasses || [];
  options.externalClasses.push('custom-class');

  // 给组件添加behaviors,默认值是basic,basic 是一个behavior,封装了小程序常用的几个方法。比如 set、$emit 等,添加此 behavior 后,所有的组件都可以调用set、$emit等方法。
  options.behaviors = options.behaviors || [];
  options.behaviors.push(basic);

  // 如果vantOptions中存在relation属性,则将relation.relations和relation.mixin添加到options中
  const { relation } = vantOptions;
  if (relation) {
    options.relations = relation.relations;
    options.behaviors.push(relation.mixin);
  }

  // 如果vantOptions.field 属性存在,则表明这个组件是个表单组件,则将wx://form-field这个behavior添加到options.behaviors中
  if (vantOptions.field) {
    options.behaviors.push('wx://form-field');
  }

  // 所有组件都可以添加多个slot,所有组件都可以使用外部样式
  options.options = {
    multipleSlots: true,
    addGlobalClass: true,
  };
  // 创建小程序组件
  Component(options);
}

export { VantComponent };

上面的index.ts代码

js 复制代码
VantComponent({
  props: {
    dashed: Boolean,
    hairline: Boolean,
    contentPosition: String,
    fontSize: String,
    borderColor: String,
    textColor: String,
    customStyle: String,
  },
});

经过处理后就变成了我们熟悉的样子。

js 复制代码
Component({
  properties: {
    dashed: Boolean,
    hairline: Boolean,
    contentPosition: String,
    fontSize: String,
    borderColor: String,
    textColor: String,
    customStyle: String,
  },
});

可以看到小程序可以接收dashedhairlinecontentPosition等外部属性,通过这些属性来实现虚线、细线、添加文本等功能

4.3 index.wxml 代码

html 复制代码
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />

<view
  class="custom-class {{ utils.bem('divider', [{ dashed, hairline }, contentPosition]) }}"
  style="{{ computed.rootStyle({ borderColor, textColor, fontSize, customStyle }) }}"
>
  <slot />
</view>

代码非常简单,开头引入了两个wxs,如果不知道wxs是什么的,可以点击这里查看 wxs教程。简单来说wxs就是可以内嵌在wxml中的,有限制的javascript。

在这里我们使用了utils和index两个wxs。

utils.bem的作用是给class名添加前缀,并且把vant的class名转成符合bem规范的格式,比如如果dashed为true

下面这个代码得到的class名就是 vant-divider--dashed

js 复制代码
utils.bem('divider', [{ dashed }])

computed.rootStyle的作用是将js样式对象,转成样式字符串。比如我们设置borderColor为"red"。

ini 复制代码
<van-divider borderColor="red" />

这样组件里borderColor属性就是red。传到wxml中,经过computed.rootStyle转换后,结果如下

js 复制代码
computed.rootStyle({ borderColor}}

等到的样式就是border-color:red,这样我们就可以把线条的颜色设置成红色了。

html 复制代码
<view
  class="custom-class {{ utils.bem('divider', [{ dashed, hairline }, contentPosition]) }}"
  style="{{ computed.rootStyle({ borderColor, textColor, fontSize, customStyle }) }}"
>
  <slot />
</view>

这段代码中,view 的custom-class是传过来的外部样式类属性(externalClasses),用来让组件使用页面或者全局定义的一些样式.

教程:自定义组件 / 组件模板和样式

slot是占位符,可以在组件内插入一些内容,比如文字、图片等。比如下面这个,"文字"就会插入到slot所在的位置。

html 复制代码
<van-divider contentPosition="center">文本</van-divider>

教程:自定义组件 / 组件模板和样式

4.4 index.less 代码

divider分割线的各种效果是通过css代码实现的。

其中线条是通过before伪类和after伪类来实现的。如果没有内容,只有一个线条的话,线条是通过before伪类来实现的。如果有内容的话,内容是:before伪类 内容 after伪类。 其中before伪类实现左线条,after伪类实现右线条。

代码很简单,没有啥可讲的了。

css 复制代码
@import '../common/style/var.less';

.van-divider {
  display: flex;
  align-items: center;
  border-style: solid;
  border-width: 0;
  margin: var(--divider-margin, @divider-margin);
  color: var(--divider-text-color, @divider-text-color);
  font-size: var(--divider-font-size, @divider-font-size);
  line-height: var(--divider-line-height, @divider-line-height);
  border-color: var(--divider-border-color, @divider-border-color);

  &::before,
  &::after {
    display: block;
    flex: 1;
    box-sizing: border-box;
    height: 1px;
    border-color: inherit;
    border-style: inherit;
    border-width: 1px 0 0;
  }

  &::before {
    content: '';
  }

  &--hairline {
    &::before,
    &::after {
      transform: scaleY(0.5);
    }
  }

  &--dashed {
    border-style: dashed;
  }

  &--center,
  &--left,
  &--right {
    &::before {
      margin-right: var(--divider-content-padding, @divider-content-padding);
    }

    &::after {
      content: '';
      margin-left: var(--divider-content-padding, @divider-content-padding);
    }
  }

  &--left {
    &::before {
      max-width: var(--divider-content-left-width, @divider-content-left-width);
    }
  }

  &--right {
    &::after {
      max-width: var(
        --divider-content-right-width,
        @divider-content-right-width
      );
    }
  }
}

总结

通过vant-divider组件的源码解读,我们差不多算是了解了vant组件的实现方式。其他组件也类似。

小程序组件整体来说还是比较简单,比vue和react组建都要简单。但是也有很多地方值得我们学习,比如组件架构和代码风格,如果让自己写的话,自己很难写出这么干净漂亮的代码。

加油,继续努力吧

相关推荐
isixe1 个月前
Vant4 Vue3 实现横屏签名框
vue.js·vant
用户7579419949702 个月前
(Vant UI)Vue项目中Tab栏使用细节总结
前端·vant
芭拉拉小魔仙2 个月前
【Vue3/Typescript】从零开始搭建H5移动端项目
前端·vue.js·typescript·vant
学吧别真挂了4 个月前
Vue 3三大UI组件库全解析:从安装到实战
element·ant design·vant
夏天想5 个月前
vant4+vue3上传一个pdf文件并实现pdf的预览。使用插件pdf.js
开发语言·javascript·pdf·vant
不老刘5 个月前
微信小程序使用 Vant Weapp 组件库教程
微信小程序·小程序·vant
似璟如你6 个月前
vscode软件中引入vant组件
vscode·vue·vant
杨晓风-linda7 个月前
Swipe横滑与SwipeItem自定义横滑相互影响
前端·javascript·vue.js·vant
蓝黑20207 个月前
从Vant图标的CSS文件提取图标文件
前端·css·python·vant