如何在React(NextJS+TypeScript)的.tsx文件中嵌入Shopify购买按钮
在 Next.js + TypeScript 中嵌入 Shopify 购买按钮的实现方案
针对你的场景,直接在.tsx文件中粘贴原生HTML脚本会因为React的DOM渲染机制和脚本执行时机问题导致组件无法正常初始化,下面提供两种适配Next.js + TypeScript的可行方案:
方案一:使用 React useEffect 动态加载脚本
利用React的useEffect钩子在组件挂载后完成脚本加载和Shopify组件初始化,完美贴合React生命周期:
import { useEffect, useRef } from 'react'; // 声明ShopifyBuy的全局类型,避免TypeScript报错 declare global { interface Window { ShopifyBuy: any; } } export default function ShopifyCollection() { // 用useRef获取容器元素,避免重复查询DOM const collectionRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!collectionRef.current) return; const scriptURL = 'https://sdks.shopifycdn.com/buy-button/latest/buy-button-storefront.min.js'; // 初始化Shopify组件的逻辑 const initShopify = () => { const client = window.ShopifyBuy.buildClient({ domain: 'xxxxxxxxxxx.myshopify.com', storefrontAccessToken: 'xxxxxxxxxxxxxxxxxxxxxx', }); window.ShopifyBuy.UI.onReady(client).then((ui: any) => { ui.createComponent('collection', { id: 'xxxxxxxxxxxxx', node: collectionRef.current, moneyFormat: '%24%7B%7Bamount%7D%7D', options: { "product": { "styles": { "product": { "@media (min-width: 601px)": { "max-width": "calc(25% - 20px)", "margin-left": "20px", "margin-bottom": "50px", "width": "calc(25% - 20px)" }, "img": { "height": "calc(100% - 15px)", "position": "absolute", "left": "0", "right": "0", "top": "0" }, "imgWrapper": { "padding-top": "calc(75% + 15px)", "position": "relative", "height": "0" } }, "title": { "font-size": "14px", "color": "#000000" }, "button": { ":hover": { "background-color": "#10002b" }, "background-color": "#100030", ":focus": { "background-color": "#10452b" }, "padding-left": "35px", "padding-right": "35px" }, "price": { "color": "#086cce" }, "compareAt": { "color": "#0000ce" }, "unitPrice": { "color": "#0000ce" } }, "text": { "button": "Add to cart" } }, "productSet": { "styles": { "products": { "@media (min-width: 601px)": { "margin-left": "-20px" } } } }, "modalProduct": { "contents": { "img": false, "imgWithCarousel": true, "button": false, "buttonWithQuantity": true }, "styles": { "product": { "@media (min-width: 601px)": { "max-width": "100%", "margin-left": "0px", "margin-bottom": "0px" } }, "button": { ":hover": { "background-color": "#10452b" }, "background-color": "#124d30", ":focus": { "background-color": "#10452b" }, "padding-left": "35px", "padding-right": "35px" }, "title": { "font-family": "Helvetica Neue, sans-serif", "font-weight": "bold", "font-size": "26px", "color": "#40004c" }, "price": { "font-family": "Helvetica Neue, sans-serif", "font-weight": "normal", "font-size": "14px", "color": "#40004c" }, "compareAt": { "font-family": "Helvetica Neue, sans-serif", "font-weight": "normal", "font-size": "11.9px", "color": "#40004c" }, "unitPrice": { "font-family": "Helvetica Neue, sans-serif", "font-weight": "normal", "font-size": "11.9px", "color": "#40004c" } }, "text": { "button": "Add to cart" } }, "option": {}, "cart": { "styles": { "button": { ":hover": { "background-color": "#10002b" }, "background-color": "#100000", ":focus": { "background-color": "#10002b" } } }, "text": { "total": "Subtotal", "button": "Checkout" }, "popup": false }, "toggle": { "styles": { "toggle": { "background-color": "#120030", ":hover": { "background-color": "#10002b" }, ":focus": { "background-color": "#10002b" } } } } }, }); }); }; // 检查ShopifyBuy是否已加载 if (window.ShopifyBuy) { if (window.ShopifyBuy.UI) { initShopify(); } else { // 动态创建脚本标签加载Shopify SDK const script = document.createElement('script'); script.async = true; script.src = scriptURL; script.onload = initShopify; document.head.appendChild(script); } } else { const script = document.createElement('script'); script.async = true; script.src = scriptURL; script.onload = initShopify; document.head.appendChild(script); } // 清理函数:组件卸载时移除脚本(可选,避免重复加载) return () => { const existingScript = document.querySelector(`script[src="${scriptURL}"]`); if (existingScript) { document.head.removeChild(existingScript); // 清空全局变量,避免下次加载冲突 delete window.ShopifyBuy; } }; }, []); return <div ref={collectionRef} id="collection-component-XXXXXXXXXX9"></div>; }
方案二:使用 Next.js Script 组件(推荐)
Next.js 提供的Script组件支持脚本加载策略优化(如懒加载、预加载),更适合Next.js项目:
import { useRef } from 'react'; import Script from 'next/script'; declare global { interface Window { ShopifyBuy: any; } } export default function ShopifyCollection() { const collectionRef = useRef<HTMLDivElement>(null); const initShopify = () => { if (!collectionRef.current) return; const client = window.ShopifyBuy.buildClient({ domain: 'xxxxxxxxxxx.myshopify.com', storefrontAccessToken: 'xxxxxxxxxxxxxxxxxxxxxx', }); window.ShopifyBuy.UI.onReady(client).then((ui: any) => { ui.createComponent('collection', { id: 'xxxxxxxxxxxxx', node: collectionRef.current, moneyFormat: '%24%7B%7Bamount%7D%7D', options: { // 粘贴你原有的options配置,与方案一完全一致即可 }, }); }); }; return ( <> <div ref={collectionRef} id="collection-component-XXXXXXXXXX9"></div> {/* 使用Next.js Script组件加载Shopify SDK,策略选afterInteractive确保页面交互后加载 */} <Script src="https://sdks.shopifycdn.com/buy-button/latest/buy-button-storefront.min.js" strategy="afterInteractive" onLoad={initShopify} /> </> ); }
注意事项
- 替换代码中所有
xxxxxxxxxxx占位符为你实际的Shopify店铺域名、AccessToken和集合ID - TypeScript类型声明可以安装
@types/shopify-buy包获得更精确的类型,上述代码中的any为简化处理 - 用
useRef获取DOM元素比getElementById更可靠,避免React渲染时DOM未就绪的问题 - 测试时注意清理浏览器缓存,避免旧脚本影响加载效果
内容的提问来源于stack exchange,提问作者Crunchy
相关产品推荐
相关产品推荐

