logo

给 Notification 组件添加 delay 属性

Thu Aug 11 2022 Posted 2 years ago

前几天给 Lew-UI 提了一个给 Notification 组件添加 delay 属性的 PR,参考了 ELement-plus 的实现,也算是学习到了一些东西,所以想简单做一个总结。

Notification 组件目前只能通过调用函数来使用,所以首先要给原本的调用函数增加一个名为 delay 的参数,类型为 Number,默认值为 3000,当传入的值为 0 时则通知不会自动关闭。

先看一下原本的代码逻辑

    // 在通知盒子里添加一个通知
    LewMessageDom?.appendChild(newMessage, LewMessageDom?.childNodes[0]);

    newMessage.setAttribute(
        'class',
        `lew-notification lew-notification-${type}`,
    );
    setTimeout(() => {
        newMessage.setAttribute(
            'class',
            `lew-notification lew-notification-${type} lew-notification-show`,
        );
        // 延时3000ms隐藏通知并延时移除dom
        setTimeout(() => {
            newMessage.setAttribute(
                'class',
                `lew-notification lew-notification-${type} lew-notification-hidden`,
            );
            setTimeout(() => {
                LewMessageDom?.removeChild(newMessage);
            }, 250);
        }, 3000);
    }, 10);

原先的关闭逻辑比较耦合,所以首先要把关闭相关的逻辑抽离出来:

    function handleClose() {
        newMessage.setAttribute(
            'class',
            `lew-notification lew-notification-${type} lew-notification-hidden`,
        );
        setTimeout(() => {
            LewMessageDom?.removeChild(newMessage);
        }, 250);
    }

因为我们需要一个可以控制的关闭延时,所以应该把关闭的定时器抽离出来:

 let timer: (() => void) | undefined = undefined;

    function startTimer() {
        if (delay > 0) {
            ({ stop: timer } = useTimeoutFn(() => {
                handleClose();
            }, delay));
        }
    }

    // 给关闭图标添加关闭事件
    newMessage.children[0].children[2].addEventListener('click', handleClose);

    setTimeout(() => {
        newMessage.setAttribute(
            'class',
            `lew-notification lew-notification-${type} lew-notification-show`,
        );
        delay > 0 && startTimer();
    }, 10);

然后加一点小细节,鼠标移入时清空计时器,移出时再重新添加计时器:

function clearTimer() {
  timer?.()
}
newMessage.addEventListener('mouseenter', clearTimer)
newMessage.addEventListener('mouseleave', startTimer)

完整的代码如下

import '../styles/index.scss';
import { useTimeoutFn } from '@vueuse/core';

export type NotificationParamsTyped = {
    title: string;
    content: string;
    delay?: number;
};

export type NotificationFn = (options: NotificationParamsTyped) => void;

export interface NotificationInstance {
    name: string;
    warning: NotificationFn;
    info: NotificationFn;
    normal: NotificationFn;
    success: NotificationFn;
    error: NotificationFn;
}

const warning = ({ title, content, delay = 3000 }: NotificationParamsTyped) => {
    notification('warning', title, content, delay);
};

const error = ({ title, content, delay = 3000 }: NotificationParamsTyped) => {
    notification('error', title, content, delay);
};

const info = ({ title, content, delay = 3000 }: NotificationParamsTyped) => {
    notification('info', title, content, delay);
};

const normal = ({ title, content, delay = 3000 }: NotificationParamsTyped) => {
    notification('normal', title, content, delay);
};

const success = ({ title, content, delay = 3000 }: NotificationParamsTyped) => {
    notification('success', title, content, delay);
};

const createMessageList = () => {
    const div: HTMLDivElement = document.createElement('div');
    div.setAttribute('id', 'lew-notification');
    document.body.appendChild(div);
};

const notification = (
    type: string,
    title: string,
    content: string,
    delay: number,
) => {
    if (!document.getElementById('lew-notification')) {
        createMessageList();
        notification(type, title, content, delay);
    } else {
        add(type, title, content, delay);
    }
};

