基于React实现智能弹窗队列的完整方案,支持优先级控制、显示频率限制和动态内容渲染,结合了队列管理、状态解耦和动画控制的核心思想:
一、架构设计
1. 核心数据结构
// types/modal.d.ts
export interface ModalConfig {
id: string; // 唯一标识
content: React.ReactNode; // 弹窗内容
priority?: number; // 优先级(0-10)
max displays?: number; // 最大显示次数
expireTime?: number; // 过期时间戳
onShow?: () => void; // 显示回调
onClose?: () => void; // 关闭回调
}
2. 队列管理器实现
// hooks/useModalQueue.ts
import { useState, useCallback, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
const MODAL_ROOT_ID = '__modal-root__';
const modalRoot = document.createElement('div');
modalRoot.id = MODAL_ROOT_ID;
document.body.appendChild(modalRoot);
const useModalQueue = () => {
const [queue, setQueue] = useState<ModalConfig[]>([]);
const [current, setCurrent] = useState<ModalConfig | null>(null);
// 添加弹窗到队列
const enqueue = useCallback((config: ModalConfig) => {
// 频率控制逻辑
const now = Date.now();
const existing = queue.find(m => m.id === config.id);
if (existing) {
if (existing.maxDisplays && existing.displayCount >= existing.maxDisplays) {
console.warn(`弹窗 ${config.id} 已达最大显示次数`);
return;
}
if (existing.expireTime && now > existing.expireTime) {
console.warn(`弹窗 ${config.id} 已过期`);
return;
}
// 更新现有弹窗配置
setQueue(prev => prev.map(m =>
m.id === config.id ? { ...m, ...config } : m
));
} else {
setQueue(prev => [...prev, config]);
}
}, [queue]);
// 处理队列逻辑
const processQueue = useCallback(() => {
if (!current && queue.length > 0) {
const next = queue.sort((a, b) => b.priority! - a.priority!)[0];
setCurrent(next);
}
}, [current, queue]);
// 显示弹窗
const showModal = useCallback((config: ModalConfig) => {
enqueue(config);
processQueue();
}, [enqueue, processQueue]);
// 关闭当前弹窗
const closeModal = useCallback(() => {
if (!current) return;
current.onClose?.();
setQueue(prev => prev.filter(m => m.id !== current.id));
setCurrent(null);
processQueue();
}, [current, processQueue]);
// 自动处理队列
useEffect(() => {
processQueue();
}, [queue, processQueue]);
// 渲染弹窗
useEffect(() => {
if (!current) return;
const root = createRoot(modalRoot);
root.render(
<ModalWrapper
config={current}
onClose={closeModal}
/>
);
}, [current, closeModal]);
return { showModal, closeModal };
};
// 弹窗包装组件
const ModalWrapper = ({ config, onClose }: {
config: ModalConfig;
onClose: () => void
}) => {
return (
<div className="modal-backdrop">
<div className="modal-content">
{config.content}
<button onClick={onClose}>关闭</button>
</div>
</div>
);
};
二、核心功能实现
1. 显示频率控制
// 示例:每天最多显示3次
const today = new Date().toISOString().split('T')[0];
const config: ModalConfig = {
id: 'promotion',
content: <PromotionComponent />,
maxDisplays: 3,
expireTime: Date.now() + 24 * 60 * 60 * 1000,
onShow: () => {
localStorage.setItem(`modal_${config.id}`, today);
}
};
2. 优先级队列排序
// 在processQueue中修改排序逻辑
const next = queue.sort((a, b) => {
if (a.priority === b.priority) return 0;
return b.priority - a.priority; // 降序排列
})[0] || queue[0];
3. 动态内容渲染
// 使用动态组件
const DynamicContent = ({ type }: { type: 'alert' | 'confirm' }) => {
return type === 'alert' ? <AlertComponent /> : <ConfirmComponent />;
};
// 调用示例
showModal({
id: 'dynamic-modal',
content: <DynamicContent type="confirm" />,
priority: 5
});
三、高级特性扩展
1. 插队机制
// 修改enqueue方法
const enqueue = useCallback((config: ModalConfig & { immediate?: boolean }) => {
if (config.immediate) {
setQueue([config, ...queue.filter(m => m.id !== config.id)]);
} else {
// 常规入队逻辑
}
}, [queue]);
2. 动画控制
// 使用react-transition-group
import { CSSTransition } from 'react-transition-group';
const ModalWrapper = ({ config, onClose }) => {
return (
<CSSTransition
in={!!current}
timeout={300}
classNames="modal"
unmountOnExit
>
<div className="modal-container">
{config.content}
</div>
</CSSTransition>
);
};
四、使用示例
// 注册弹窗
const { showModal } = useModalQueue();
// 显示普通弹窗
showModal({
id: 'welcome',
content: <WelcomeMessage />,
priority: 3,
maxDisplays: 1
});
// 显示高优先级弹窗
showModal({
id: 'urgent-alert',
content: <UrgentAlert />,
priority: 10,
immediate: true
});
五、状态持久化方案
// 使用localStorage记录显示历史
const useModalQueue = () => {
const [queue, setQueue] = useState<ModalConfig[]>(() => {
const stored = localStorage.getItem('modalQueue');
return stored ? JSON.parse(stored) : [];
});
useEffect(() => {
localStorage.setItem('modalQueue', JSON.stringify(queue));
}, [queue]);
};
六、埋点统计增强
1. 埋点事件类型定义
// types/tracking.ts
export enum TrackingEventType {
ModalShow = 'modal_show',
ModalClose = 'modal_close',
ButtonClick = 'modal_button_click',
NetworkError = 'modal_network_error'
}
2. 埋点中间件实现
// hooks/useTrackingMiddleware.ts
import { useEffect } from 'react';
import { TrackingEventType } from '../types/tracking';
type TrackingMiddleware = (
event: TrackingEventType,
payload: Record<string, any>
) => void;
const trackingMiddlewares: TrackingMiddleware[] = [];
export const useTracking = () => {
const track = (event: TrackingEventType, payload: Record<string, any>) => {
// 执行所有中间件
trackingMiddlewares.forEach(middleware => middleware(event, payload));
};
return { track };
};
// 注册全局埋点中间件(示例:发送到Google Analytics)
export const registerTrackingMiddleware = (middleware: TrackingMiddleware) => {
trackingMiddlewares.push(middleware);
};
3. 弹窗埋点集成
// hooks/useModalQueue.ts
const useModalQueue = () => {
const { track } = useTracking();
// 显示弹窗时触发埋点
const showModal = useCallback((config: ModalConfig) => {
track(TrackingEventType.ModalShow, {
modalId: config.id,
source: config.source || 'system'
});
enqueue(config);
processQueue();
}, [track]);
// 关闭弹窗埋点
const closeModal = useCallback(() => {
if (!current) return;
track(TrackingEventType.ModalClose, { modalId: current.id });
// ...原有关闭逻辑
}, [current, track]);
// 按钮点击埋点
const trackButtonClick = useCallback((modalId: string, buttonType: 'confirm' | 'cancel') => {
track(TrackingEventType.ButtonClick, { modalId, buttonType });
}, [track]);
};
二、自动重试机制
1. 重试策略配置
// types/retry.ts
export interface RetryConfig {
maxAttempts?: number; // 最大重试次数
initialDelay?: number; // 初始延迟(ms)
backoffFactor?: number; // 退避系数(指数增长倍数)
}
2. 自动重试中间件
// hooks/useRetryMiddleware.ts
import { useEffect, useRef } from 'react';
import { TrackingEventType } from './tracking';
type RetryMiddleware = (
action: () => Promise<void>,
config: RetryConfig
) => Promise<void>;
export const useRetry = () => {
const retryQueue = useRef<any[]>([]);
const executeWithRetry = async <T>(
action: () => Promise<T>,
config: RetryConfig = {}
) => {
const { maxAttempts = 3, initialDelay = 1000, backoffFactor = 2 } = config;
let attempts = 0;
while (attempts < maxAttempts) {
try {
return await action();
} catch (error) {
attempts++;
if (attempts >= maxAttempts) throw error;
const delay = initialDelay * Math.pow(backoffFactor, attempts);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
};
return { executeWithRetry };
};
3. 弹窗重试集成
// 在弹窗组件中使用
const PromotionModal = ({ onClose }: { onClose: () => void }) => {
const { executeWithRetry } = useRetry();
const track = useTracking();
const handleShow = async () => {
try {
await executeWithRetry(
() => sendTrackingData('modal_show', { modalId: 'promotion' }),
{ maxAttempts: 5, backoffFactor: 1.5 }
);
} catch (error) {
track(TrackingEventType.NetworkError, {
modalId: 'promotion',
error: error.message
});
}
};
return (
<div>
<button onClick={() => handleShow()}>显示弹窗</button>
</div>
);
};
三、全局拦截器
1. 拦截器架构设计
// hooks/useInterceptor.ts
type Interceptor = (
context: {
modalId: string;
config: ModalConfig;
next: () => Promise<void>;
}
) => Promise<void>;
const interceptors: Interceptor[] = [];
export const useInterceptor = () => {
const register = (interceptor: Interceptor) => {
interceptors.push(interceptor);
};
const runInterceptors = async (context: {
modalId: string;
config: ModalConfig;
next: () => Promise<void>;
}) => {
for (const interceptor of interceptors) {
await interceptor(context);
}
};
return { register, runInterceptors };
};
2. 拦截器实现示例
// 权限拦截器
const authInterceptor = async ({ config, next }: InterceptorContext) => {
if (config.requiresAuth && !isUserLoggedIn()) {
redirectToLogin();
throw new Error('权限拦截');
}
await next();
};
// 日志拦截器
const logInterceptor = async ({ config, next }: InterceptorContext) => {
console.log(`[${new Date().toISOString()}] 弹窗显示: ${config.id}`);
await next();
console.log(`[${new Date().toISOString()}] 弹窗关闭: ${config.id}`);
};
3. 弹窗队列集成
// 修改processQueue逻辑
const processQueue = useCallback(async () => {
if (!current && queue.length > 0) {
const next = queue.sort(/*...*/)[0] || queue[0];
try {
await interceptor.runInterceptors({
modalId: next.id,
config: next,
next: () => setCurrent(next)
});
setCurrent(next);
} catch (error) {
console.error('拦截器阻止弹窗显示:', error);
}
}
}, [interceptor]);
四、完整集成方案
1. 项目结构
src/
├── hooks/
│ ├── useModalQueue.ts # 核心队列管理
│ ├── useTracking.ts # 埋点中间件
│ ├── useRetry.ts # 重试机制
│ └── useInterceptor.ts # 拦截器系统
├── middleware/ # 中间件集合
│ ├── analytics.ts # 埋点实现
│ ├── errorHandler.ts # 错误处理
│ └── authInterceptor.ts # 权限拦截
├── components/
│ └── ModalWrapper/ # 弹窗包装组件
└── utils/
└── retryPolicy.ts # 重试策略工具
2. 使用示例
// 初始化配置
const { track } = useTracking();
const { executeWithRetry } = useRetry();
const { register } = useInterceptor();
// 注册拦截器
register(authInterceptor);
register(logInterceptor);
// 显示带埋点的弹窗
const showModal = async () => {
try {
await executeWithRetry(
() => showModalConfig({
id: 'promotion',
content: <PromotionComponent />,
onShow: () => track('modal_show', { source: 'homepage' })
}),
{ maxAttempts: 3 }
);
} catch (error) {
track('modal_show_failed', { error: error.message });
}
};