mirror of
https://github.com/web-infra-dev/midscene.git
synced 2025-12-29 08:00:09 +00:00
chore: optimize common logic
This commit is contained in:
parent
7cc296031b
commit
c82d0c493d
@ -32,30 +32,26 @@ interface FormData {
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [transformedEvents, setTransformedEvents] = useState<RecordedEvent[]>([]);
|
||||
const [optimizedEvents, setOptimizedEvents] = useState<RecordedEvent[]>([]);
|
||||
const [rawEventsCount, setRawEventsCount] = useState(0);
|
||||
const [mergedEventsCount, setMergedEventsCount] = useState(0);
|
||||
|
||||
const eventRecorderRef = useRef<EventRecorder | null>(null);
|
||||
const eventOptimizerRef = useRef<EventOptimizer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// 创建事件优化器
|
||||
eventOptimizerRef.current = new EventOptimizer();
|
||||
|
||||
// 创建事件记录器
|
||||
eventRecorderRef.current = new EventRecorder((event: RecordedEvent) => {
|
||||
setRawEventsCount((prev) => prev + 1);
|
||||
if (eventOptimizerRef.current) {
|
||||
const optimizedEvents = eventOptimizerRef.current.addEvent(event);
|
||||
setTransformedEvents(optimizedEvents);
|
||||
console.log('All Events:', optimizedEvents);
|
||||
const optimized = eventOptimizerRef.current.addEvent(event);
|
||||
console.log('optimized', optimized)
|
||||
setOptimizedEvents([...optimized]);
|
||||
setMergedEventsCount(eventOptimizerRef.current.getEventCount());
|
||||
}
|
||||
});
|
||||
|
||||
// 开始记录
|
||||
eventRecorderRef.current.start();
|
||||
|
||||
return () => {
|
||||
// 停止记录
|
||||
if (eventRecorderRef.current) {
|
||||
eventRecorderRef.current.stop();
|
||||
}
|
||||
@ -67,10 +63,8 @@ const App: React.FC = () => {
|
||||
message.error('两次输入的密码不一致!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Form Data:', values);
|
||||
console.log('Recorded Events:', transformedEvents);
|
||||
|
||||
console.log('Optimized Events:', optimizedEvents);
|
||||
message.success('注册成功!');
|
||||
};
|
||||
|
||||
@ -97,7 +91,6 @@ const App: React.FC = () => {
|
||||
>
|
||||
<Form.Item
|
||||
label="用户名"
|
||||
htmlFor="null"
|
||||
name="username"
|
||||
rules={[
|
||||
{ required: true, message: '请输入用户名!' },
|
||||
@ -207,10 +200,10 @@ const App: React.FC = () => {
|
||||
|
||||
<div className="rr-ignore" style={{ marginTop: 20, fontSize: 12, color: '#666', padding: '10px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
|
||||
<p style={{ margin: '4px 0' }}>📊 录制统计</p>
|
||||
<p style={{ margin: '4px 0' }}>记录事件: {transformedEvents.length}</p>
|
||||
<p style={{ margin: '4px 0' }}>🔄 合并事件: {mergedEventsCount}</p>
|
||||
<p style={{ margin: '4px 0' }}>原始事件: {rawEventsCount}</p>
|
||||
<p style={{ margin: '4px 0' }}>优化后事件: {mergedEventsCount}</p>
|
||||
<p style={{ margin: '4px 0', fontSize: '10px', color: '#999' }}>
|
||||
优化率: {transformedEvents.length > 0 ? Math.round((mergedEventsCount / transformedEvents.length) * 100) : 0}%
|
||||
优化率: {rawEventsCount > 0 ? Math.round((1 - mergedEventsCount / rawEventsCount) * 100) : 0}%
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@ -12,7 +12,7 @@ export class EventOptimizer {
|
||||
return [...this.events];
|
||||
}
|
||||
|
||||
// 如果是输入事件,检查是否需要跳过
|
||||
// 如果是输入事件,检查是否需要跳过或合并
|
||||
if (event.type === 'input') {
|
||||
const shouldSkip = this.shouldSkipInputEvent(event);
|
||||
if (shouldSkip) {
|
||||
@ -23,6 +23,24 @@ export class EventOptimizer {
|
||||
});
|
||||
return [...this.events];
|
||||
}
|
||||
const shouldMerge = this.shouldMergeInputEvent(event);
|
||||
if (shouldMerge) {
|
||||
// 获取旧的输入事件信息用于日志
|
||||
const oldInputEvent = this.events[this.events.length - 1];
|
||||
// 用新的输入事件替换最后一个输入事件
|
||||
this.events[this.events.length - 1] = {
|
||||
value: (event.element as HTMLInputElement)?.value,
|
||||
...event,
|
||||
};
|
||||
console.log('Merging input event:', {
|
||||
oldValue: oldInputEvent.value,
|
||||
newValue: event.value,
|
||||
oldTimestamp: oldInputEvent.timestamp,
|
||||
newTimestamp: event.timestamp,
|
||||
target: event.targetTagName,
|
||||
});
|
||||
return [...this.events];
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是滚动事件,检查是否需要替换上一个滚动事件
|
||||
@ -31,10 +49,8 @@ export class EventOptimizer {
|
||||
if (shouldReplace) {
|
||||
// 获取旧的滚动事件信息用于日志
|
||||
const oldScrollEvent = this.events[this.events.length - 1];
|
||||
|
||||
// 用新的滚动事件替换最后一个滚动事件
|
||||
this.events[this.events.length - 1] = event;
|
||||
|
||||
console.log('Replacing last scroll event with new scroll event:', {
|
||||
oldPosition: `${oldScrollEvent.x},${oldScrollEvent.y}`,
|
||||
newPosition: `${event.x},${event.y}`,
|
||||
@ -68,6 +84,45 @@ export class EventOptimizer {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否应该合并输入事件
|
||||
private shouldMergeInputEvent(inputEvent: RecordedEvent): boolean {
|
||||
const lastEvent = this.getLastEvent();
|
||||
|
||||
// 如果上一个事件是输入事件,并且是同一个输入目标
|
||||
if (
|
||||
lastEvent &&
|
||||
lastEvent.type === 'input' &&
|
||||
this.isSameInputTarget(lastEvent, inputEvent)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是同一个输入目标
|
||||
private isSameInputTarget(
|
||||
event1: RecordedEvent,
|
||||
event2: RecordedEvent,
|
||||
): boolean {
|
||||
// 比较元素标签名和ID
|
||||
if (event1.targetTagName !== event2.targetTagName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果都有ID,比较ID
|
||||
if (event1.targetId && event2.targetId) {
|
||||
return event1.targetId === event2.targetId;
|
||||
}
|
||||
|
||||
// 如果都没有ID,比较标签名(通常是document或body)
|
||||
if (!event1.targetId && !event2.targetId) {
|
||||
return event1.targetTagName === event2.targetTagName;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否应该替换滚动事件
|
||||
private shouldReplaceScrollEvent(scrollEvent: RecordedEvent): boolean {
|
||||
const lastEvent = this.getLastEvent();
|
||||
|
||||
@ -30,9 +30,7 @@ export class EventRecorder {
|
||||
private isRecording = false;
|
||||
private eventCallback: EventCallback;
|
||||
private scrollThrottleTimer: number | null = null;
|
||||
private lastScrollEvent: RecordedEvent | null = null;
|
||||
private scrollThrottleDelay = 1000; // 1000ms 节流
|
||||
private events: RecordedEvent[] = [];
|
||||
private lastViewportScroll: { x: number; y: number } | null = null;
|
||||
|
||||
constructor(eventCallback: EventCallback) {
|
||||
@ -46,9 +44,9 @@ export class EventRecorder {
|
||||
this.isRecording = true;
|
||||
|
||||
// 添加事件监听器
|
||||
document.addEventListener('click', this.handleClick, true);
|
||||
document.addEventListener('scroll', this.handleScroll, true);
|
||||
document.addEventListener('input', this.handleInput, true);
|
||||
document.addEventListener('click', this.handleClick);
|
||||
document.addEventListener('scroll', this.handleScroll);
|
||||
document.addEventListener('input', this.handleInput);
|
||||
|
||||
// 添加页面加载事件
|
||||
const navigationEvent: RecordedEvent = {
|
||||
@ -72,9 +70,9 @@ export class EventRecorder {
|
||||
}
|
||||
|
||||
// 移除事件监听器
|
||||
document.removeEventListener('click', this.handleClick, true);
|
||||
document.removeEventListener('scroll', this.handleScroll, true);
|
||||
document.removeEventListener('input', this.handleInput, true);
|
||||
document.removeEventListener('click', this.handleClick);
|
||||
document.removeEventListener('scroll', this.handleScroll);
|
||||
document.removeEventListener('input', this.handleInput);
|
||||
}
|
||||
|
||||
// 点击事件处理器
|
||||
@ -83,25 +81,11 @@ export class EventRecorder {
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
// 优化:如果上一个事件是 label 点击,并且 labelInfo.htmlFor 等于当前 input 的 id,则跳过
|
||||
const lastEvent = this.getLastEvent();
|
||||
if (
|
||||
lastEvent &&
|
||||
lastEvent.type === 'click' &&
|
||||
lastEvent.isLabelClick &&
|
||||
lastEvent.labelInfo?.htmlFor === target.id
|
||||
) {
|
||||
console.log('Skip input event triggered by label click:', target.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是 label 触发的点击
|
||||
const { isLabelClick, labelInfo } = this.checkLabelClick(target);
|
||||
|
||||
// 获取元素相对于 viewport 的位置
|
||||
const rect = target.getBoundingClientRect();
|
||||
const relativeX = rect.left;
|
||||
const relativeY = rect.top;
|
||||
|
||||
const clickEvent: RecordedEvent = {
|
||||
type: 'click',
|
||||
@ -117,12 +101,10 @@ export class EventRecorder {
|
||||
targetClassName: target?.className,
|
||||
isTrusted: event.isTrusted,
|
||||
detail: event.detail,
|
||||
viewportX: relativeX,
|
||||
viewportY: relativeY,
|
||||
viewportX: rect.left,
|
||||
viewportY: rect.top,
|
||||
};
|
||||
|
||||
console.log('Click Event:', clickEvent);
|
||||
this.events.push(clickEvent);
|
||||
this.eventCallback(clickEvent);
|
||||
};
|
||||
|
||||
@ -151,7 +133,7 @@ export class EventRecorder {
|
||||
target instanceof Document ? window.scrollY : target.scrollTop;
|
||||
|
||||
// 始终保存最新的滚动事件
|
||||
this.lastScrollEvent = {
|
||||
const scrollEvent: RecordedEvent = {
|
||||
type: 'scroll',
|
||||
x: scrollXTarget,
|
||||
y: scrollYTarget,
|
||||
@ -168,18 +150,8 @@ export class EventRecorder {
|
||||
}
|
||||
|
||||
this.scrollThrottleTimer = window.setTimeout(() => {
|
||||
if (this.lastScrollEvent && this.isRecording) {
|
||||
console.log('Throttled Scroll Event:', this.lastScrollEvent);
|
||||
|
||||
// 优化:如有必要,替换最后一个 scroll 事件,否则 push
|
||||
if (this.shouldReplaceScrollEvent(this.lastScrollEvent)) {
|
||||
this.events[this.events.length - 1] = this.lastScrollEvent;
|
||||
} else {
|
||||
this.events.push(this.lastScrollEvent);
|
||||
}
|
||||
|
||||
this.eventCallback(this.lastScrollEvent);
|
||||
this.lastScrollEvent = null;
|
||||
if (scrollEvent && this.isRecording) {
|
||||
this.eventCallback(scrollEvent);
|
||||
}
|
||||
this.scrollThrottleTimer = null;
|
||||
}, this.scrollThrottleDelay);
|
||||
@ -191,22 +163,8 @@ export class EventRecorder {
|
||||
|
||||
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
||||
|
||||
// 优化:如果上一个事件是 label 点击,并且 labelInfo.htmlFor 等于当前 input 的 id,则跳过
|
||||
const lastEvent = this.getLastEvent();
|
||||
if (
|
||||
lastEvent &&
|
||||
lastEvent.type === 'click' &&
|
||||
lastEvent.isLabelClick &&
|
||||
lastEvent.labelInfo?.htmlFor === target.id
|
||||
) {
|
||||
console.log('Skip input event triggered by label click:', target.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取元素相对于 viewport 的位置
|
||||
const rect = target.getBoundingClientRect();
|
||||
const relativeX = rect.left;
|
||||
const relativeY = rect.top;
|
||||
|
||||
const inputEvent: RecordedEvent = {
|
||||
type: 'input',
|
||||
@ -217,12 +175,10 @@ export class EventRecorder {
|
||||
targetId: target?.id,
|
||||
targetClassName: target?.className,
|
||||
inputType: target.type || 'text',
|
||||
viewportX: relativeX,
|
||||
viewportY: relativeY,
|
||||
viewportX: rect.left,
|
||||
viewportY: rect.top,
|
||||
};
|
||||
|
||||
console.log('Input Event:', inputEvent);
|
||||
this.events.push(inputEvent);
|
||||
this.eventCallback(inputEvent);
|
||||
};
|
||||
|
||||
@ -267,51 +223,4 @@ export class EventRecorder {
|
||||
isActive(): boolean {
|
||||
return this.isRecording;
|
||||
}
|
||||
|
||||
private getLastEvent(): RecordedEvent | undefined {
|
||||
return this.events[this.events.length - 1];
|
||||
}
|
||||
|
||||
// 检查是否应该替换滚动事件
|
||||
private shouldReplaceScrollEvent(scrollEvent: RecordedEvent): boolean {
|
||||
const lastEvent = this.getLastEvent();
|
||||
|
||||
// 如果最后一个事件是滚动事件,并且是同一个元素,则替换
|
||||
if (
|
||||
lastEvent &&
|
||||
lastEvent.type === 'scroll' &&
|
||||
this.isSameScrollTarget(lastEvent, scrollEvent)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是同一个滚动目标
|
||||
private isSameScrollTarget(
|
||||
event1: RecordedEvent,
|
||||
event2: RecordedEvent,
|
||||
): boolean {
|
||||
// 比较元素标签名和ID
|
||||
if (event1.targetTagName !== event2.targetTagName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果都有ID,比较ID
|
||||
if (event1.targetId && event2.targetId) {
|
||||
return event1.targetId === event2.targetId;
|
||||
}
|
||||
|
||||
// 如果都没有ID,比较标签名(通常是document或body)
|
||||
if (!event1.targetId && !event2.targetId) {
|
||||
return event1.targetTagName === event2.targetTagName;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getEvents(): RecordedEvent[] {
|
||||
return [...this.events];
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user