- 起步
- 核心概念
- 教程
- 基础内容
- 内置功能
- 集成
- 操作指南
- 使用指南
- 配置
- 参考
- 社区资源
- 其他
- 为文档做出贡献
- 手动安装 Astro
- 升级到 Astro v4
- 升级到 Astro v3
- 升级到 Astro v2
- Legacy v0.x Upgrade Guide
- 从 Create React App(CRA)迁移
- 关于本教程
- 第一单元:前期准备
- 准备开发环境
- 创建你的第一个 Astro 项目
- 你的第一行 Astro 代码
- 创建在线代码仓库
- 将你的网站部署到网络
- 第二单元:页面
- 创建你的第一个 Astro 页面
- 编写你的第一篇 Markdown 博客文章
- 添加「关于你」的动态内容
- 给你的「关于」页面添加样式
- 应用全局样式
- 第三单元:组件
- 创建一个可重复使用的导航组件
- 创建社交媒体 footer
- 自己动手搭建导航 Header
- 编写你的第一个浏览器脚本
- 第四单元:布局
- 编写你的第一个布局
- 创建并传递数据到博客布局
- 布局结合,两全其美
- 第五单元 - Astro API
- 创建文章存档页
- 生成标签页面
- 编写标签索引页面
- 添加 RSS 订阅源
- 第六单元 - Astro 群岛
- 搭建你的第一个 Astro 岛屿
- 回到干燥的陆地。让你的博客从白天到黑夜,无需岛屿!
- 恭喜你!
- @astrojs/mdx
- 添加阅读时间
- 创建开发者工具栏应用
- @astrojs/alpinejs
- @astrojs/lit
- @astrojs/preact
- @astrojs/react
- @astrojs/solid-js
- @astrojs/svelte
- @astrojs/vue
- @astrojs/cloudflare
- @astrojs/netlify
- @astrojs/node
- @astrojs/vercel
- @astrojs/db
- @astrojs/markdoc
- @astrojs/partytown
- @astrojs/sitemap
- @astrojs/tailwind
- 使用流式处理来提升页面性能
- 从 Gatsby 迁移
- 从 Next.js 迁移
- 从 NuxtJS 迁移
- 从 Docusaurus 迁移到 Astro
- 从 Eleventy 迁移到 Astro
- 从 GitBook 迁移到 Astro
- 从Gridsome迁移到Astro
- 从 Hugo 迁移
- 从 Jekyll 迁移
- 从 Pelican 迁移
- 从 SvelteKit 迁移
- 从VuePress迁移
- 从 WordPress 迁移
- 状态共享
- Storyblok 与 Astro
- Contentful 与 Astro
- ButterCMS & Astro
- Builder.io & Astro
- Cosmic 与 Astro
- DatoCMS 与 Astro
- Front Matter CMS 与 Astro
- Ghost & Astro
- Hashnode & Astro
- Hygraph 与 Astro
- Keystatic & Astro
- Kontent.ai 与 Astro
- 无头模式的 Statamic 与 Astro
- Strapi 与 Astro
- Tina CMS 与 Astro
- Umbraco & Astro
- 无头(headless)模式的 WordPress 与 Astro
- ApostropheCMS 与 Astro
- Caisy & Astro
- CloudCannon与Astro
- Crystallize 与 Astro
- Decap CMS 与 Astro
- Directus 与 Astro
- KeystoneJS 与 Astro
- microCMS & Astro
- Payload CMS 与 Astro
- Prepr CMS & Astro
- Prismic & Astro
- Sanity & Astro
- Sitecore 体验管理器 & Astro
- Spinal & Astro
- 创作内容
- Firebase 与 Astro
- Supabase 与 Astro
- Turso 和 Astro
- Xata 与 Astro
- Appwrite & Astro
- 部署你的 Astro 站点至 Netlify
- 部署你的 Astro 站点至 Vercel
- 如何将你的 Astro 网站部署到 Deno
- 部署你的 Astro 站点至 GitHub Pages
- 部署你的 Astro 站点至 GitLab Pages
- 部署你的 Astro 站点至 Cloudflare Pages
- 将你的 Astro 网站部署到 AWS
- 将你的 Astro 网站通过 Flightcontrol 部署到 AWS
- 将你的 Astro 网站通过 SST 部署到 AWS
- 将你的 Astro 网站部署至 Google Cloud
- 部署你的 Astro 站点至 Google Firebase 托管
- 将你的 Astro 网站部署到 Heroku
- 将你的 Astro 网站部署到 Microsoft Azure
- 部署你的 Astro 站点至 Buddy
- 将你的 Astro 网站部署到 Edgio
- 将你的 Astro 站点部署到 Fly.io
- 将你的 Astro 网站部署到 Render
- 将你的 Astro 网站部署到 Stormkit
- 将你的 Astro 网站部署到 Surge
- 通过 Cleavr 部署你的 Astro 网站
- 将你的 Astro 站点部署到 Kinsta 应用托管
- 将你的 Astro 网站部署到 Space
- 将你的 Astro 站点部署到 Zeabur
- 将你的 Astro 网站部署到 Zerops
- 添加 RSS 摘要
- 安装一个 Vite 或 Rollup 插件
- 构建自定义图像组件
- 使用 API 路由构建表单
- 在 Astro 页面中构建 HTML 表单
- 在 Astro 中使用 Bun
- 调用服务器端点
- 校验验证码
- 用 Docker 来构建你的 Astro 网站
- 动态导入图片
- 为链接添加图标
- 添加 i18n 功能
- 添加最后修改时间
- 在 Astro 组件中共享状态
- 使用 Tailwind Typography 美化渲染后的 Markdown
- Unknown compiler error.
- Astro.redirect is not available in static mode.
- Astro.clientAddress is not available in current adapter.
- Astro.clientAddress cannot be used inside prerendered routes.
- Astro.clientAddress is not available in static mode.
- No static path found for requested path.
- Invalid type returned by Astro page.
- Missing value for client:media directive.
- No matching renderer found.
- No client entrypoint specified in renderer.
- Missing hint on client:only directive.
- Invalid value returned by a getStaticPaths path.
- Invalid entry inside getStaticPath's return value
- Invalid value returned by getStaticPaths.
- getStaticPaths RSS helper is not available anymore.
- Missing params property on getStaticPaths route.
- Invalid value for getStaticPaths route parameter.
- getStaticPaths() function required for dynamic routes.
- Invalid slot name.
- Cannot use Server-side Rendering without an adapter.
- No import found for component.
- Invalid prerender export.
- Invalid component arguments.
- Page number param not found.
- Image missing required "alt" property.
- Error while loading image service.
- Missing image dimensions
- Failed to retrieve remote image dimensions
- Unsupported image format
- Unsupported image conversion
- Prerendered dynamic endpoint has path collision.
- Expected src to be an image.
- Expected image options.
- Cannot set both densities and widths
- Image not found.
- Could not process image metadata.
- Image not found.
- Could not transform image.
- Unable to set response.
- The middleware didn't return a Response.
- The middleware returned something that is not a Response object.
- The endpoint did not return a Response.
- Value assigned to locals is not accepted.
- Astro.response.headers must not be reassigned.
- Can't load the middleware.
- Local images must be imported.
- Astro.glob() used outside of an Astro file.
- Astro.glob() did not match any files.
- A redirect must be given a location with the Location header.
- Invalid dynamic route.
- Could not find Sharp.
- Unknown Vite Error.
- Could not import file.
- Invalid glob pattern.
- Astro couldn't find the correct page to render
- The provided locale does not exist.
- Index page not found.
- You can't use the current function with the current strategy
- Prerendered routes aren't supported when internationalization domains are enabled.
- 启用了手动国际化路由但没有提供中间件。
- Astro can't render the route.
- Unhandled rejection
- i18n Not Enabled
- Route not found.
- Unknown CSS Error
- CSS Syntax Error
- Unknown Markdown Error.
- Failed to parse Markdown frontmatter.
- Invalid frontmatter injection.
- MDX integration missing.
- Unknown configuration error.
- Specified configuration file not found.
- Legacy configuration detected.
- Unknown CLI Error.
- Failed to generate content types.
- Unknown Content Collection Error.
- Content entry frontmatter does not match schema.
- Invalid content entry slug.
- Content Schema should not contain slug.
- Collection does not exist
- Content and data cannot be in same collection.
- Collection contains entries of a different type.
- Data collection entry failed to parse.
- Duplicate content entry slug.
- Actions must be used with server output.
- Unsupported transform in content config.
状态共享
当使用群岛结构 / 部分激活构建一个 Astro 网站时,你可能会遇到这样的问题:我想在我的组件之间共享状态。
像 React 或者 Vue 这样的 UI 框架可能鼓励使用 “context” 来为其他组件提供上下文信息。但是在 Astro 或者 Markdown 中的 部分激活组件 (partially hydrating components) 不能使用上下文封装。
Astro 推荐了一个不同的客户端共享存储的解决方案: Nano Stores。
相关操作指南: 在 Astro 组件中共享状态为什么使用 Nano Stores?
Nano Stores 库允许你编写任何组件都能与之互动的状态库。我们推荐 Nano Stores,因为:
- 它是轻量级的。 Nano Stores 提供了你所需要的最低限度的 JS(不到 1KB),并且零依赖。
- 它是框架无关的。 这意味着在框架之间共享状态将是无缝的!Astro 是建立在灵活性之上的,所以我们喜欢那些无论你的偏好如何都能提供类似开发者体验的解决方案。
尽管如此,你仍然可以探索一些替代方案。这些方法包括:
- Svelte 的内置 stores
- Solid signals 组件之外的上下文
- Vue 的响应式 API
- 在组件之间 发送自定义浏览器事件
安装 Nano Stores
为你喜欢的 UI 框架安装 Nano Stores 和他们的帮助包:
- Preact
- React
- Solid
- Svelte
- Vue
- Lit
npm i nanostores @nanostores/preact
npm i nanostores @nanostores/react
npm i nanostores @nanostores/solid
npm i nanostores
npm i nanostores @nanostores/vue
npm install nanostores @nanostores/lit
你可以跳转到 Nano Stores 使用指南 或者跟随我们下面的例子!
用例 - 电商购物车抽屉
假如我们正在搭建一个简单的电商页面,有下面三个交互元素:
- 一个 “add to cart” 按钮
- 一个购物车抽屉来显示已添加的商品
- 一个购物车抽屉开关
在你的机器上尝试完整的例子 或者通过 StackBlitz 在线尝试!
你基础的 Astro 文件看起来应该是这样的:
src/pages/index.astro---
import CartFlyoutToggle from '../components/CartFlyoutToggle';
import CartFlyout from '../components/CartFlyout';
import AddToCartForm from '../components/AddToCartForm';
---
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<header>
<nav>
<a href="/">Astro storefront</a>
<CartFlyoutToggle client:load />
</nav>
</header>
<main>
<AddToCartForm client:load>
<!-- ... -->
</AddToCartForm>
</main>
<CartFlyout client:load />
</body>
</html>
使用 “atoms”
让我们在点击购物车抽屉开关(CartFlyoutToggle
)的时候打开购物车抽屉(CartFlyout
)
首先,创建一个新的 JS 或 TS 文件来存放我们的状态库。我们将会使用 “atom” 来做这件事:
src/cartStore.jsimport { atom } from 'nanostores';
export const isCartOpen = atom(false);
现在,我们可以在任意文件中导入这个状态库来进行读写。我们接下来着手开发我们的 CartFlyoutToggle
组件:
- Preact
- React
- Solid
- Svelte
- Vue
- Lit
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';
export default function CartButton() {
// 使用 `useStore` 钩子来读取状态库
const $isCartOpen = useStore(isCartOpen);
// 使用 `.set` 来将数据写入状态库
return (
<button onClick={() => isCartOpen.set(!$isCartOpen)}>Cart</button>
)
}
)}"> src/components/CartFlyoutToggle.jsximport { useStore } from '@nanostores/react';
import { isCartOpen } from '../cartStore';
export default function CartButton() {
// 使用 `useStore` 钩子来读取状态库
const $isCartOpen = useStore(isCartOpen);
// 使用 `.set` 来将数据写入状态库
return (
<button onClick={() => isCartOpen.set(!$isCartOpen)}>Cart</button>
)
}
)}"> src/components/CartFlyoutToggle.jsximport { useStore } from '@nanostores/solid';
import { isCartOpen } from '../cartStore';
export default function CartButton() {
// 使用 `useStore` 钩子来读取状态库
const $isCartOpen = useStore(isCartOpen);
// 使用 `.set` 来将数据写入状态库
return (
<button onClick={() => isCartOpen.set(!$isCartOpen)}>Cart</button>
)
}
)}"> src/components/CartFlyoutToggle.svelte<script>
import { isCartOpen } from '../cartStore';
</script>
<!--使用 "$" 来读取状态库的值-->
<button on:click={() => isCartOpen.set(!$isCartOpen)}>Cart</button>
"> src/components/CartFlyoutToggle.vue<template>
<!--使用 `.set` 来将数据写入状态库-->
<button @click="isCartOpen.set(!$isCartOpen)">Cart</button>
</template>
<script setup>
import { isCartOpen } from '../cartStore';
import { useStore } from '@nanostores/vue';
// 使用 `useStore` 钩子来读取状态库
const $isCartOpen = useStore(isCartOpen);
</script>
"> src/components/CartFlyoutToggle.tsimport { LitElement, html } from 'lit';
import { isCartOpen } from '../cartStore';
export class CartFlyoutToggle extends LitElement {
handleClick() {
isCartOpen.set(!isCartOpen.get());
}
render() {
return html`
<button @click="${this.handleClick}">Cart</button>
`;
}
}
customElements.define('cart-flyout-toggle', CartFlyoutToggle);
`; }}customElements.define('cart-flyout-toggle', CartFlyoutToggle);">然后,我们可以从我们的 CartFlyout
组件中读取 isCartOpen
值:
- Preact
- React
- Solid
- Svelte
- Vue
- Lit
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
return $isCartOpen ? <aside>...</aside> : null;
}
src/components/CartFlyout.jsximport { useStore } from '@nanostores/react';
import { isCartOpen } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
return $isCartOpen ? <aside>...</aside> : null;
}
src/components/CartFlyout.jsximport { useStore } from '@nanostores/solid';
import { isCartOpen } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
return $isCartOpen ? <aside>...</aside> : null;
}
src/components/CartFlyout.svelte<script>
import { isCartOpen } from '../cartStore';
</script>
{#if $isCartOpen}
<aside>...</aside>
{/if}
src/components/CartFlyout.vue<template>
<aside v-if="$isCartOpen">...</aside>
</template>
<script setup>
import { isCartOpen } from '../cartStore';
import { useStore } from '@nanostores/vue';
const $isCartOpen = useStore(isCartOpen);
</script>
src/components/CartFlyout.tsimport { isCartOpen } from '../cartStore';
import { LitElement, html } from 'lit';
import { StoreController } from '@nanostores/lit';
export class CartFlyout extends LitElement {
private cartOpen = new StoreController(this, isCartOpen);
render() {
return this.cartOpen.value ? html`<aside>...</aside>` : null;
}
}
customElements.define('cart-flyout', CartFlyout);
使用 “maps”
现在,让我们来跟踪你购物车里的商品。为了避免重复和跟踪 “数量”,我们可以把你的购物车存储为一个对象,以商品的 ID 为键。我们将使用一个 Map 来做这件事。
让我们在先前的 cartStore.js
中添加一个 cartItem
状态库。如果你愿意的话,你也可以使用 TypeScript 文件来定义。
- JavaScript
- TypeScript
import { atom, map } from 'nanostores';
export const isCartOpen = atom(false);
/**
* @typedef {Object} CartItem
* @property {string} id
* @property {string} name
* @property {string} imageSrc
* @property {number} quantity
*/
/** @type {import('nanostores').MapStore<Record<string, CartItem>>} */
export const cartItems = map({});
src/cartStore.tsimport { atom, map } from 'nanostores';
export const isCartOpen = atom(false);
export type CartItem = {
id: string;
name: string;
imageSrc: string;
quantity: number;
}
export const cartItems = map<Record<string, CartItem>>({});
现在,让我们导出一个 addCartItem
函数供我们的组件使用。
- 如果你的购物车中不存在该商品,添加商品并设置初始数量 1。
- 如果购物车中 已经 存在该商品,则将该商品数量增加 1。
- JavaScript
- TypeScript
...
export function addCartItem({ id, name, imageSrc }) {
const existingEntry = cartItems.get()[id];
if (existingEntry) {
cartItems.setKey(id, {
...existingEntry,
quantity: existingEntry.quantity + 1,
})
} else {
cartItems.setKey(
id,
{ id, name, imageSrc, quantity: 1 }
);
}
}
src/cartStore.ts...
type ItemDisplayInfo = Pick<CartItem, 'id' | 'name' | 'imageSrc'>;
export function addCartItem({ id, name, imageSrc }: ItemDisplayInfo) {
const existingEntry = cartItems.get()[id];
if (existingEntry) {
cartItems.setKey(id, {
...existingEntry,
quantity: existingEntry.quantity + 1,
});
} else {
cartItems.setKey(
id,
{ id, name, imageSrc, quantity: 1 }
);
}
}
有了状态库之后,我们就可以在每次提交表单时调用 AddToCartForm
函数。我们还可以打开购物车抽屉,这样你就可以看到一个完整的购物车概要。
- Preact
- React
- Solid
- Svelte
- Vue
- Lit
import { addCartItem, isCartOpen } from '../cartStore';
export default function AddToCartForm({ children }) {
// we'll hardcode the item info for simplicity!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
return (
<form onSubmit={addToCart}>
{children}
</form>
)
}
src/components/AddToCartForm.jsximport { addCartItem, isCartOpen } from '../cartStore';
export default function AddToCartForm({ children }) {
// we'll hardcode the item info for simplicity!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
return (
<form onSubmit={addToCart}>
{children}
</form>
)
}
src/components/AddToCartForm.jsximport { addCartItem, isCartOpen } from '../cartStore';
export default function AddToCartForm({ children }) {
// we'll hardcode the item info for simplicity!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
return (
<form onSubmit={addToCart}>
{children}
</form>
)
}
src/components/AddToCartForm.svelte<form on:submit|preventDefault={addToCart}>
<slot></slot>
</form>
<script>
import { addCartItem, isCartOpen } from '../cartStore';
// we'll hardcode the item info for simplicity!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart() {
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
</script>
src/components/AddToCartForm.vue<template>
<form @submit="addToCart">
<slot></slot>
</form>
</template>
<script setup>
import { addCartItem, isCartOpen } from '../cartStore';
// we'll hardcode the item info for simplicity!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
</script>
src/components/AddToCartForm.tsimport { LitElement, html } from 'lit';
import { isCartOpen, addCartItem } from '../cartStore';
export class AddToCartForm extends LitElement {
static get properties() {
return {
item: { type: Object },
};
}
constructor() {
super();
this.item = {};
}
addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(this.item);
}
render() {
return html`
<form @submit="${this.addToCart}">
<slot></slot>
</form>
`;
}
}
customElements.define('add-to-cart-form', AddToCartForm);
最后,我们将在 CartFlyout
组件中渲染购物车商品:
- Preact
- React
- Solid
- Svelte
- Vue
- Lit
import { useStore } from '@nanostores/preact';
import { isCartOpen, cartItems } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);
return $isCartOpen ? (
<aside>
{Object.values($cartItems).length ? (
<ul>
{Object.values($cartItems).map(cartItem => (
<li>
<img src={cartItem.imageSrc} alt={cartItem.name} />
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</li>
))}
</ul>
) : <p>Your cart is empty!</p>}
</aside>
) : null;
}
src/components/CartFlyout.jsximport { useStore } from '@nanostores/react';
import { isCartOpen, cartItems } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);
return $isCartOpen ? (
<aside>
{Object.values($cartItems).length ? (
<ul>
{Object.values($cartItems).map(cartItem => (
<li>
<img src={cartItem.imageSrc} alt={cartItem.name} />
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</li>
))}
</ul>
) : <p>Your cart is empty!</p>}
</aside>
) : null;
}
src/components/CartFlyout.jsximport { useStore } from '@nanostores/solid';
import { isCartOpen, cartItems } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);
return $isCartOpen ? (
<aside>
{Object.values($cartItems).length ? (
<ul>
{Object.values($cartItems).map(cartItem => (
<li>
<img src={cartItem.imageSrc} alt={cartItem.name} />
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</li>
))}
</ul>
) : <p>Your cart is empty!</p>}
</aside>
) : null;
}
src/components/CartFlyout.svelte<script>
import { isCartOpen, cartItems } from '../cartStore';
</script>
{#if $isCartOpen}
{#if Object.values($cartItems).length}
<aside>
{#each Object.values($cartItems) as cartItem}
<li>
<img src={cartItem.imageSrc} alt={cartItem.name} />
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</li>
{/each}
</aside>
{:else}
<p>Your cart is empty!</p>
{/if}
{/if}
src/components/CartFlyout.vue<template>
<aside v-if="$isCartOpen">
<ul v-if="Object.values($cartItems).length">
<li v-for="cartItem in Object.values($cartItems)" v-bind:key="cartItem.name">
<img :src=cartItem.imageSrc :alt=cartItem.name />
<h3>{{cartItem.name}}</h3>
<p>Quantity: {{cartItem.quantity}}</p>
</li>
</ul>
<p v-else>Your cart is empty!</p>
</aside>
</template>
<script setup>
import { cartItems, isCartOpen } from '../cartStore';
import { useStore } from '@nanostores/vue';
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);
</script>
src/components/CartFlyout.tsimport { LitElement, html } from 'lit';
import { isCartOpen, cartItems } from '../cartStore';
import { StoreController } from '@nanostores/lit';
export class CartFlyoutLit extends LitElement {
private cartOpen = new StoreController(this, isCartOpen);
private getCartItems = new StoreController(this, cartItems);
renderCartItem(cartItem) {
return html`
<li>
<img src="${cartItem.imageSrc}" alt="${cartItem.name}" />
<h3>${cartItem.name}</h3>
<p>Quantity: ${cartItem.quantity}</p>
</li>
`;
}
render() {
return this.cartOpen.value
? html`
<aside>
${
Object.values(this.getCartItems.value).length
? html`
<ul>
${Object.values(this.getCartItems.value).map((cartItem) =>
this.renderCartItem(cartItem)
)}
</ul>
`
: html`<p>Your cart is empty!</p>`
}
</aside>
`
: null;
}
}
customElements.define('cart-flyout', CartFlyoutLit);
现在,你应该拥有了一个完全交互式的电商示例,并且是宇宙中最小的 JS 包
在你的机器上尝试完整的例子 或者通过 StackBlitz 在线尝试!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论