打造自己的UI组件库是前端开发中的一个重要挑战,不仅能够提高代码复用性和开发效率,还能加深对前端技术的理解。在本文中,我将分享我在Vue3学习之旅中打造UI组件库的经验和心得,希望能够为你提供一些有用的指导和建议。
技术栈
Vue3:作为一个流行的前端框架,提供了许多新的特性和改进,可以帮助我们更高效地编写代码。
Vite:作为一个现代化的构建工具,可以提高开发效率并支持快速的热重载
TypeScript:作为一种静态类型检查工具,可以帮助我们在开发过程中避免许多常见的错误。
VueRouter:可以帮助我们更好地管理应用程序的路由。
项目初始化
使用Vite创建项目yarn create vite-app simple-ui
vue-router初始化代码如下
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import HelloWorld from './components/HelloWorld.vue'
import { createWebHashHistory, createRouter } from 'vue-router'
const history = createWebHashHistory();
const router = createRouter({
history,
routes: [
{ path: '/', component: HelloWorld }
]
});
const app = createApp(App)
app.use(router)
app.mount('#app')
createMemoryHistory 内存型路由:这个路由模式不会将路由信息存储在浏览器的地址栏中,而是存储在内存中。它适用于一些不需要被搜索引擎收录,或者需要保护隐私的项目,如后台管理系统等。
createWebHashHistory Hash型路由:这个路由模式使用URL的hash部分(#)来模拟一个完整的URL地址。它适用于前后端分离的项目,可以避免路由冲突和支持前端路由刷新,同时兼容性也较好。
createWebHistory History型路由:这个路由模式使用真实的URL地址来处理路由信息,适用于需要进行SEO优化的项目。它可以产生真实的URL地址,更易于被搜索引擎收录,但在前后端分离的项目中容易产生路由冲突。
问题1
当我们使用VSCode开发导入组件时,会提示找不到模块xxx.vue,这是因为TypeScript只能理解.ts文件, 无法理解.vue文件,解决方案如下:
// src/shims-vue.d.ts
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const ComponentOptions: ComponentOptions
export default ComponentOptions
}
跨平台中文解决方案
为了使中文在所有平台都好看,我们可以使用这个跨平台解决方案。
官网链接:zenozeng.github.io/fonts.css/
侧边栏点击 “显示和隐藏”
组件结构如上图,app里面有一个topnav和aside,功能实现思路如下:
- 首先我们的目标是希望实现当用户点击topnav里面的某个地方时,隐藏或显示aside。
- 可以用一个变量asideVisible来控制aside的状态,为true时,表示显示,反之隐藏。
- 但是这个变量要放在哪里?当然是要放在app里面,因为topnav和aside不能互相访问。
- 那怎么才能让topnav和aside访问到app里面的数据?我们可以让app标记一下这个变量可以被子组件访问。
- 如何标记呢?答案就是provide。“provide不管app和topnav中间有多少层组件,都可以访问。”
- 子组件如何访问这个变量呢?答案是使用inject()。
// app 声明并标记变量
<script lang="ts">
import { ref, provide } from 'vue'
export default {
name: "App",
setup(){
// 获取屏幕宽度,适应手机屏幕
const width = document.documentElement.clientWidth;
// 当屏幕宽度大于500时,设置为不能切换的,且初始值为true
const asideVisible =ref(width <= 500 ? false:true)
provide('asideVisible',asideVisible)
}
}
</script>
// topnav代码
<script lang="ts">
import { inject, Ref } from 'vue'
export default {
setup() {
const asideVisible = inject<Ref<boolean>>('asideVisible');
const toggleAside = () => {
asideVisible!.value = !asideVisible!.value;
}
return {toggleAside}
}
}
</script>
<div class="logo" @click="toggleAside">LOGO</div>
// aside代码
<aside v-if="asideVisible">
<script lang="ts">
import { inject, Ref } from 'vue'
export default {
setup() {
const asideVisible = inject<Ref<boolean>>('asideVisible');
return { asideVisible };
},
}
</script>
Switch组件
实现思路:我们使用 button
元素来实现 switch
组件,其中 span
元素表示中间的小圆。通过控制一个变量的值,来改变CSS即 span
元素的位置,从而实现 switch
组件的效果。
<button @click="toggle" :class="{checked}"><span></span></button>
<script lang="ts">
import { ref } from 'vue'
export default {
setup() {
const checked = ref(false)
const toggle = () =>{
checked.value =!checked.value
}
return {checked, toggle}
}
}
</script>
代码优化:现在虽然实现了Switch组件的基本效果,但是我们在调用的时候无法控制Switch组件的初始状态。
-
我们调用时可以给Switch组件添加一个value来控制初始状态,然后通过监听事件获取内部的状态值。
<template> <Switch :value="y" @input="y = $event"/> </template> <script lang="ts"> import Switch from "../lib/Switch.vue" import {ref} from "vue" export default{ components: { Switch }, setup(){ const y = ref(true) return { y} } } </script>
-
然后我们在Switch组件里面通过props获取value的值,并使用emit()将最新的状态传递给外面。
<button @click="toggle" :class="{checked:value}"><span></span></button> <script lang="ts"> export default { props:{ value:Boolean }, setup(props, context) { const toggle = () =>{ context.emit('input', !props.value) } return {toggle} } } </script>
-
然后我们改用Vue3的v-module写法
// 改前 <Switch :value="y" @input="y = $event"/> // 改后 <Switch v-model:value="y"/> // 改前 context.emit('input', !props.value) // 改后 context.emit('update:value', !props.value)
Dialog组件
具名插槽
如果有两个插槽,且需要不同的内容,我们可以使用具名插槽,使用方法如下:
<slot name="title"/>
<slot name="content"/>
// 使用
<template v-slot:title>
<div>标题内容</div>
</template>
<template v-slot:content>
<div>文本内容</div>
</template>
Teleport
Teleport
是 Vue 3 中的一个组件,它可以将组件的内容渲染到指定的目标元素中,而不是在组件本身的位置渲染。
所以为了防止Dialog被遮挡,我们需要将Dialog移动到body下面,直接使用Teleport即可。<Teleport to="body">
展示源代码
我们使用vueI18nPlugin插件,通过这个插件将代码转换成新的代码,然后检查文件中是否包含i18n,如果没有,则返回原文件,反之读取组件文件内容,并使用 baseParse 解析 AST 树。
const vueI18nPlugin = {
name: 'vue-i18n',
transform(code, id) {
const isI18nComponent = /vue&type=i18n/.test(id)
if (!isI18nComponent) { return }
const file = fs.readFileSync(id.split('?')[0]).toString();
const parsed = baseParse(file).children.find(n => n.tag === 'i18n');
const title = parsed.children[0].content;
const main = file.split(parsed.loc.source).join('').trim();
return `export default function (Component) {
Component.__sourceCode = ${JSON.stringify(main)
}
Component.__sourceCodeTitle = ${JSON.stringify(title)}
}`.trim();
},
}
所以我们在需要展示的地方加上i18n
<i18n>
常规用法
</i18n>
然后我们通过component!.__sourceCode
来获取代码。