效果
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 的设置是有限制的,就是不能有任何祖先元素设置了 transform
、perspective
或者 filter
样式属性。也就是说如果我们想要用 CSS transform
为祖先节点 <div class="outer">
设置动画,就会不小心破坏模态框的布局!
z-index 受限于它的容器元素。如果有其他元素与 <div class="outer">
重叠并有更高的 z-index
,则它会覆盖住我们的模态框。
vue 中提供了 <Teleport>
来将元素插入到需要的元素上,实际上你也可以用这种方式来使用 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<ToastMessageProps>();
这个时候就会出现下面的错误:
然后我又想到了思路二:
// ToastMessage.vue
export interface ToastMessageProps {message: string;dangerouslyUseHTMLString?: boolean
}
const props = defineProps<ToastMessageProps>();
这个时候其实类型 ToastMessageProps 对于 ts 来说是识别不到的,因为 .vue 文件在处理的时候是统一处理的。并没有其他类型, 可以看到 env.d.ts
/// <reference types="vite/client" />
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<typeof toastMessageProps>;
// 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道面试题,并对每个问题作出了回答和解析。
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:
文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取