Shopify结账配送选项扩展中useSubscription引发无限循环问题求助
Shopify结账配送选项扩展中useSubscription引发无限循环问题求助
看起来你遇到了两个关联的问题:一个是useSubscription触发的无限循环,另一个是初始渲染时多次打印test的问题。我们一步步来拆解原因并给出修复方案:
一、无限循环的原因分析
你的代码里存在一个闭环逻辑:
- 你订阅了
deliveryGroups,并把它作为useEffect的依赖 - 在这个
useEffect里调用updateAttributes,而applyAttributeChange会修改结账的自定义属性 - 自定义属性的更新会触发Shopify checkout状态变化,进而导致
deliveryGroups重新订阅更新 - 这又会再次触发
useEffect,形成无限循环
二、无限循环的修复方案
我们需要打破这个闭环,调整触发更新的逻辑,并避免无意义的状态修改:
1. 正确使用useSubscription获取稳定值
Shopify UI扩展返回的lines、isTargetSelected等都是订阅对象,直接访问.current可能会拿到过时的引用,应该用useSubscription获取稳定的当前值:
// 替换直接访问.current的方式,改用useSubscription获取订阅值 const currentLines = useSubscription(lines); const currentIsTargetSelected = useSubscription(isTargetSelected); const currentShippingAddress = useSubscription(shippingAddress); const currentTarget = useSubscription(target);
2. 调整updateAttributes的触发条件
不要依赖deliveryGroups来触发属性更新,而是仅在选中状态、dalMin或dalMax变化时执行:
useEffect(() => { if (currentIsTargetSelected && dalMin && dalMax) { updateAttributes(); } }, [currentIsTargetSelected, dalMin, dalMax]); // 仅依赖必要的状态
3. 避免无意义的属性更新
在updateAttributes中先检查当前属性值是否已经和目标值一致,只有不同时才调用applyAttributeChange:
const updateAttributes = async () => { try { // 先查询当前的自定义属性值 const attributeRes = await query(`{ checkout { customAttributes { key value } } }`); const currentAttributes = attributeRes.data.checkout.customAttributes.reduce((acc, attr) => { acc[attr.key] = attr.value; return acc; }, {}); const newDalMin = dalMin.toISOString().slice(0, 10); const newDalMax = dalMax.toISOString().slice(0, 10); // 仅当值不同时才执行更新 if (currentAttributes.dal_min !== newDalMin) { await applyAttributeChange({ type: 'updateAttribute', key: 'dal_min', value: newDalMin, }); } if (currentAttributes.dal_max !== newDalMax) { await applyAttributeChange({ type: 'updateAttribute', key: 'dal_max', value: newDalMax, }); } } catch (err) { console.error('更新自定义属性失败:', err); } };
三、初始渲染多次打印test的原因
你的扩展挂载在purchase.checkout.shipping-option-item.render-after,每个配送选项都会生成一个该组件的实例(你有4个配送方法,就有4个组件实例)。而React组件每次状态更新时都会重新渲染,所以console.log('test')会在每个实例的每次渲染时打印,这是正常现象。
如果想减少调试打印,可以把console.log移到特定的useEffect里,或者只在开发环境打印:
useEffect(() => { if (process.env.NODE_ENV === 'development') { console.log('组件渲染:', shippingMethodTitle); } }, [shippingMethodTitle]);
完整修改后的代码示例
import { Text, useApi, useTranslate, reactExtension, useSubscription, } from '@shopify/ui-extensions-react/checkout'; import { useEffect, useState } from 'react'; import i18nIsoCountries from 'i18n-iso-countries'; i18nIsoCountries.registerLocale(require("i18n-iso-countries/langs/en.json")); export default reactExtension( 'purchase.checkout.shipping-option-item.render-after', () => <Extension />, ); function Extension() { const { shop, shippingAddress, lines, target, query, i18n, applyAttributeChange, isTargetSelected, deliveryGroups } = useApi(); const translate = useTranslate(); const [items, setItems] = useState([]); const [dalMin, setDalMin] = useState(); const [dalMax, setDalMax] = useState(); // 使用useSubscription获取稳定的订阅值 const currentLines = useSubscription(lines); const currentIsTargetSelected = useSubscription(isTargetSelected); const currentShippingAddress = useSubscription(shippingAddress); const currentTarget = useSubscription(target); console.log('test') const country = i18nIsoCountries.getName(currentShippingAddress.countryCode, 'en'); const shopDomain = shop.myshopifyDomain; const shippingMethodTitle = currentTarget.title; // 仅在选中状态、dalMin/dalMax变化时更新属性 useEffect(() => { if (currentIsTargetSelected && dalMin && dalMax) { updateAttributes(); } }, [currentIsTargetSelected, dalMin, dalMax]); const updateAttributes = async () => { try { // 查询当前自定义属性值 const attributeRes = await query(`{ checkout { customAttributes { key value } } }`); const currentAttributes = attributeRes.data.checkout.customAttributes.reduce((acc, attr) => { acc[attr.key] = attr.value; return acc; }, {}); const newDalMin = dalMin.toISOString().slice(0, 10); const newDalMax = dalMax.toISOString().slice(0, 10); // 避免重复更新 if (currentAttributes.dal_min !== newDalMin) { await applyAttributeChange({ type: 'updateAttribute', key: 'dal_min', value: newDalMin, }); } if (currentAttributes.dal_max !== newDalMax) { await applyAttributeChange({ type: 'updateAttribute', key: 'dal_max', value: newDalMax, }); } } catch (err) { console.error('更新属性失败:', err); } }; useEffect(() => { async function fetchSkus() { try { const res = await query(`{ nodes(ids:[${currentLines.map((l) => `"${l.merchandise.id}"`).join(',')}]){ ...on ProductVariant{ sku } } }`); if (res.errors) { const err = new Error('获取SKU失败'); err.errors = res.errors; throw err; } setItems(res.data.nodes); } catch (err) { console.error(err) } } async function fetchDal() { try { await fetchSkus(); // 补充你的API请求逻辑 const res = await fetch(`https://你的API地址`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ skus: items.map(item => item.sku), country: country }) }); const json = await res.json(); setDalMin(new Date(json.dals[0].dalMin)); setDalMax(new Date(json.dals[0].dalMax)); } catch (err) { console.error(err) } } fetchDal(); }, [currentLines, country]); // 依赖变化时重新获取数据 if (dalMin && dalMax) { return ( <Text size='small' appearance='subdued'> {translate('delai', { dalMin: i18n.formatDate(dalMin, { dateStyle: 'short' }), dalMax: i18n.formatDate(dalMax, { dateStyle: 'short' }) })} </Text> ); } return null; // 数据未加载时返回null,避免空渲染 }
备注:内容来源于stack exchange,提问作者Kenyuki
相关产品推荐
相关产品推荐

