从0开始搭建一个前端UI组件库

1,739 阅读7分钟

背景

这是一个基于Vue框架,参考ElementUI项目结构的组件库demo。

半年前萌生了做个自己UI库的想法。最近准备重构下个人作品(聊天室系统),借这个契机落地这个想法。element-UI是一个很优秀的vue的UI库,看了部分源码深以为然,决定整个项目结构参照element-UI组件库来做。

好,现在明确下这个UI库的具体功能目标:
  1. 提供良好可用的组件
  2. 语言切换功能
  3. 主题切换功能

实践

一、创建项目

首先创建个新的vue项目,那就取名"aowu-dev"(取名之前去npm官网查询下是否已被注册)。

二、写组件

组件是整个项目最核心的部分,UI库的作用就是给用户提供良好的组件(增加复用,提升内聚,降低耦合),满足业务上的需求。接下来写个简单的组件做示例:

  1. 在根目录下新建名为packages的文件夹(所有UI组件都放在这儿)
  2. 在packages下新建button文件夹(一个组件一个文件夹)
  3. 可以开始写button组件了,如下图所示:

    说明:
    ① button/src文件夹下是button组件
    ② button/index.js作用是给button添加一个注册函数,当使用Vue.use按需引入时执行(例:Vue.use(Button))
  4. 在根目录的src文件夹下新建一个index文件,主要作用是导出/注册UI组件。如下图所示:

    说明:我们在项目中引入element-ui(import ElementUI from 'element-ui')的时候,并非引入'elment-ui/src/index.js'。这时需要去package.json文件中设置'入口属性'("main": "./src/index.js")
  5. 测试:
    在其他项目中使用aowu-ui试试~~

三、添加types文件

发现在新项目中引入UI库是时候,其他库有类型声明文件(作用是提供在TypeScript环境下的使用支持),如下图所示:

现在开始为项目添加类型文件。

  1. 在根目录下新建types文件夹,并添加相应类型声明文件:
    说明:因为是vue项目,所以UI组件类型都是继承于Vue(component.d.ts中可以看到)。除了导出UI组件,还有一些函数,比如install函数等
  2. 在UI库的package.json中设置TypeScript 的入口文件("typings": "types/index.d.ts"字段)。
  3. 测试类型提示:

四、语言切换功能

语言切换功能也就是国际化,来看看百度百科的解释:

i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)。[1]
在vue生态方面,这么常用的功能肯定会有相应的插件,比如vue-i18n。但在ElementUI中并没有采用这些第三插件,是自己写了个函数来实现(猜测是为了减少依赖,提升项目稳定性)。好,接下来看看是怎么实现的:
  • 原理分析:
    问:先思考一下,假如你要做这个功能,你会怎么做?
    答:明确目标:要的最终效果是项目在不同语言环境下,呈现不同的语言字段(栗子:上方按钮组件的例子,在中文语言环境下内容应该是“嗷呜”,英文下是“aowu”)。不难看出这些字段都有特定含义,那能不能给它定义一个的key值,这个key对应着在中文下是什么字段,在英文下是什么字段。可一个key值只能对应一个字段,那能不能新建多个文件,比如zh_CN.js文件,en.js文件,在切换语言时读取相应的文件并返回正确字段。
  • 实践:
    1. 添加相应文件:

      说明:
      ① lang文件夹下存放的是key值对应的各种语言字段文件(zh-CN.js,en.js)
      ② index.js是关键文件,里面定义了翻译函数t;兼容i18n插件的处理函数i18nHandler;初始化页面时设置语言的use函数(需要刷新页面才能生效)
      ③ format.js是关键文件,用来处理多语言变量插值问题
    2. 来看下关键代码-翻译函数(t):
      
      //引入默认语言文件(可更改)
      import defaultLang from './lang/en';
      export const t = function (path, options) {
      //兼容i18n插件
      let value = i18nHandler.apply(this, arguments);
      if (value !== null && value !== undefined) return value;
      //传入来的是个路径,如aowu.test.btnContent,分割成数组,去获取对应的字段值
      const array = path.split('.');
      let current = lang;
      for (let i = 0, j = array.length; i < j; i++) {
      const property = array[i];
      value = current[property];
      //获取btnContent字段值并交给format处理并返回
      if (i === j - 1) return format(value, options);
      if (!value) return '';
      current = value;
      }
      return '';
      };
      
      
    3. 来看看在.vue文件中是如何使用的:
      说明:
      ① src/mixins文件夹存放所有要混入(mixin)到组件中的对象。
      ② src/mixins/locale.js文件只是引入t函数,并绑定所在组件的作用域。
      ③ button.vue中混入了locale.js中的t方法,之后在组件中就可以使用this.t("path",options)
    4. 测试:
      一直没明白,t函数中取到目标值直接return就行了,为何要交给format做处理?做个测试,先把format函数去掉试试:
    5. 发现不使用format函数也能使用,那format是干嘛的?多了个options参数又是干嘛的? ElementUI中备注format.js灵感来于string-template( Inspired:* github.com/Matt-Esch/s… )。一番折腾之后发现这是用来做"变量插值",options对象是变量值。详情见下图:
      说明:到此切换语言功能基本完成。

五、主题切换

  • 原理分析:
    问:思考一下,如果是你要主题切换功能,会怎么做?
    答:主题切换功能的本质是修改css属性,那难点在于哪?在于如何优雅,简洁,批量地去修改目标css属性,避免重复机械的操作。谈到批量,很多人会想到变量,对,就是用css的变量来做。
    目前CSS变量有两种:1.CSS自身提供;2.CSS预处理器(Sass,Less,Stylus)提供;
    在此讨论使用css预处理器实现。
  • 实践
    在实践之前,我们先来看下在Element中修改主题会发生什么。

    可以看到,有以下四个步骤:
    ① 前端修改color-primary的颜色
    ② 前端向后端发送了个更新css的请求,参数中有$--color-primary: "#FF4086"
    ③ 服务器实时编译并返回一个css文件(当中所有的--color-primary都已替换为"#FF4086")
    ④ 前端加载使用这个css文件

    好,搞懂了原理,现在我们也来实现一份(ElementUI使用Sass,我使用Stylus):
    1. 在项目中创建相应的文件,如下图图所示:
      说明:
      ① 新建了个theme-chalk文件夹(ElementUI中theme-chalk是个独立项目,便于其他项目单独安装)
      ② theme-chalk/src下存放各个样式文件,packages中的一个组件对应这里的一个样式文件
      ③ theme-chalk/src/common/var.js存放各种样式变量(栗子:$--button-color = #fff)
    2. 测试样式是否符合预期:
    3. 电脑上安装stylus插件(npm install -g stylus ),使用命令编译一份黑夜模式的样式:
    4. 将编译生成的darkTheme.css放在云服务器上,起个tomcat进行服务。在网页上试试能否访问到:
      说明:在ElementUI中是将放回的css字段写到head的style标签中进行覆盖(栗子:<style id="docs-style">...</style>,新增的样式就接在后边<style id="docs-style">...</style>)。我这边简单起见,就用link标签引入,但是外联的样式比内联样式等级低,所以在主要改变的地方加了个!important。
    5. 测试:
      上边在网页中通过地址能够访问,那可以确定link标签引入也是没问题。来看看实际效果:
      说明:到此主题切换功能基本完成


其他

发布npm包

添加授权文件

git地址

aowu-dev

规范

css规范
git提交规范
栗子:git rebase + merge --no-ff提交记录:

结语

到此UI库的结构已基本搭建完成!