React TS2786报错:Card无法作为JSX组件使用问题咨询
错误根因
React 函数组件不支持定义为 async 异步函数:
- async 函数的返回值永远是
Promise类型,不符合 React 对组件返回值必须是同步 JSX 元素/null的要求,这就是 TS2786 报错的直接来源 - 你当前在组件函数体顶部直接
await AdyenCheckout(configuration)的写法,就算绕过 TS 校验,运行时也会因为组件返回 Promise 导致 React 渲染失败
修复步骤
- 移除组件定义的
async修饰符,让组件保持同步返回 JSX 的标准结构 - 将 Adyen 实例初始化、挂载的异步逻辑迁移到
useEffect中执行(useEffect 执行时 DOM 已经完成渲染,可以正常获取挂载容器节点) - 使用
useRef存储 Adyen checkout、卡组件实例,避免重复初始化,同时在组件卸载时调用实例的unmount方法做内存清理
修复后可运行代码
import { useEffect, useRef } from 'react'; import AdyenCheckout from '@adyen/adyen-web'; import type { CbObjOnBinLookup, AdyenCheckoutError, CardState } from '@adyen/adyen-web'; const Card = () => { // 存储容器和实例引用 const cardContainerRef = useRef<HTMLDivElement>(null); const checkoutRef = useRef<Awaited<ReturnType<typeof AdyenCheckout>> | null>(null); const cardComponentRef = useRef<ReturnType<Awaited<ReturnType<typeof AdyenCheckout>>['create']> | null>(null); useEffect(() => { // 异步初始化逻辑放到effect中 const initAdyenCard = async () => { if (!cardContainerRef.current) return; const handleOnChange = (state: CardState, component: unknown) => { state.isValid; // 校验用户输入是否全部合法 state.data; // 调用/payments接口需要传的支付数据 component; // 触发事件的当前组件实例 }; const configuration = { locale: "en_US", environment: "test", clientKey: "YOUR_CLIENT_KEY", onChange: handleOnChange }; const checkout = await AdyenCheckout(configuration); checkoutRef.current = checkout; cardComponentRef.current = checkout.create('securedfields', { type: 'card', brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro'], styles: { error: { color: 'red' }, validated: { color: 'green' }, placeholder: { color: '#d8d8d8' } }, ariaLabels: { lang: 'en-GB', encryptedCardNumber: { label: 'Credit or debit card number field', iframeTitle: 'Iframe for secured card number', error: 'Message that gets read out when the field is in the error state' } }, onChange: () => {}, onValid: () => {}, onLoad: () => {}, onConfigSuccess: () => {}, onFieldValid: () => {}, onBrand: () => {}, onError: (error: AdyenCheckoutError) => { console.error('Adyen card component error:', error); }, onFocus: () => {}, onBinValue: (bin: string) => {}, onBinLookup: (callbackObj: CbObjOnBinLookup) => {} // 直接挂载到ref对应的DOM节点,不需要依赖id查询 }).mount(cardContainerRef.current); }; initAdyenCard(); // 组件卸载时清理实例 return () => { cardComponentRef.current?.unmount(); checkoutRef.current = null; cardComponentRef.current = null; }; }, []); return ( <div ref={cardContainerRef}> <label> <span>Card number:</span> <span data-cse="encryptedCardNumber"></span> </label> <label> <span>Expiry date:</span> <span data-cse="encryptedExpiryDate"></span> </label> <label> <span>CVV/CVC:</span> <span data-cse="encryptedSecurityCode"></span> </label> </div> ); }; export default Card;
额外注意点
- 不要再给组件外层套空 Fragment 后再写带id的容器,直接给容器绑定ref更可靠,避免id重复、DOM查询时机不对的问题
- 如果你使用的 Adyen Web 版本 >=5.0.0,官方已经提供了React封装包,不需要手动挂载实例,直接用封装好的组件可以减少这类生命周期问题
- 替换代码里的
YOUR_CLIENT_KEY为你自己的真实客户端密钥,test环境要注意对应测试密钥的权限配置
内容的提问来源于stack exchange,提问作者Bob
相关产品推荐
相关产品推荐

