用 Vue3 打造自己的 UI 组件库

打造自己的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/

侧边栏点击 “显示和隐藏”

image.png

组件结构如上图,app里面有一个topnav和aside,功能实现思路如下:

  1. 首先我们的目标是希望实现当用户点击topnav里面的某个地方时,隐藏或显示aside。
  2. 可以用一个变量asideVisible来控制aside的状态,为true时,表示显示,反之隐藏。
  3. 但是这个变量要放在哪里?当然是要放在app里面,因为topnav和aside不能互相访问。
  4. 那怎么才能让topnav和aside访问到app里面的数据?我们可以让app标记一下这个变量可以被子组件访问。
  5. 如何标记呢?答案就是provide。“provide不管app和topnav中间有多少层组件,都可以访问。”
  6. 子组件如何访问这个变量呢?答案是使用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组件

图片[1]-用 Vue3 打造自己的 UI 组件库-五八三
实现思路:我们使用 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组件的初始状态。

  1. 我们调用时可以给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>
    
    
  2. 然后我们在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>
    
    
  3. 然后我们改用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来获取代码。


项目预览:guozhq.gitee.io/simple-ui
项目源码:github.com/guozhq/simp…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYqwayYU' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片