天天看点

vant源码分析--checkbox组件

这篇文章就是看看一个ui compent是怎么建造出来的,vant的拆分写法写的如何优秀。本人最近也只是在瞎琢磨vant源码。

本文的思想来源于这里可以跳转的一篇文章。有很多东西借鉴了这里的想法,也是这篇文章的一个延伸吧。

组件目录结构

src
└─ button
   ├─ demo             # 示例代码
   ├─ test             # 单元测试
   ├─ index.js         # 组件入口
   ├─ index.less       # 组件样式
   ├─ README.md        # 英文文档
   └─ README.zh-CN.md  # 中文文档
           

vant的轮子

在vant源码中,utils中封装了很多工具和方法。涉及了ts,tsx,js,vue等技术栈。

每个组件在一开是都会调用

createNamespace

函数,它接收

参数(name)

,返回

createComponent, createBEM, createI18N

三个函数。即comonent实例,css帮助类函数,以及多语言工具。

import { createBEM, BEM } from './bem';
import { createComponent } from './component';
import { createI18N, Translate } from './i18n';

type CreateNamespaceReturn = [
  ReturnType<typeof createComponent>,
  BEM,
  Translate
];

export function createNamespace(name: string): CreateNamespaceReturn {
  name = 'van-' + name;
  return [createComponent(name), createBEM(name), createI18N(name)];
}
           

1. createComponent

createComponent方法根据参数name,创建一个vue组件。

export function createComponent(name: string) {
 return function<Props = DefaultProps, Events = {}, Slots = {}>(
 	// sfc参数是调用createComponent参数时传的值 支持两种格式,对象式组件或者函数式组件
   sfc: VantComponentOptions | FunctionComponent
 ): TsxComponent<Props, Events, Slots> {
   if (isFunction(sfc)) {  // 这里判断是否是函数式组件,如是转换为对象式组件
     sfc = transformFunctionComponent(sfc);
   }

   if (!sfc.functional) {
   	//mixins是vue自带的,算是一种组件可复用的一些功能组合。
   	// 当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
     sfc.mixins = sfc.mixins || [];
     //  push了一个兼容低版本scopedSlots的mixin
     sfc.mixins.push(SlotsMixin);
   }

   sfc.name = name;
   // install中调用了vue.compoent注册组件的方法
   sfc.install = install;

   return sfc as TsxComponent<Props, Events, Slots>;
 };
}
           

2. createBEM

createBEM函数接收name参数生成符合BEM规范的class

export function createBEM(name: string) {
 return function(el?: Mods, mods?: Mods): Mods {
   if (el && typeof el !== 'string') {  // 判断el参数是否存在并不是string类型
     mods = el;
     el = '';
   }
   el = join(name, el, ELEMENT); // join方法返回类似checkbox__text的类名

   return mods ? [el, prefix(el, mods)] : el;  
 };
}
           

最后返回类似BEM格式的class类名

/**
 * bem helper
 * b() // 'button'
 * b('text') // 'button__text'
 * b({ disabled }) // 'button button--disabled'
 * b('text', { disabled }) // 'button__text button__text--disabled'
 * b(['disabled', 'primary']) // 'button button--disabled button--primary'
 */

           

3. createI18N

多种语言处理

import { get, isFunction } from '..';
import { camelize } from '../format/string';
import locale from '../../locale';

export function createI18N(name: string) {
 const prefix = camelize(name) + '.';

 return function(path: string, ...args: any[]): string {
   const messages = locale.messages();
   const message = get(messages, prefix + path) || get(messages, path);

   return isFunction(message) ? message(...args) : message;
 };
}

export type Translate = ReturnType<typeof createI18N>;

           

van组件checkbox

van组件checkbox的具体配置

首先引入createNamespace函数和mixin组件可复用功能。并调用createNamespace函数

import { createNamespace } from '../utils';
import { CheckboxMixin } from '../mixins/checkbox';

const [createComponent, bem] = createNamespace('checkbox');  // 这里是来接收createNamespace函数返回的方法
           

接下来是引入checkbox组件的配置

// 调用createComponent方法并返回这个方法的具体结果
export default createComponent({
  // mixins组件可复用的一些功能和方法
  // 这里具体引入了一些props、computed、methods还有rander函数
  mixins: [
    CheckboxMixin({
      bem,
      role: 'checkbox',
      parent: 'vanCheckbox',
    }),
  ],
  // checked计算属性用来完成checkbox的v-model双向绑定的原理
  computed: {
    checked: {
      get() {
        if (this.parent) {
          return this.parent.value.indexOf(this.name) !== -1;
        }
        return this.value;
      },

      set(val) {
        if (this.parent) {
          this.setParentValue(val);
        } else {
          this.$emit('input', val);
        }
      },
    },
  },
  // 监听value属性,只要发生改变,就会向父组件发出通知
  watch: {
    value(val) {
      this.$emit('change', val);
    },
  },
  // methods用来组件提供的方法
  methods: {
    // @exposed-api
    // toggle的具体用法参见官网api
    toggle(checked = !this.checked) {
      // When toggle method is called multiple times at the same time,
      // only the last call is valid.
      // This is a hack for usage inside Cell.
      clearTimeout(this.toggleTask);
      this.toggleTask = setTimeout(() => {
        this.checked = checked;
      });
    },

    setParentValue(val) {
      const { parent } = this;
      const value = parent.value.slice();

      if (val) {
        if (parent.max && value.length >= parent.max) {
          return;
        }

        /* istanbul ignore else */
        if (value.indexOf(this.name) === -1) {
          value.push(this.name);
          parent.$emit('input', value);
        }
      } else {
        const index = value.indexOf(this.name);

        /* istanbul ignore else */
        if (index !== -1) {
          value.splice(index, 1);
          parent.$emit('input', value);
        }
      }
    },
  },
});

           

继续阅读