学习制作一个基于Vue3 + TS 的UI库

简介: 学习制作一个基于Vue3 + TS 的UI库

学习制作一个基于Vue3 + TS 的UI库


吃一堑长一智

先说明一下我写这篇博客的原因,因为我在学习过程中,自己胡乱鼓捣导致yarn build命令报错,自己完全菜鸡把我写的代码目录重新删掉,从GitHub上下载回来,尝试正常以后我本想重新git上去,但是报错,我在谷歌搜索解决办法,使用git push -u origin main导致我的提交记录全都删掉,我跟着学的八十几commit全都不翼而飞😱就很崩溃,就写下这篇文章,记录一下思路,把脉络梳理,方便日后重新回顾,水平慢慢提高

[项目代码](zxjdzxb/ShellyShip-UI: 预览效果 (github.com))

[预览效果]ShellyShip UI库 (zxjdzxb.github.io))

网络异常,图片无法展示
|

1、使用Vite项目搭建

这次我们使用Vite搭建:

  • 先全局安装create-vite-app:yarn global add create-vite-app,
  • 然后cva 项目名, 或者yarn create vite-app
  • cd
  • yarn
  • yarn dev

2、使用 Vue Router4 用于页面切换

  • 先安装,yarn add vue-router
  • 初始化
  • 新建 history 对象
  • 新建 router 对象
  • 引入 TypeScript
把main.js改为main.ts
//main.ts
import {createWebHashHistory, createRouter} from 'vue-router'
const history = createWebHashHistory()
const router = createRouter({
  history,
  routes: [
    { path: '/', component: Lifa }
  ]
})
  • app.use(router)
  • 添加
  • 添加

.vue文件会报错,解决办法是:

创建src/shims.d.ts,告诉TS如何理解.vue文件:

declare module '*.vue' {
  import { ComponentOptions } from 'vue'
  const componentOptions: ComponentOptions
  export default componentOptions
}

3、开始创建页面

这里大概是分两个页面,一个Home和一个Doc,路径为 #/ 时渲染Home,#/doc渲染Doc

Home.vue

  • Topnav:左边是logo,右边是menu
  • Banner:文字介绍 + 开始按钮

Doc.vue

  • Topnav:同上
  • Content:左边是 aside,右边是 main

4、项目制作

将一些页面共有代码放进去,让自己更深入的理解,并且更有逻辑性,也使自己更容易修改 添加样式,vue的实现都很方便,大家赶兴趣的可以看一下我的源码

写代码的步骤和思路

  • 写 HTML
  • 写 CSS
  • 写 JS
  • 测试
  • 改写代码
  • 再测试
  • 再改写
  • 再测试
  • 再改写
  • 再测试
  • 再改写

如何让组件的某一部分触发事件?

Button 组件中,外部有一个 div 红框,点击这个 div 会触发事件,但我们只需要点击 button 触发事件。如何才能实现呢?

网络异常,图片无法展示
|

第一步:让组件里的 div 不继承属性

设置 inheritAttrs: false 即可,这时点击div不会触发事件。inherit意为继承,attrs为attributes的缩写,意为属性。但是,这时的button点击也不会触发事件,因为这个语句默认会把所有继承的事件屏蔽掉。下一步我们需要进行另外的设置。

<script lang="ts">
export default {
  inheritAttrs: false
}
</script>

第二步:让 div 中的 button 绑定 $attrs

$attrs 可以让某个元素 button 继承绑定在 Button 上的所有属性。只需要在 button 上加一句 v-bind="$attrs"即可。

<button v-bind="$attrs">

这时点击 button,就可以在控制台中看到 @click/@focus/@mouseover 事件结果出现。

网络异常,图片无法展示
|

如何按需加载不同组件的事件?

比如我一个组件想要 clickfocus 两个事件,另一个组件只需要 click 事件,应该如何实现呢?主要思路是在组件内部声明setup,传入参数,并将获取到的参数 return 出来即可。

<template>
  <div :size="size"> <!--第三步:在标签上定义获取到的事件或属性,这里是规定了尺寸大小-->
    <button v-bind="$attrs">
      <slot/>
    </button>
  </div>
