组件的使用
思考一下我们怎么使用组件
回顾一下我们使用的那些组件库
npm i element-ui -S
npm i element-plus -S
npm i naive-ui -S
都是这样安装,再看看它们是如何使用的
可以看到它们都分别提供了
- 完整引入
- 按需引入
- 手动引入
三种方式。
我们以 Element Plus
去分析一下,借以抛砖引玉,反向推导我们应该如何实现组件库
完整引入
从 element-plus
的官方文档中,完整引入的方式是这样的
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
让我们看看这里的实现:
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
的实现
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
里面配置
{
"compilerOptions": {
// ...
"types": ["element-plus/global"]
}
}
这是为了主动通知 vue
vscode
插件告诉它全局组件的智能提示。
手动引入
Element Plus 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。
但你需要安装 unplugin-element-plus
来导入样式。
<template>
<el-button>I am ElButton</el-button>
</template>
<script>
import { ElButton } from 'element-plus'
export default {
components: { ElButton },
}
</script>
我们来分析一下这种场景,已经知道 css
和 js
是分离的,但是上面代码,明明是引入一个组件,那为什么样式也被引入进来呢
这一切都在于 unplugin-element-plus
这个编译插件做个转化:
import { ElButton } from 'element-plus'
// ↓ ↓ ↓ ↓ ↓ ↓
import { ElButton } from 'element-plus'
import 'element-plus/es/components/button/style/css'
element-plus/es/components/button/style/css
和element-plus/es/components/button/style/index
的不同点在于引入的一个是css
一个是scss
本质上这个插件就是先用 es-module-lexer
分析一下导入, 然后在最后一个导入下面,插入样式导入语句。
自动导入
刚刚介绍了手动引入的编译插件,自动导入方案实际上是另外一种编译方案
我们来看一下它的表象:
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 的角度考虑,肯定是手动和 自动导入好,因为他们都是按需导入的。不会引入过多的 css
和 js
从使用的便携性来说,肯定是自动导入最好,但是你需要有一些心里预期,一旦上了自动导入这条船,就很难下来了,除非我们把 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
进行替换。
总结
所以参考这些优秀组件库的思路,你也应该吸收并去改造你自己的组件库。
多阅读和参考优秀的开源代码,能够大大加速你的成长。