关于 faststore-state-management
faststore-state-management 是面向 FastStore 电商交互开发的状态管理技能,指导开发者使用 @faststore/sdk 管理购物车、会话和搜索。它强调平台同步、购物车验证以及搜索状态与 URL 的联动。
主要功能
- 01使用 useCart 管理购物车操作
- 02使用 useSession 管理会话数据
- 03使用 useSearch 管理搜索状态
- 04结账前处理购物车验证状态
- 05使用 SDK 发送电商分析事件
适用场景
- 01添加、移除或更新购物车商品
- 02更改货币、语言或销售渠道
- 03实现筛选、排序与分页
- 04阻止验证中的购物车进入结账
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill faststore-state-management安装后在 Claude Code 中输入/faststore-state-management确认已加载。
详细介绍
FastStore SDK 状态管理
适用场景
在以下情况下使用此技能:
- 构建涉及购物车、用户会话、商品搜索/筛选或分析跟踪的交互式电商功能。
- 需要添加、移除或更新购物车商品。
- 需要读取或更改会话数据(货币、区域设置、销售渠道、邮政编码)。
- 需要管理分面搜索状态(排序顺序、选中的分面、分页)。
- 使用
@faststore/sdk钩子(useCart、useSession、useSearch)。
以下情况不要使用此技能:
- 仅更改视觉效果——请使用
faststore-theming技能。 - 替换或自定义原生组件——请使用
faststore-overrides技能。 - 扩展 GraphQL 架构或获取自定义数据——请使用
faststore-data-fetching技能。
决策规则
- 所有购物车操作都使用
useCart()——它会自动处理平台验证、价格核验和分析。 - 所有会话数据都使用
useSession()——它会触发validateSessionmutations,使平台保持同步。 - 所有搜索状态都在
SearchProvider中使用useSearch()——它会与 URL 参数同步,并支持浏览器后退按钮导航。 - 不要为购物车、会话或搜索构建自定义状态管理(React Context、Redux、Zustand、
useState/useReducer)。SDK 钩子已接入 FastStore 平台集成层。 - 始终检查
useCart()中的isValidating,并在验证期间阻止跳转到结账页面。 - 使用 SDK 中的
sendAnalyticsEvent()跟踪兼容 GA4 的电商事件。
硬性约束
约束:购物车、会话和搜索状态必须使用 @faststore/sdk
管理购物车、会话和搜索状态时,必须使用 @faststore/sdk 钩子(useCart、useSession、useSearch)。不得为这些领域构建自定义状态管理(React Context、Redux、Zustand、useState/useReducer)。
为什么这很重要
SDK 钩子已接入 FastStore 平台集成层。useCart() 会触发购物车验证 mutations。useSession() 会将区域设置/货币变更传递给所有数据查询。useSearch() 会与 URL 参数同步,并触发搜索重新获取。自定义状态会绕过所有这些机制——购物车不会经过验证,价格可能过时,搜索不会与 URL 同步,分析事件也不会触发。
检测方式
如果看到 useState 或 useReducer 用于管理购物车商品、购物车总额、会话区域设置、会话货币或搜索分面 → 停止。这些状态应使用来自 @faststore/sdk 的 useCart()、useSession() 或 useSearch() 管理。如果看到名称类似 CartContext、SessionContext 或 SearchContext 的 createContext → 停止。SDK 已提供这些上下文。
正确
// src/components/MiniCart.tsx
import React from 'react'
import { useCart } from '@faststore/sdk'
export default function MiniCart() {
const { items, totalItems, isValidating, removeItem } = useCart()
if (totalItems === 0) {
return <p>Your cart is empty</p>
}
return (
<div data-fs-mini-cart>
<h3>Cart ({totalItems} items)</h3>
{isValidating && <span>Updating cart...</span>}
<ul>
{items.map((item) => (
<li key={item.id}>
<span>{item.itemOffered.name}</span>
<span>${item.price}</span>
<button onClick={() => removeItem(item.id)}>Remove</button>
</li>
))}
</ul>
</div>
)
}
错误
// WRONG: Building a custom cart context instead of using @faststore/sdk
import React, { createContext, useContext, useReducer } from 'react'
interface CartItem {
id: string
name: string
price: number
quantity: number
}
// This custom context duplicates what @faststore/sdk already provides.
// Cart changes here will NOT trigger platform validation.
// Prices and availability will NOT be verified against VTEX.
// Analytics events will NOT fire for add-to-cart actions.
const CartContext = createContext<{
items: CartItem[]
dispatch: React.Dispatch<any>
}>({ items: [], dispatch: () => {} })
function cartReducer(state: CartItem[], action: any) {
switch (action.type) {
case 'ADD':
return [...state, action.payload]
case 'REMOVE':
return state.filter((item) => item.id !== action.payload)
default:
return state
}
}
export function CartProvider({ children }: { children: React.ReactNode }) {
const [items, dispatch] = useReducer(cartReducer, [])
return (
<CartContext.Provider value={{ items, dispatch }}>
{children}
</CartContext.Provider>
)
}
约束:始终处理购物车验证状态
必须检查 useCart() 中的 isValidating 标志,并在购物车验证期间显示适当的加载状态。当 isValidating 为 true 时,不得允许跳转到结账页面。
为什么这很重要
购物车验证是一项异步操作,会针对 VTEX 平台检查商品的当前价格、库存情况和适用促销。如果用户在验证期间继续结账,可能会看到过时的价格或遇到错误。isValidating 标志正是为防止这种情况而设置的。
检测方式
如果看到 useCart() 的解构结果中没有 isValidating,而组件包含结账链接或“继续结账”按钮 → 提示未处理验证状态。如果看到结账链接或按钮在跳转前没有检查 isValidating → 提示可能会使用过时的购物车数据。
正确
// src/components/CartSummary.tsx
import React from 'react'
import { useCart } from '@faststore/sdk'
export default function CartSummary() {
const { items, totalItems, isValidating } = useCart()
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
return (
<div data-fs-cart-summary>
<p>{totalItems} item{totalItems !== 1 ? 's' : ''} in your cart</p>
<p>Subtotal: ${subtotal.toFixed(2)}</p>
{isValidating && (
<p data-fs-cart-validating>Verifying prices and availability...</p>
)}
<a
href="/checkout"
data-fs-checkout-button
aria-disabled={isValidating}
onClick={(e) => {
if (isValidating) {
e.preventDefault()
}
}}
>
{isValidating ? 'Updating cart...' : 'Proceed to Checkout'}
</a>
</div>
)
}
错误
// WRONG: Ignoring cart validation state
import React from 'react'
import { useCart } from '@faststore/sdk'
export default function CartSummary() {
const { items, totalItems } = useCart()
// Missing isValidating — user can click checkout while cart is being validated.
// This can lead to price mismatches at checkout or failed orders.
return (
<div>
<p>{totalItems} items</p>
<a href="/checkout">Proceed to Checkout</a>
{/* No loading state. No validation check. User may proceed with stale prices. */}
</div>
)
}
限制:请勿将会话数据存储在 localStorage 中
必须使用 @faststore/sdk 中的 useSession() 访问会话数据(货币、语言区域、渠道、个人信息)。不得直接读取或写入 localStorage 或 sessionStorage 中的会话数据。
为何这很重要
SDK 的会话模块负责管理与 VTEX 平台的同步。会话数据发生变化时,SDK 会触发 validateSession mutation,以更新服务端会话并重新验证购物车。直接写入 localStorage 会绕过此验证——平台不会获知此项变更,价格可能会以错误的货币显示,购物车商品也可能无法反映正确的销售渠道。
检测
如果看到使用 localStorage.getItem 或 localStorage.setItem 访问与会话数据相关的键(货币、语言区域、渠道、地区、邮政编码)→ 停止。这些数据应通过 useSession() 管理。如果看到将 sessionStorage 用于相同目的 → 停止。
正确
// src/components/LocaleSwitcher.tsx
import React from 'react'
import { useSession } from '@faststore/sdk'
export default function LocaleSwitcher() {
const { locale, currency, setSession } = useSession()
const handleLocaleChange = (newLocale: string, newCurrency: string) => {
// setSession triggers platform validation and re-fetches data
setSession({
locale: newLocale,
currency: { code: newCurrency, symbol: newCurrency === 'USD' ? '$' : 'R$' },
})
}
return (
<div data-fs-locale-switcher>
<button
onClick={() => handleLocaleChange('en-US', 'USD')}
aria-pressed={locale === 'en-US'}
>
EN
</button>
<button
onClick={() => handleLocaleChange('pt-BR', 'BRL')}
aria-pressed={locale === 'pt-BR'}
>
PT
</button>
<span>Current: {locale} ({currency.code})</span>
</div>
)
}
错误
// WRONG: Managing session data manually via localStorage
import React, { useState, useEffect } from 'react'
export default function LocaleSwitcher() {
const [locale, setLocale] = useState('en-US')
useEffect(() => {
// WRONG: Reading session data from localStorage
const saved = localStorage.getItem('store-locale')
if (saved) setLocale(saved)
}, [])
const handleLocaleChange = (newLocale: string) => {
// WRONG: Writing session data to localStorage
// The VTEX platform does NOT know about this change.
// Product prices, availability, and cart will NOT update.
localStorage.setItem('store-locale', newLocale)
setLocale(newLocale)
}
return (
<div>
<button onClick={() => handleLocaleChange('en-US')}>EN</button>
<button onClick={() => handleLocaleChange('pt-BR')}>PT</button>
</div>
)
}
首选模式
建议同时使用以下 SDK Hooks:
// src/components/CartDrawer.tsx
import React from 'react'
import { useCart } from '@faststore/sdk'
import { useSession } from '@faststore/sdk'
import { Button, Loader } from '@faststore/ui'
export default function CartDrawer() {
const { items, totalItems, isValidating, removeItem, updateItemQuantity } =
useCart()
const { currency, locale } = useSession()
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency.code,
})
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
if (totalItems === 0) {
return (
<div data-fs-cart-drawer>
<h2>Your Cart</h2>
<p>Your cart is empty. Start shopping to add items.</p>
</div>
)
}
return (
<div data-fs-cart-drawer>
<h2>Your Cart ({totalItems} items)</h2>
{isValidating && (
<div data-fs-cart-loading>
<Loader />
<span>Verifying prices and availability...</span>
</div>
)}
<ul data-fs-cart-items>
{items.map((item) => (
<li key={item.id} data-fs-cart-item>
<span data-fs-cart-item-name>{item.itemOffered.name}</span>
<span data-fs-cart-item-price>{formatter.format(item.price)}</span>
<div data-fs-cart-item-quantity>
<Button
variant="tertiary"
onClick={() => updateItemQuantity(item.id, item.quantity - 1)}
disabled={item.quantity <= 1}
>
-
</Button>
<span>{item.quantity}</span>
<Button
variant="tertiary"
onClick={() => updateItemQuantity(item.id, item.quantity + 1)}
>
+
</Button>
</div>
<Button variant="tertiary" onClick={() => removeItem(item.id)}>
Remove
</Button>
</li>
))}
</ul>
<div data-fs-cart-summary>
<span>Subtotal: {formatter.format(subtotal)}</span>
<a
href="/checkout"
data-fs-checkout-button
aria-disabled={isValidating}
onClick={(e) => {
if (isValidating) e.preventDefault()
}}
>
{isValidating ? 'Updating cart...' : 'Proceed to Checkout'}
</a>
</div>
</div>
)
}
使用 useSearch() 管理搜索状态:
// src/components/FacetFilter.tsx
import { useSearch } from '@faststore/sdk'
export default function FacetFilter() {
const { state, setState } = useSearch()
const toggleFacet = (facetKey: string, facetValue: string) => {
const currentFacets = state.selectedFacets || []
const exists = currentFacets.some(
(f) => f.key === facetKey && f.value === facetValue
)
const newFacets = exists
? currentFacets.filter(
(f) => !(f.key === facetKey && f.value === facetValue)
)
: [...currentFacets, { key: facetKey, value: facetValue }]
setState({
...state,
selectedFacets: newFacets,
page: 0, // Reset pagination when filters change
})
}
return (
<div data-fs-facet-filter>
<button onClick={() => toggleFacet('brand', 'Nike')}>
Nike {state.selectedFacets?.some((f) => f.key === 'brand' && f.value === 'Nike') ? '✓' : ''}
</button>
</div>
)
}
常见失效模式
- 为购物车状态创建自定义 React Context(
CartContext、useReducer)——这会导致购物车与 VTEX 平台验证、分析和结账流程脱节。 - 将会话数据(语言区域、货币、邮政编码)存储在
localStorage中——SDK 的validateSessionmutation 不会触发,导致平台状态不同步。 - 使用
useState构建自定义搜索状态——这会失去 URL 同步,破坏浏览器后退按钮导航,并绕过 SDK 优化的查询生成。 - 忽略
useCart()返回的isValidating标志——用户可能会在价格过时或商品缺货时继续结账。 - 使用
useCart_unstable或useSession_unstableHooks,却不了解它们的接口不稳定,可能会发生变化。
审核清单
- 是否仅通过
@faststore/sdk中的useCart()管理购物车状态? - 是否仅通过
@faststore/sdk中的useSession()访问会话数据? - 是否在
SearchProvider上下文中通过useSearch()管理搜索状态? - 是否在允许导航至结账前检查
isValidating标志? - 是否未使用自定义 React Context、Redux 或 Zustand Store 重复存储 SDK 状态?
- 是否未直接通过
localStorage/sessionStorage访问会话相关数据?
参考
- FastStore SDK 概览 — 介绍 SDK 模块及其职责
- useCart Hook — 购物车 Hook 的 API 参考,包含所有属性和函数
- 购物车模块概览 — 购物车数据结构、验证和平台集成
- 会话模块 — 会话数据结构,以及货币、语言区域和渠道管理
- useSearch Hook — 搜索 Hook 的 API 参考,包含排序、筛选条件和分页
- SearchProvider —
useSearch正常运行所需的上下文提供程序 - 分析模块 — 兼容 GA4 的分析事件跟踪
- 实验性 Hooks 和组件 — 适用于高级用例的不稳定 Hooks(useCart_unstable、useSession_unstable)
faststore-data-fetching— 相关技能:通过 GraphQL API 获取商品数据