</template>
<script lang="ts">
export default {
  inheritAttrs: false,
  setup(props, context){
    //第一步:析构语法获取到外部的事件或属性
    const {size, onClick, onMouseOver} = context.attrs;
    //第二步:把获取到的size事件或属性return出来
    return {size}
  }
}
</script>

还可以使用 ES6 最新的剩余操作符来简化。获取到 ...rest 之后,在 template 中使用即可。

<script lang="ts">
export default {
  inheritAttrs: false,
  setup(props, context){
    //使用...rest扩展操作符,获取到除size之外其他的属性或事件
    const {size, ...rest} = context.attrs;
    return {size, rest}
  }
}
</script>

弹窗中的具名插槽(slot)

我们制作的弹窗(Dialog),希望可以让用户自定义其中的标题和内容,这时候可以使用具名插槽 v-slot。Vue 3 中的具名插槽的使用方式跟 Vue 2 有所不同。

第一步:使用 v-slot:xxx 定义好插槽的名字:

<template v-slot:content>
   <div>hello</div>
</template>

第二步:在需要的地方引入插槽,使用 <slot name="xxx">

<main>
    <slot name="content"/>
</main>

实现效果如下:

网络异常,图片无法展示
|

使用 Teleport “传送”组件

由于 CSS 层级上下文的原因,Dialog 组件虽然 z-index10,但是其处于<div style="z-index: 1;">标签中,所以 Dialog 的层级肯定是没有外部 <div style="z-index: 2;"> 的层级高,从而被红色方框遮挡。这个组件的层级是由其所在的环境大小决定的,而不是由组件本身的层级来决定。 打个比方,一般学校里的尖子班,即便成绩排名中游,也会比普通班的中游成绩要高。

网络异常,图片无法展示
|

那我们应该如何防止 Dialog 被遮挡呢?这时候就需要使用 teleportteleport 的中文意思是“传送”,我们可以借用它来把 Dialog “传送”出去,这样就不会被其他元素影响了。

使用 <Teleport>把需要“传送”的组件包起来,加入 to="body" 来指定需要传送的目的地,这里直接移动到 body 标签下。

网络异常,图片无法展示
|

成功后,我们在开发者工具中就可以看到 Dialog 已经被成功“传送”了。

网络异常,图片无法展示
|

动态挂载组件

如果不想在组件中声明一个变量,又想改变这个变量的值,我们可以使用 createApp 的 h 来实现。这里就拿【一键打开Dialog组件】这个功能来举例。

第一步:创建一个 Button,添加 @click="showDialog" 事件,功能为点击打开 Dialog

第二步:showDialog 函数中调用 openDialog,用户可以传入 titlecontentokcancel 来自定义 Dialog 组件中的内容;

<script>
    const showDialog = ()=>{
      openDialog({
        title:'标题',
        content:'你好',
        ok(){console.log('ok');},
        cancel(){console.log('cancel');}
      })
    }
    return {showDialog}
  }
}
</script>

第三步:创建一个 openDialog.ts 组件,可以获取外部的属性,在 body 中直接创建一个 div 。这里需要使用h() 来构造新的 Dialog

import Dialog from './Dialog.vue';
import {createApp, h} from 'vue';
export const openDialog = (options) => {
    const {title, content, ok, cancel} = options;
    const div = document.createElement('div');
    document.body.appendChild(div);
    const close = () => {
        app.unmount(div);
        div.remove();
    };
    const app = createApp({
        render() {
          //使用h构造Dialog
            return h(Dialog, {
                visible: true, 'onUpdate:visible': (newVisible) => {
                    if (newVisible === false) {
                        close();
                    }
                },
                ok, cancel
            }, {
                title, content
            });
        }
    });
  //挂载新的div
    app.mount(div);
};

使用 Template Refs 动态设置 div 宽度

在做好 Tab 的底部导航条提醒后,一开始把宽度规定为 100px,但宽度已经超过了文字的宽度。对于组件库来说,如何根据使用者的文字来动态设置这个导航条的宽度呢?我们需要使用 Vue3 中的 Template Refs

网络异常,图片无法展示
|

根据 Vue 3 文档中的用法,我们在导航文字 div 上绑定一个 ref ,挂载组件的时候使用 ref 里面的 value 获取到 div 宽度。

