跳转至

正则演示组件(RegexTester)

类型:wiki(知识沉淀) 描述:让用户即改即看的正则测试折叠组件,避免抽象正则改完不知道效果 最后更新:2026-05-26(v0.10.13) 相关源码src/sections/settings/regex-tester.tsx 使用位置src/sections/settings/view/settings-view.tsx(邮箱/手机/社媒正则旁各一个)

是什么

折叠按钮 + textarea + 实时匹配结果 — 用户改正则字段时点开「演示」即可看效果。

邮箱正则: [_____________________________]
[🧪 演示邮箱提取 · 匹配 2 项]            ▼

  ┌─ 测试 ─────────────────────────────┐
  │ 测试文本(贴入 HTML/纯文本均可)       │
  │ ┌─────────────────────────────────┐ │
  │ │ Contact: hello@acme.com,        │ │
  │ │ john_doe@sub.example.org        │ │
  │ └─────────────────────────────────┘ │
  │ ✓ 匹配到 2 项:                     │
  │   ┌──────────────────┐              │
  │   │ hello@acme.com    │              │
  │   │ john_doe@sub.exa..│              │
  │   └──────────────────┘              │
  └────────────────────────────────────┘

为什么

正则太抽象,用户改完不知道效果。v0.10.13 之前的痛点:

  • 用户改了 phoneRegex → 跑一次任务 → 看结果发现不对 → 再改 → 再跑 → ...
  • 一轮调试 5 分钟,10 次就是 50 分钟。

加 RegexTester 后: - 改正则 → 点演示 → 0.1 秒看结果 → 继续改 → ...

关键代码

Props

interface Props {
  regex: string;           // 当前正则字符串
  sampleDefault: string;   // 默认示例文本(含「应匹配」+「应过滤」例子)
  label?: string;          // 折叠按钮文字
  flags?: string;          // 默认 'gi'
  groupIndex?: number;     // 默认 0(整体);手机 Tier 用 1
}

即改即看

const { matches, error } = useMemo(() => {
  try {
    const re = new RegExp(regex, flags);
    const arr: string[] = [];
    let m;
    while ((m = re.exec(text)) !== null) {
      const captured = m[groupIndex] ?? m[0];
      if (captured && !arr.includes(captured)) arr.push(captured);
      if (!re.global) break;       // 防死循环
      if (arr.length >= 50) break;  // 上限
    }
    return { matches: arr, error: '' };
  } catch (e) {
    return { matches: [], error: e.message };
  }
}, [regex, text, flags, groupIndex]);

失败友好

正则非法时显示红字 ⚠️ <message>不抛错。这样用户敲到一半也不会崩。

用法示例

import { RegexTester, SAMPLE_EMAIL_TEXT } from '../regex-tester';

<RHFTextField name="emailRegex" />
<RegexTester
  regex={(currentValues as any)?.emailRegex || DEFAULT_EMAIL_REGEX}
  sampleDefault={SAMPLE_EMAIL_TEXT}
  label="演示邮箱提取"
  flags="gi"
/>

内置示例文本

每个示例都包含应匹配 + 应过滤的例子,让用户能看到过滤效果:

export const SAMPLE_EMAIL_TEXT = `Contact us:
- Sales: sales@acme.com
- Support: help@sub.example.org

<a data-cfemail="...">Protected</a>     ← Cloudflare 解码示例

(spam to filter)
test@test.com                            ← 内置黑名单应过滤
icon.png@cdn-cgi/l/email-protection      ← 噪音模式应过滤
`;

export const SAMPLE_PHONE_TEXT = `Call us!
<a href="tel:+1-415-555-1234">Click</a>  ← Tier 1
Phone: +44 20 7946 0958                  ← Tier 2
Free text: +1 234 567 8901               ← Tier 3
ISBN 9780123456789                       ← 应被长度过滤
Order #1234567890                        ← 应被黑名单过滤
`;

export const SAMPLE_SOCIAL_TEXT = `...含正面 + 反面例子...`;

已踩坑(已修复)

⚠️ 坑 1:useMemo key 漏字段

最初 key 只放了 regex —— 用户改 text 不重算。 修复:key 加 flags + groupIndex + text 全部依赖。

⚠️ 坑 2:re.global 检测不准

不是 re.global flag 而是 flags.includes('g')。修复同上。

⚠️ 坑 3:匹配项太多导致 UI 卡顿

上限 50 项 + whilearr.length >= 50 break。

修改这块时要 / 不要做什么

  • ✅ 加新示例文本 → export SAMPLE_<类型>_TEXT,含正反例
  • ✅ 加新 prop → keep optional,默认走旧行为
  • ❌ 不要去掉错误友好(try/catch) — 用户敲到一半时正则不合法是常态
  • ❌ 不要去掉 50 项上限(防 UI 卡顿)

版本里程碑

版本 事件
v0.10.13 最初引入,邮箱/手机/社媒 3 处使用