mcpField
V
vtex-io-react-apps

vtex-io-react-apps

github.com/vtex开发471.6k 次安装2 周前更新
代码仓库

关于 vtex-io-react-apps

面向使用 React builder 构建 VTEX IO 前端应用的开发者技能,涵盖 Store Framework 区块、Site Editor 模式、样式和数据获取,并提供组件结构检查规则。

主要功能

  1. 01用 interfaces.json 映射区块
  2. 02通过 contentSchemas.json 暴露属性
  3. 03用 css-handles 设置店面样式
  4. 04管理界面采用 VTEX Styleguide
  5. 05GraphQL 数据获取与国际化指导
  6. 06组件导出和检查清单

适用场景

  1. 01构建自定义 Store Framework 店面区块
  2. 02让组件属性可在 Site Editor 编辑
  3. 03开发 VTEX Admin 管理面板界面
  4. 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] 中的 updateThemeIds mutation 将其迁移到新的主版本;请参阅 vtex-io-storefront-theme-versioning。
  • 使用 react-intl 和 messages builder 实现 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] 的 updateThemeIds mutation 之前,对解析器不可见。请尽可能使用 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,是否已评估计划中的主版本升级对商家的影响?

相关技能

参考资料

评论

支持 Markdown