<template>
  <div :ref="element => { if(element) navItems[index] = element }"></div>
<!--如果element存在,那么就让navItems里的元素等于element-->
  <div class="tree-tabs-nav-indicator"></div>
</template>
<script lang="ts">
import {onMounted, ref} from 'vue';
export default {
  setup(props, context) {
    const navItems = ref<HTMLDivElement[]>([]);
    const indicator = ref<HTMLDivElement>(null);
    onMounted(() => {
      const divs = navItems.value;
      const result = divs.filter(div=>div.classList.contains('selected'))[0]
      const {width} = result.getBoundingClientRect()
      indicator.value.style.width = width + 'px'
    });
    return {navItems, indicator};
  }
};
</script>

成功运行代码后,我们可以在控制台实时获取 indicator 宽度。

网络异常,图片无法展示
|

使用 custom block 展示源代码

我们想要把组件中的源代码展示到页面上,这时候需要用到custom block这个插件。

网络异常,图片无法展示
|

第一步:在 vite.config.ts 中加入以下代码,主要作用是解析组件中的代码。

import {md} from './plugins/md'
import fs from 'fs'
import {baseParse} from '@vue/compiler-core'
export default {
    vueCustomBlockTransforms: {
        demo: (options) => {
            const { code, path } = options
            const file = fs.readFileSync(path).toString()
            const parsed = baseParse(file).children.find(n => n.tag === 'demo')
            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()
        }
    }
}

第二步:在需要展示代码的组件中,添加 <demo> 标签:

<demo>
常规用法
</demo>
<template>
  <Switch v-model:value="bool"/>
</template>

第三步:父组件中展示代码:

<div class="demo-code">
  <pre>{{Switch1Demo.__sourceCode}}</pre>
</div>

使用 prismjsv-html 高亮源代码

prismjs 是一个用于高亮代码的库,只需引入即可使用,适用于我们UI库中进行代码展示。

安装prismjs:yarn add prismjs

在组件中引入,并使用 setup() 导出,方便使用:

import 'prismjs';
import 'prismjs/themes/prism-okaidia.css';
//themes文件中为不同的主题,大家可以多试几个,用自己比较喜欢的
const Prism = (window as any).Prism;
export default {
  setup() {
    return {Prism};
  }
};

template 中进行展示:

<template>
   <div class="demo-code">
     <pre class="language-html" v-html="Prism.highlight(Switch1Demo.__sourceCode,
            Prism.languages.html,'html')"/>
   </div>
</template>

高亮代码效果如下:

网络异常,图片无法展示
|

兼容 TypeScript 和 markdown

有时候一些库没有TypeScript声明文件,可以在安装完库后运行如下代码,xxx为库名:

yarn add --dev @types/xxx

一般IDE都不会识别markdown类型文件,可以在 shims.d.ts 中声明一下,避免报错:

declare module '*.md'{
    const str: string
    export default str
}

项目自动化打包和部署

使用如下命令:

yarn build

运行成功后会在项目根目录下生成一个 dist 文件夹,只需要把这个文件夹上传到 github 即可。

一般从打包到部署 github 需要输入很多命令行,这时候我们可以使用一个 deploy.sh 来实现自动化部署。

在项目根目录下新建一个 deploy.sh,输入以下命令(加&&是需要确认命令的运行情况,如某条命令运行失败则步停止):

rm -rf dist &&
yarn build &&
cd dist &&
git init &&
git add . &&
git commit -m "update" &&
git branch -M main &&
//这里输入你的的github仓库地址 
git remote add origin git@github.com:xxxxxx &&
git push -f -u origin main &&
cd ..

以后只需要在终端中输入 sh deploy.sh 即可实现一行代码部署

参考

