Dialog组件结构
- 基于
Popup的二次封装 Popup可以看这里 pick函数自动赋值给Popup组件tsx写法的组件的结构和逻辑真的很清晰
// Dialog.tsx
...
return () => {
const { width, title, theme, message, className } = props;
return (
<Popup
ref={root}
role="dialog"
class={[bem([theme]), className]}
style={{ width: addUnit(width) }}
tabindex={0}
aria-labelledby={title || message}
onKeydown={onKeydown}
onUpdate:show={updateShow}
{...pick(props, popupInheritKeys)}
>
{renderTitle()}
{renderContent()}
{renderFooter()}
</Popup>
);
};
// pick函数实现
function pick<T, U extends keyof T>(obj:T,keys:ReadonlyArray<U>,ignoreUndefined?:boolean){
return keys.reduce((ret, key) => {
if(!ignoreUndefined || obj[key] !== undefined){
ret[key] = obj[key]
}
return ret
},{} as Writeable<Pick<T,U>>)
}
withInstall注册全局组件
vue3在调用app.use的过程中会判断组件有没有install函数,会自动安装withInstall给属性增加了一个函数,没有立刻加载组件,做到懒加载全局组件- 自动安装驼峰和原名组件
// index.ts
import _Dialog from './Dialog';
export const Dialog = withInstall(_Dialog);
export default Dialog;
export {
showDialog,
closeDialog,
showConfirmDialog,
setDialogDefaultOptions,
resetDialogDefaultOptions,
} from './function-call';
// withinstall.ts
export function withInstall<T extends Component>(options: T) {
(options as Record<string, unknown>).install = (app: App) => {
const { name } = options;
if (name) {
app.component(name, options);
app.component(camelize(`-${name}`), options); // 首字母大写
}
};
return options as WithInstall<T>;
}
全局调用弹窗方法
createApp实例化一个单例的instance,通过mount挂载在document.body下usePopupState提供弹窗开关响应式状态以及暴露的方法控制弹窗开关状态showDialog方法就可以直接调用usePopupState提供的open方法打开
Vant 中导出了以下 Dialog 相关的辅助函数:
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| showDialog | 展示弹窗 | options: DialogOptions | Promise<void> |
| showConfirmDialog | 展示消息确认弹窗 | options: DialogOptions | Promise<void> |
| closeDialog | 关闭弹窗 | - | void |
| setDialogDefaultOptions | 修改默认配置,影响所有的 showDialog 调用 | options: DialogOptions | void |
| resetDialogDefaultOptions | 重置默认配置,影响所有的 showDialog 调用 | - | void |
// function-call.tsx
let instance: ComponentInstance;
const DEFAULT_OPTIONS = {
title: '',
width: '',
theme: null,
message: '',
overlay: true,
callback: null,
teleport: 'body',
className: '',
allowHtml: false,
lockScroll: true,
transition: undefined,
beforeClose: null,
overlayClass: '',
overlayStyle: undefined,
messageAlign: '',
cancelButtonText: '',
cancelButtonColor: null,
cancelButtonDisabled: false,
confirmButtonText: '',
confirmButtonColor: null,
confirmButtonDisabled: false,
showConfirmButton: true,
showCancelButton: false,
closeOnPopstate: true,
closeOnClickOverlay: false,
} as const;
let currentOptions = extend({}, DEFAULT_OPTIONS); // 避免污染DEFAULT_OPTIONS 常量
function initInstance() {
const Wrapper = {
setup() {
const { state, toggle } = usePopupState();
return () => <Dialog {...state} onUpdate:show={toggle} />;
},
};
({ instance } = mountComponent(Wrapper));
// 实例化新组件挂载到新节点上 createApp mount
// usePopupState hook 拓展了instance暴露的方法以及提供响应式状态
// useExpose({ open, close, toggle });
}
export function showDialog(options: DialogOptions) {
/* istanbul ignore if */
if (!inBrowser) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
// 单个弹窗实例
if (!instance) {
initInstance();
}
// 暴露的open方法控制show的状态,返回promise
instance.open(
extend({}, currentOptions, options, {
callback: (action: DialogAction) => {
(action === 'confirm' ? resolve : reject)(action);
},
})
);
});
}
// 设置全局currentOptions状态
export const setDialogDefaultOptions = (options: DialogOptions) => {
extend(currentOptions, options);
};
// 重置全局currentOptions状态
export const resetDialogDefaultOptions = () => {
currentOptions = extend({}, DEFAULT_OPTIONS);
};
export const showConfirmDialog = (options: DialogOptions) =>
showDialog(extend({ showCancelButton: true }, options));
export const closeDialog = () => {
if (instance) {
instance.toggle(false);
}
};