Skip to content

组件的使用

思考一下我们怎么使用组件

回顾一下我们使用的那些组件库

bash
npm i element-ui -S
npm i element-plus -S
npm i naive-ui -S

都是这样安装,再看看它们是如何使用的

可以看到它们都分别提供了

  1. 完整引入
  2. 按需引入
  3. 手动引入

三种方式。

我们以 Element Plus 去分析一下,借以抛砖引玉,反向推导我们应该如何实现组件库

完整引入

element-plus 的官方文档中,完整引入的方式是这样的

js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

那么反向推导可得,element-plus 的默认导出是一个 vue plugin 让我们看看这里的实现:

js
import { makeInstaller } from './make-installer.mjs';
import Components from './component.mjs';
import Plugins from './plugin.mjs';
// makeInstaller 为创建一个 vue 插件
// Components 是所有的组件,Plugins 是内置的所有的 Vue 插件, 帮助注册一些指令和全局的一些 $xxx
var installer = makeInstaller([...Components, ...Plugins]);

export { installer as default };

以下是 makeInstaller 的实现

js
import { version } from './version.mjs';
import { INSTALLED_KEY } from './constants/key.mjs';
import { provideGlobalConfig } from './components/config-provider/src/hooks/use-global-config.mjs';

const makeInstaller = (components = []) => {
  const install = (app, options) => {
    // 防止重复注册
    if (app[INSTALLED_KEY])
      return;
    app[INSTALLED_KEY] = true;
    // app 层级全局注册
    components.forEach((c) => app.use(c));
    if (options)
      provideGlobalConfig(options, app, true);
  };
  // 返回的是一个 vue plugin
  return {
    version,
    install
  };
};

export { makeInstaller };

所以因为这种全局注册的方式,所以为了智能提示,就需要在 tsconfig.json 里面配置

json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

这是为了主动通知 vue vscode 插件告诉它全局组件的智能提示。

手动引入

Element Plus 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。

但你需要安装 unplugin-element-plus 来导入样式。

vue
<template>
  <el-button>I am ElButton</el-button>
</template>

<script>
import { ElButton } from 'element-plus'

export default {
  components: { ElButton },
}
</script>

我们来分析一下这种场景,已经知道 cssjs 是分离的,但是上面代码,明明是引入一个组件,那为什么样式也被引入进来呢

这一切都在于 unplugin-element-plus 这个编译插件做个转化:

js
import { ElButton } from 'element-plus'

//    ↓ ↓ ↓ ↓ ↓ ↓

import { ElButton } from 'element-plus'
import 'element-plus/es/components/button/style/css'

element-plus/es/components/button/style/csselement-plus/es/components/button/style/index 的不同点在于引入的一个是 css 一个是 scss

本质上这个插件就是先用 es-module-lexer 分析一下导入, 然后在最后一个导入下面,插入样式导入语句。

自动导入

刚刚介绍了手动引入的编译插件,自动导入方案实际上是另外一种编译方案

我们来看一下它的表象:

ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

为什么它能做到自动导入呢?

unplugin-auto-import / unplugin-vue-components

实际上,他们都是在编译时,去收集没有引用的API,组件和指令,然后在修改字符串的时候,在你的源代码中注入导入语句,从而自动引入

其中他们都支持自定义的 resolver 去兼容我们自己的组件库,从而支持自动引入。

几种方式比较

从编译的角度来说,肯定是自动导入最好,但是手动导入也是一个不错的方案

首先从 js 和 css 的角度考虑,肯定是手动和 自动导入好,因为他们都是按需导入的。不会引入过多的 cssjs

从使用的便携性来说,肯定是自动导入最好,但是你需要有一些心里预期,一旦上了自动导入这条船,就很难下来了,除非我们把 unplugin-vue-components 这种 fork 一下,改造成一个脚本,运行一下批量去更改所有的源代码,预先注入所有的组件和指令的导入语句,这样才能下掉。

另外自动导入的方式,面对有准确命名的框架组件还好,假如是自己的组件就有命名上的一些问题。

下一代组件库 shadcn 思路

这是目前 react 中最流行的组件库,可以去了解和使用一下它的组件库设计和使用思路

shadcn-ui ,内部的 headless 组件用的 radix-ui

也有非官方的 shadcn-vue 版本,内部的 headless 组件用的 reka-ui

它们组件库的使用,都是以一个基底的不带任何样式,只有交互行为的组件库,然后样式系统完全交给 tailwindcss 去控制

然后直接把简化过的核心代码,copy 到项目中,再通过设置 components.json 这个配置文件,来达成 component cli 的配置和同步的效果。

这带来的好处是,极高的自定义,不再会被原先老的UI库里面的交互,样式,还有固定元素限制。

假如你使用过 reka-ui,你会发现里面几乎都是抽象的组件,有 dom 也可以使用 as 这个 prop 进行替换。

总结

所以参考这些优秀组件库的思路,你也应该吸收并去改造你自己的组件库。

多阅读和参考优秀的开源代码,能够大大加速你的成长。

Released under the CC BY-NC-SA 4.0 License.