接入文档 / 实践

在 React 中接入

处理脚本加载、组件挂载和清理,让控件跟着页面生命周期走。

让控件跟着组件挂载

cap 使用浏览器控件,不依赖 React 专用 SDK。组件显示时创建容器,脚本加载后调用 window.capCaptcha.mount();组件移除时清理事件和容器。

下面的组件使用普通表单提交。把它保存为 CapForm.jsx,在已有 React 项目中传入你的公开 sitekey。后端沿用表单文章中的 /contact 处理逻辑。

CapForm.jsx下载文件 ↓
import { useEffect, useRef, useState } from 'react';

let loader;
function widgetReady() {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error('widget unavailable')), 10000);
    customElements.whenDefined('altcha-widget').then(() => { clearTimeout(timer); resolve(); });
  });
}
function loadCap() {
  if (window.capCaptcha) return widgetReady();
  if (!loader) loader = new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = 'https://cap.bug.im/v1.js';
    script.onload = () => {
      if (!window.capCaptcha) { reject(new Error('cap unavailable')); return; }
      widgetReady().then(resolve, reject);
    };
    script.onerror = () => { script.remove(); loader = undefined; reject(new Error('cap unavailable')); };
    document.head.append(script);
  });
  return loader;
}

export default function CapForm({ sitekey }) {
  const host = useRef(null);
  const [status, setStatus] = useState('正在加载验证控件…');
  useEffect(() => {
    let cancelled = false;
    const node = host.current;
    const container = document.createElement('div');
    container.className = 'cap-captcha';
    container.dataset.sitekey = sitekey;
    const onState = event => {
      const state = event.detail?.state;
      setStatus(state === 'verified' ? '验证完成,可以提交。' : state === 'error' ? '验证失败,请重试。' : '请完成验证。');
    };
    let widget;
    node.append(container);
    loadCap().then(() => {
      if (!cancelled) {
        window.capCaptcha.mount();
        widget = container.querySelector('altcha-widget');
        widget?.addEventListener('statechange', onState);
        setStatus('请完成验证。');
      }
    }).catch(() => { if (!cancelled) setStatus('控件加载失败,请刷新后重试。'); });
    return () => {
      cancelled = true;
      widget?.removeEventListener('statechange', onState);
      container.remove();
    };
  }, [sitekey]);
  return <form method="post" action="/contact">
    <label>留言 <textarea name="message" maxLength={2000} required /></label>
    <div ref={host} />
    <p role="status">{status}</p>
    <button type="submit">提交</button>
  </form>;
}
在页面中使用
import CapForm from './CapForm.jsx';

export default function Contact() {
  return <CapForm sitekey="YOUR_SITEKEY" />;
}

路由切换与开发模式

React 开发模式可能重复执行 Effect 的设置与清理。示例每次创建独立容器,清理时移除,避免重复挂载。如果使用支持服务端组件的框架,请将该文件标为客户端组件,再在客户端执行控件逻辑。

脚本载入成功不代表发题成功。域名、网络或 sitekey 配置问题会在控件中显示错误。脚本加载失败则刷新页面重试。

如果改用 fetch 提交

在 submit 处理器中用 new FormData(event.currentTarget) 读取隐藏字段,并发送给你自己的后端。不要从浏览器直接携带 secret 调用验证接口。成功提交、错误重试或重新显示表单时,重新挂载控件取得新 token。

生命周期依据:React useEffect 文档。

← 返回接入文档 · 体验验证