2. 做一个极简 UI 库之Toast 组件

效果

2. 做一个极简 UI 库之Toast 组件_第1张图片

API 设计

先设计好了 API 写起来代码才不会犯迷糊

Toast(message: string; otherParams?: ToastParams): ToastReturn

interface ToastParams {time?: number;appendTo?: string | HTMLElement;dangerouslyUseHTMLString?: boolean;
}

interface ToastReturn {close():void
} 

ToastParams 详解

属性 说明 类型 默认值
time Toast 存在的时长, 单位秒, -1 代表永不消失 number 2
appendTo 将 Toast 放到那个 dom 下 string 或 HTMLElement document.body
dangerouslyUseHTMLString 是否将 message 解析为 html 片段 boolean false

ToastReturn 详解

属性 说明 类型 默认值
close 关闭 Toast Function

难点说明

先说一下实现一个居中的 Toast 提示的基本思路:

div {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);
} 

有了这个核心样式 Toast 就有了。再继续往下做其实还需要俩步:

1.解决覆盖(遮挡)问题:需要放到顶级元素 body 下
2.如何用 js 代码进行这个组件的调用

先说第一个

fixed 的设置是有限制的,就是不能有任何祖先元素设置了 transformperspective 或者 filter 样式属性。也就是说如果我们想要用 CSS transform 为祖先节点

设置动画,就会不小心破坏模态框的布局!

z-index 受限于它的容器元素。如果有其他元素与

重叠并有更高的 z-index,则它会覆盖住我们的模态框。

vue 中提供了 来将元素插入到需要的元素上,实际上你也可以用这种方式来使用 ToastMessage.vue 组件。只是需要用 v-if 来控制

第二个问题

如何用 js 直接调用 ToastMessage.vue 组件呢?好像必须要写在 template 里面才行吧?这里其实就要用到一些高级技巧了:

import { createVNode, render } from "vue";
import ToastMessage from "ToastMessage.vue";// 将组件变为 vnode 节点, 这个就是写 jsx 的时候的 h 方法
const vnode = createVNode(ToastMessage, ...);
// 将 vnode 节点挂载到浏览器的 dom 元素上
render(vnode, document.body) 

这是关键的俩步,将这俩步封装成一个 Function 就可以四处调用了

注意事项

defineProps

我们平常使用 defineProps 可能是这个样子的:

const props = defineProps<{message: string;dangerouslyUseHTMLString?: boolean
}>(); 

基于上面的思路我就想到了把 defineProps 接受的类型单独拿出来变成这样方便处理,结果新的事情发生了:

// type.ts
export interface ToastMessageProps {message: string;dangerouslyUseHTMLString?: boolean
}
// ToastMessage.vue
import { ToastMessageProps } from "./type"
const props = defineProps(); 

这个时候就会出现下面的错误:

然后我又想到了思路二:

// ToastMessage.vue
export interface ToastMessageProps {message: string;dangerouslyUseHTMLString?: boolean
}

const props = defineProps(); 

这个时候其实类型 ToastMessageProps 对于 ts 来说是识别不到的,因为 .vue 文件在处理的时候是统一处理的。并没有其他类型, 可以看到 env.d.ts

/// 

declare module "*.vue" {import type { DefineComponent } from "vue";// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-typesconst component: DefineComponent<{}, {}, any>;export default component;
} 

因此最后的实现方式是:

// type.ts
export const toastMessageProps = {message: {type: String,default: "",},dangerouslyUseHTMLString: {type: Boolean,default: false,},
};
export type ToastMessageProps = ExtractPropTypes;

// ToastMessage.vue
const props = defineProps(toastMessageProps); 

采用了 vue 最原始的定义参数的方式,然后 vue 提供了 ExtractPropTypes 类型来帮你获得需要的类型,也是很方便的。

两个文件间的引用

目前我的文件划分是(也是正确的划分方式):

ToastMessage.vue // 实现 Toast 组件的地方
type.ts // 定义了 ToastMessage 相关的一些类型(props emit defineExpose)
index.ts // 实现非组件化调用 Toast 的地方 

之前的文件划分方式:

ToastMessage.vue // 实现 Toast 的地方
index.ts // 实现非组件化调用 Toast 的地方 和 ToastMessage 的相关类型 

这种方式就存在了下面的问题

// ToastMessage.vue
import { toastMessageProps } from './index.ts'
const props = defineProps(toastMessageProps);
// index.ts
import ToastMessage from "./ToastMessage.vue"
export const toastMessageProps = { ... };

function Toast( ... ) {createVNode(ToastMessage, props);
} 

简单而言就是 index.ts 依赖的导入 ToastMessage.vue 文件又依赖了 index.ts 的导出。这个时候就会出现:

其他逻辑

查看源码

这里教一个小技巧,在 github 的项目界面按: SHIFT + . 可以进入 github.dev 网站,简单来说就是用浏览器端的 vscode 打开你的项目。

总结

这里主要是对于 vue 的 createVNode render 的运用。然后还可以学到 ExtractPropTypes 这个类型处理的方法。当然了我走过的坑小伙伴们就别再走一次了

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

你可能感兴趣的:(ui,javascript,vue.js)