[大威Wayne](juejin.cn/post/698756…

5、关于Vue3的知识点

1. ref, 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property.value

import {ref} from 'vue'
const value = ref<boolean>(false)  //false为默认值
return {
    value
}

2.setup(compoents ApI)

export default{
  /*
  props 父组件传过来的属性
  context当前实例
  */
    setup(props,context){
    //this.$emit('xxx')
    context.emit('xxxx')
  }
}

3.v-model

<switch :value='x' @update:value="x=$event"/>
<switch v-model:value="y"/>

4.inheritAttrs:false(组件内部不在继承调用时传来的属性)

默认所有属性都绑定到了根元素之上

<script lang="ts">
export default {
    inheritAttrs:false
}  
</script>

5.$attrs(组价调用时候传递过来的属性对象)

6.props vs attrs 的区别

  1. props需要先声明才能取值,attrs不用先声明
  2. props不包含事件,attrs包含
  3. props支持string以外的类型,attrs只有string类型(这条貌似存疑, attrs好像也能接受其他)
  4. props没有声明的属性,会跑到attrs里面去

7.ui库不能使用scoped ,每一个class 必须添加前缀,css最小影响原则

8.检查子组件传来的参数,是不是一个组件。

每一个.vue文件都将转换成type 而这个type 就是一个render函数

9.组件内部用JS获取slot传来的内容(context.slots.default())

10.v-for 获取每一个el元素

内部用途 v-for

在 Vue 2 中,在v-for里使用的refattribute 会用 ref 数组填充相应的$refsproperty;

在 Vue 3 中,这样的用法将不再在$ref中自动创建数组。要从单个绑定获取多个 ref,请将ref绑定到一个更灵活的函数上 (这是一个新特性)

在内部使用时,Composition API模板引用没有特殊处理v-for。而是使用函数ref来执行自定义处理:

<template>
  <div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
    {{ item }}
  </div>
</template>
<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'
  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])
      // make sure to reset the refs before each update
      onBeforeUpdate(() => {
        divs.value = []
      })
      return {
        list,
        divs
      }
    }
  }
</script>

11.组件触发事件之后要满足一定的条件再执行某个动作的时候就不能使用emit 触发事件,必须使用函数当做props,然后在内部进行判断。

12.钩子onMounted和onUpdated,可考虑使用watchEffect代替

13.Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。

<Teleport to="body">
  内容
</Teleport>
    ```


相关文章
|
1月前
|
JavaScript
Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
这篇文章介绍了如何使用Ant Design Vue UI框架创建一个简单的后台管理模板,包括创建Vue项目、安装和使用ant-design-vue、以及编写后台管理通用页面的代码和样式。
Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
|
1月前
|
JavaScript
vue3 element-ui-plus Carousel 跑马灯 的使用 及 踩坑记录
本文介绍了在Vue 3中使用Element UI Plus的Carousel组件实现跑马灯效果的方法,并分享了在实现过程中遇到的常见问题和解决方案。
vue3 element-ui-plus Carousel 跑马灯 的使用 及 踩坑记录
|
1月前
|
JavaScript 前端开发 开发工具
使用vue3+element-ui plus 快速构建后台管理模板
本文介绍了如何使用Vue 3和Element UI Plus快速构建后台管理模板的步骤,包括安装Vue 3脚手架、Element UI Plus以及如何全局配置Element UI Plus。然后详细讲解了如何使用Element UI Plus构建布局,包括Header组件、Aside组件和HomeView视图的创建和样式调整,以及App.vue和main.css的修改,最后提供了项目的文件结构图和效果展示。
使用vue3+element-ui plus 快速构建后台管理模板
|
2月前
|
JavaScript
vue element-ui 中el-message重复弹出问题解决 el-message重复弹出解决办法
vue element-ui 中el-message重复弹出问题解决 el-message重复弹出解决办法
168 49
|
5天前
|
JavaScript 索引
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
32 0
|
9天前
|
Linux C# Android开发
分享3款开源、免费的Avalonia UI控件库
分享3款开源、免费的Avalonia UI控件库
|
1月前
|
搜索推荐 前端开发 C#
推荐7款美观且功能强大的WPF UI库
推荐7款美观且功能强大的WPF UI库
|
2月前
|
JavaScript 前端开发 安全
[译] 在 Vue 组件中分离 UI 和业务逻辑。
[译] 在 Vue 组件中分离 UI 和业务逻辑。
|
2月前
|
API Android开发
Android项目架构设计问题之选择和使用合适的UI库如何解决
Android项目架构设计问题之选择和使用合适的UI库如何解决
41 0
|
2月前
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库