const add = (type: string, title: string, content: string, delay: number) => {
    const LewMessageDom = document.getElementById('lew-notification');
    const newMessage = document.createElement('div');
    const svgArr: any = {
        success: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2zm0 1.5a8.5 8.5 0 1 0 0 17a8.5 8.5 0 0 0 0-17zm-1.25 9.94l4.47-4.47a.75.75 0 0 1 1.133.976l-.073.084l-5 5a.75.75 0 0 1-.976.073l-.084-.073l-2.5-2.5a.75.75 0 0 1 .976-1.133l.084.073l1.97 1.97l4.47-4.47l-4.47 4.47z" fill="currentColor"></path></g></svg>`,
        warning: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M10.91 2.782a2.25 2.25 0 0 1 2.975.74l.083.138l7.759 14.009a2.25 2.25 0 0 1-1.814 3.334l-.154.006H4.243a2.25 2.25 0 0 1-2.041-3.197l.072-.143L10.031 3.66a2.25 2.25 0 0 1 .878-.878zm9.505 15.613l-7.76-14.008a.75.75 0 0 0-1.254-.088l-.057.088l-7.757 14.008a.75.75 0 0 0 .561 1.108l.095.006h15.516a.75.75 0 0 0 .696-1.028l-.04-.086l-7.76-14.008l7.76 14.008zM12 16.002a.999.999 0 1 1 0 1.997a.999.999 0 0 1 0-1.997zM11.995 8.5a.75.75 0 0 1 .744.647l.007.102l.004 4.502a.75.75 0 0 1-1.494.103l-.006-.102l-.004-4.502a.75.75 0 0 1 .75-.75z" fill="currentColor"></path></g></svg>`,
        error: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M12 2c5.523 0 10 4.478 10 10s-4.477 10-10 10S2 17.522 2 12S6.477 2 12 2zm0 1.667c-4.595 0-8.333 3.738-8.333 8.333c0 4.595 3.738 8.333 8.333 8.333c4.595 0 8.333-3.738 8.333-8.333c0-4.595-3.738-8.333-8.333-8.333zm-.001 10.835a.999.999 0 1 1 0 1.998a.999.999 0 0 1 0-1.998zM11.994 7a.75.75 0 0 1 .744.648l.007.101l.004 4.502a.75.75 0 0 1-1.493.103l-.007-.102l-.004-4.501a.75.75 0 0 1 .75-.751z" fill="currentColor"></path></g></svg>`,
        normal: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M12 1.999c5.524 0 10.002 4.478 10.002 10.002c0 5.523-4.478 10.001-10.002 10.001C6.476 22.002 2 17.524 2 12.001C1.999 6.477 6.476 1.999 12 1.999zm0 1.5a8.502 8.502 0 1 0 0 17.003A8.502 8.502 0 0 0 12 3.5zm-.004 7a.75.75 0 0 1 .744.648l.007.102l.004 5.502a.75.75 0 0 1-1.494.102l-.006-.101l-.004-5.502a.75.75 0 0 1 .75-.75zm.005-3.497a.999.999 0 1 1 0 1.997a.999.999 0 0 1 0-1.997z" fill="currentColor"></path></g></svg>`,
        info: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M12 1.996a7.49 7.49 0 0 1 7.496 7.25l.004.25v4.097l1.38 3.156a1.249 1.249 0 0 1-1.145 1.75L15 18.502a3 3 0 0 1-5.995.177L9 18.499H4.275a1.251 1.251 0 0 1-1.147-1.747L4.5 13.594V9.496c0-4.155 3.352-7.5 7.5-7.5zM13.5 18.5l-3 .002a1.5 1.5 0 0 0 2.993.145l.007-.147zM12 3.496c-3.32 0-6 2.674-6 6v4.41L4.656 17h14.697L18 13.907V9.509l-.003-.225A5.988 5.988 0 0 0 12 3.496z" fill="currentColor"></path></g></svg>`,
        close: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144"></path><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 144L144 368"></path></svg>`,
    };
    newMessage.innerHTML = `
                <div class="lew-notification-box">
                    <div class="lew-notification-icon">
                      ${svgArr[type]}
                    </div>
                    <div class="lew-notification-body">
                      <div class="lew-notification-title">${title}</div>
                      ${
                          content
                              ? '<div class="lew-notification-content">' +
                                content +
                                '</div>'
                              : ''
                      }
                    </div> 
                    <div class="lew-notification-close-icon">
                      ${svgArr.close}
                    </div>
                </div>
    `;
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    LewMessageDom?.appendChild(newMessage, LewMessageDom?.childNodes[0]);

    newMessage.setAttribute(
        'class',
        `lew-notification lew-notification-${type}`,
    );

    let timer: (() => void) | undefined = undefined;

    function startTimer() {
        if (delay > 0) {
            ({ stop: timer } = useTimeoutFn(() => {
                handleClose();
            }, delay));
        }
    }

    function clearTimer() {
        timer?.();
    }

    function handleClose() {
        newMessage.setAttribute(
            'class',
            `lew-notification lew-notification-${type} lew-notification-hidden`,
        );
        setTimeout(() => {
            LewMessageDom?.removeChild(newMessage);
        }, 250);
    }

    newMessage.children[0].children[2].addEventListener('click', handleClose);
    newMessage.addEventListener('mouseenter', clearTimer);
    newMessage.addEventListener('mouseleave', startTimer);

    setTimeout(() => {
        newMessage.setAttribute(
            'class',
            `lew-notification lew-notification-${type} lew-notification-show`,
        );
        delay > 0 && startTimer();
    }, 10);
};

export default {
    name: 'LewNotification',
    warning,
    info,
    normal,
    success,
    error,
} as NotificationInstance;

整体来看这个组件的功能还是太简单了,而且存在很多问题,比如内容的 dom 元素都是用 innerHtml 来添加的,所以想要给里面的元素绑定事件会有些不直观。后期这部分我认为还是应该重构为 Vue 组件,可以极大的提高可维护性。