关于 vtex-io-react-apps
面向使用 React builder 构建 VTEX IO 前端应用的开发者技能,涵盖 Store Framework 区块、Site Editor 模式、样式和数据获取,并提供组件结构检查规则。
主要功能
- 01用 interfaces.json 映射区块
- 02通过 contentSchemas.json 暴露属性
- 03用 css-handles 设置店面样式
- 04管理界面采用 VTEX Styleguide
- 05GraphQL 数据获取与国际化指导
- 06组件导出和检查清单
适用场景
- 01构建自定义 Store Framework 店面区块
- 02让组件属性可在 Site Editor 编辑
- 03开发 VTEX Admin 管理面板界面
- 04检查区块注册和组件导出
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill vtex-io-react-apps安装后在 Claude Code 中输入/vtex-io-react-apps确认已加载。
详细介绍
前端 React 组件与 Hooks
此技能的适用场景
使用 react builder 构建 VTEX IO 前端应用时,请使用此技能:创建与 Store Framework 集成为主题区块的 React 组件、配置 interfaces.json、设置供 Site Editor 使用的 contentSchemas.json,以及应用样式模式。
- 创建自定义店面组件(商品展示、表单、横幅)
- 使用 VTEX Styleguide 构建管理面板界面
- 将组件注册为 Store Framework 区块
- 通过
contentSchemas.json在 Site Editor 中公开组件属性 - 使用
css-handles实现安全的店面样式设置
以下场景请勿使用此技能:
- 实现后端服务(请改用
vtex-io-service-apps) - 开发 GraphQL 架构和解析器(请改用
vtex-io-graphql-api) - 配置 Manifest 和 builder(请改用
vtex-io-app-structure)
决策规则
- 每个可见的店面元素都是一个区块。区块在主题 JSON 中声明,并通过接口映射到 React 组件。
/store中的interfaces.json将区块名称映射到 React 组件文件:"component"是/react中的文件名(不含扩展名),"allowed"列出子区块,"composition"控制子区块的使用方式("children"或"blocks")。- 每个导出的组件都必须在
/react中有一个根级文件重新导出该组件。Builder 会将"component": "ProductReviews"解析为react/ProductReviews.tsx。 - 对于店面组件,请使用
vtex.css-handles设置样式(不要使用内联样式或全局 CSS)。 - 对于管理端组件,请使用官方 VTEX Admin 组件库
vtex.styleguide。不要使用第三方 UI 库。 - 在
/store中使用contentSchemas.json,以便在 Site Editor 中编辑组件属性(JSON Schema 格式)。商家编辑内容由vtex.pages-graphql存储在包含声明应用主版本号的键下([email protected]:template)。声明应用的主版本升级后,解析器将无法读取这些编辑内容,除非使用[email protected]中的updateThemeIdsmutation 将其迁移到新的主版本;请参阅vtex-io-storefront-theme-versioning。 - 使用
react-intl和messagesbuilder 实现 i18n,切勿将面向用户的字符串硬编码。 - 通过 GraphQL 查询获取数据(使用
react-apollo中的useQuery),切勿从浏览器直接调用 API。
架构:
Store Theme (JSON blocks)
└── declares "product-reviews" block with props
│
▼
interfaces.json → maps "product-reviews" to "ProductReviews" component
│
▼
react/ProductReviews.tsx → React component renders
│
├── useCssHandles() → CSS classes for styling
├── useQuery() → GraphQL data fetching
└── useProduct() / useOrderForm() → Store Framework context hooks
硬性约束
约束:为所有店面区块声明接口
每个应作为 Store Framework 区块使用的 React 组件,都必须在 store/interfaces.json 中有对应条目。没有接口声明,就无法在主题 JSON 文件中引用该区块。
为何重要
Store builder 通过 interfaces.json 将区块名称解析为 React 组件。如果组件没有接口,就不会被 Store Framework 识别,也不会在店面中渲染。
检测
如果 /react 中的 React 组件计划用于店面,却没有在 store/interfaces.json 中找到对应条目,请提醒开发者。该组件可以编译,但永远不会渲染。
正确
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-item"]
},
"product-review-item": {
"component": "ReviewItem"
}
}
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews
错误
// react/ProductReviews.tsx exists but NO store/interfaces.json entry
// The component compiles fine but cannot be used in any theme.
// Adding <product-reviews /> in a theme JSON will produce:
// "Block 'product-reviews' not found"
import ProductReviews from './components/ProductReviews'
export default ProductReviews
约束:管理端 UI 使用 VTEX Styleguide
管理面板组件(使用 admin builder 的应用)必须使用 VTEX Styleguide(vtex.styleguide)提供 UI 元素。管理端应用不得使用 Material UI、Chakra UI 或 Ant Design 等第三方 UI 库。
为何重要
VTEX Admin 通过 Styleguide 统一设计语言。第三方 UI 库会导致视觉风格不一致,可能与 Admin 的全局 CSS 冲突,并增加不必要的打包体积。提交到 VTEX App Store 的应用若管理端 UI 未使用 Styleguide,将无法通过审核。
检测
如果在管理端应用中发现从 @material-ui、@chakra-ui/react、@chakra-ui、antd 或 @ant-design 导入内容,请提醒开发者改用 vtex.styleguide。
正确
// react/admin/ReviewModeration.tsx
import React, { useState } from 'react'
import {
Layout,
PageHeader,
Table,
Button,
Tag,
Modal,
Input,
} from 'vtex.styleguide'
interface Review {
id: string
author: string
rating: number
text: string
status: 'pending' | 'approved' | 'rejected'
}
function ReviewModeration() {
const [reviews, setReviews] = useState<Review[]>([])
const [modalOpen, setModalOpen] = useState(false)
const tableSchema = {
properties: {
author: { title: 'Author', width: 200 },
rating: { title: 'Rating', width: 100 },
text: { title: 'Review Text' },
status: {
title: 'Status',
width: 150,
cellRenderer: ({ cellData }: { cellData: string }) => (
<Tag type={cellData === 'approved' ? 'success' : 'error'}>
{cellData}
</Tag>
),
},
},
}
return (
<Layout fullWidth pageHeader={<PageHeader title="Review Moderation" />}>
<Table
items={reviews}
schema={tableSchema}
density="medium"
/>
</Layout>
)
}
export default ReviewModeration
错误
// react/admin/ReviewModeration.tsx
import React from 'react'
import { DataGrid } from '@material-ui/data-grid'
import { Button } from '@material-ui/core'
// Material UI components will look inconsistent in the VTEX Admin,
// conflict with global styles, and inflate bundle size.
// This app will fail VTEX App Store review.
function ReviewModeration() {
return (
<div>
<DataGrid rows={[]} columns={[]} />
<Button variant="contained" color="primary">Approve</Button>
</div>
)
}
约束:从 react/ 根目录导出组件
每个 Store Framework 区块组件都必须在 /react 目录中有一个根级导出文件,且文件名与 interfaces.json 中的 component 值一致。实际实现可以放在子目录中,但根级文件必须存在。
重要原因
react 构建器会在 /react 根目录中查找文件,以解析组件。如果 interfaces.json 声明 "component": "ProductReviews",构建器就会查找 react/ProductReviews.tsx。如果缺少这个根级导出文件,就找不到组件,区块也会渲染失败。
检测
如果 interfaces.json 引用了某个组件名称,但 /react 根目录中没有对应文件,请立即停止并创建导出文件。
正确
// react/ProductReviews.tsx — root-level export file
import ProductReviews from './components/ProductReviews/index'
export default ProductReviews
// react/components/ProductReviews/index.tsx — actual implementation
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
const CSS_HANDLES = ['container', 'title', 'list'] as const
interface Props {
title: string
maxReviews: number
}
function ProductReviews({ title, maxReviews }: Props) {
const handles = useCssHandles(CSS_HANDLES)
return (
<div className={handles.container}>
<h2 className={handles.title}>{title}</h2>
{/* ... */}
</div>
)
}
export default ProductReviews
错误
react/components/ProductReviews/index.tsx exists but
react/ProductReviews.tsx does NOT exist.
The builder cannot find the component.
Error: "Could not find component ProductReviews"
首选模式
在子目录中创建 React 组件:
// react/components/ProductReviews/index.tsx
import React, { useMemo } from 'react'
import { useQuery } from 'react-apollo'
import { useProduct } from 'vtex.product-context'
import { useCssHandles } from 'vtex.css-handles'
import GET_REVIEWS from '../../graphql/getReviews.graphql'
import ReviewItem from './ReviewItem'
const CSS_HANDLES = [
'reviewsContainer',
'reviewsTitle',
'reviewsList',
'averageRating',
'emptyState',
] as const
interface Props {
title?: string
showAverage?: boolean
maxReviews?: number
}
function ProductReviews({
title = 'Customer Reviews',
showAverage = true,
maxReviews = 10,
}: Props) {
const handles = useCssHandles(CSS_HANDLES)
const productContext = useProduct()
const productId = productContext?.product?.productId
const { data, loading, error } = useQuery(GET_REVIEWS, {
variables: { productId, limit: maxReviews },
skip: !productId,
})
const averageRating = useMemo(() => {
if (!data?.reviews?.length) return 0
const sum = data.reviews.reduce(
(acc: number, review: { rating: number }) => acc + review.rating,
0
)
return (sum / data.reviews.length).toFixed(1)
}, [data])
if (loading) return <div className={handles.reviewsContainer}>Loading...</div>
if (error) return null
return (
<div className={handles.reviewsContainer}>
<h2 className={handles.reviewsTitle}>{title}</h2>
{showAverage && data?.reviews?.length > 0 && (
<div className={handles.averageRating}>
Average: {averageRating} / 5
</div>
)}
{data?.reviews?.length === 0 ? (
<p className={handles.emptyState}>No reviews yet.</p>
) : (
<ul className={handles.reviewsList}>
{data.reviews.map((review: { id: string; author: string; rating: number; text: string }) => (
<ReviewItem key={review.id} review={review} />
))}
</ul>
)}
</div>
)
}
export default ProductReviews
根级导出文件:
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews
区块接口:
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-form"],
"render": "client"
}
}
Site Editor 架构:
{
"definitions": {
"ProductReviews": {
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Section Title",
"description": "Title displayed above the reviews list",
"default": "Customer Reviews"
},
"showAverage": {
"type": "boolean",
"title": "Show average rating",
"default": true
},
"maxReviews": {
"type": "number",
"title": "Maximum reviews",
"default": 10,
"enum": [5, 10, 20, 50]
}
}
}
}
}
在 Store Framework 主题中使用该组件:
{
"store.product": {
"children": [
"product-images",
"product-name",
"product-price",
"buy-button",
"product-reviews"
]
},
"product-reviews": {
"props": {
"title": "What Our Customers Say",
"showAverage": true,
"maxReviews": 20
}
}
}
常见失败模式
- 为管理应用导入第三方 UI 库:使用
@material-ui/core、@chakra-ui/react或antd会与 VTEX Admin 的全局 CSS 冲突,导致视觉效果不一致,并且无法通过 App Store 审核。请改用vtex.styleguide。 - 在 React 组件中直接调用 API:使用
fetch()或axios会将身份验证令牌暴露给客户端,并绕过 CORS 限制。请使用通过react-apollo的useQuery在服务器端解析的 GraphQL 查询。 - 硬编码字符串且未使用 i18n:包含硬编码字符串的组件只能在一种语言下使用。请使用
messages构建器和react-intl进行国际化。 - 缺少根级导出文件:如果
interfaces.json引用了"component": "ProductReviews",但react/ProductReviews.tsx不存在,该区块会静默地渲染失败。 - 内容承载型组件应用升级主版本:对包含
store/contentSchemas.json的应用运行vtex release major,会导致所有曾针对该应用声明的区块保存过的 Site Editor 编辑内容,在迁移到新主版本并使用[email protected]的updateThemeIdsmutation 之前,对解析器不可见。请尽可能使用patch或minor;如果无法避免升级主版本,请遵循vtex-io-storefront-theme-versioning。
审核清单
- 每个店面区块是否都在
store/interfaces.json中有对应条目? - 每个
interfaces.json组件是否都在/react中有根级导出文件? - 管理应用是否使用
vtex.styleguide(没有使用第三方 UI 库)? - 店面组件是否使用
css-handles进行样式设置? - 数据是否通过 GraphQL(
useQuery)获取,而非直接调用 API? - 面向用户的字符串是否使用
react-intl和messages构建器? - 是否为可在 Site Editor 中编辑的属性定义了
contentSchemas.json? - 如果应用包含
store/contentSchemas.json,是否已评估计划中的主版本升级对商家的影响?
相关技能
vtex-io-storefront-theme-versioning— 当应用包含store/contentSchemas.json,且版本变更必须保留或迁移 Site Editor 内容时使用。vtex-io-storefront-theme-app— 当问题涉及消费主题如何将这些区块组合成页面时使用。
参考资料
- 开发自定义店面组件 — 构建 Store Framework 组件的指南
- 接口 — 接口如何将区块映射到 React 组件
- React Builder — React builder 配置和目录结构
- 在 Site Editor 中使用自定义组件 — contentSchemas.json 和 Site Editor 集成
- Store Framework — 基于区块的店面系统概述
- 使用组件 — 如何在主题中使用原生和自定义组件
- VTEX Styleguide — VTEX Admin UI 的官方组件库