模板基础 #

一、模板结构 #

1.1 Astro 组件结构 #

Astro 组件由两个主要部分组成:

astro
---
// Frontmatter(脚本区域)
// JavaScript/TypeScript 代码
const title = "Hello Astro";
---

<!-- 模板区域 -->
<!-- HTML 模板 -->
<h1>{title}</h1>

1.2 Frontmatter 区域 #

Frontmatter 是组件顶部的代码围栏区域:

astro
---
// 导入模块
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';

// 定义变量
const siteName = "My Site";
const year = new Date().getFullYear();

// 定义函数
function formatDate(date) {
  return new Date(date).toLocaleDateString('zh-CN');
}

// 获取数据
const posts = await fetch('/api/posts').then(r => r.json());
---

<Layout title={siteName}>
  <!-- 使用定义的变量和函数 -->
  <p>&copy; {year} {siteName}</p>
  <p>日期: {formatDate(new Date())}</p>
</Layout>

二、文本插值 #

2.1 基本插值 #

使用 {变量} 语法插入动态值:

astro
---
const name = "张三";
const age = 25;
---

<p>姓名: {name}</p>
<p>年龄: {age}</p>

2.2 表达式插值 #

可以在大括号中使用 JavaScript 表达式:

astro
---
const a = 10;
const b = 20;
const items = ['苹果', '香蕉', '橙子'];
---

<p>计算结果: {a + b}</p>
<p>数组长度: {items.length}</p>
<p>当前时间: {new Date().toLocaleString()}</p>
<p>大写: {'hello'.toUpperCase()}</p>

2.3 模板字符串 #

astro
---
const name = "世界";
const count = 42;
---

<p>{`你好,${name}!`}</p>
<p>{`总数: ${count} 个`}</p>

三、属性绑定 #

3.1 基本属性 #

astro
---
const imageUrl = "/images/photo.jpg";
const linkUrl = "https://example.com";
const inputId = "username";
---

<img src={imageUrl} alt="照片" />
<a href={linkUrl}>访问链接</a>
<input id={inputId} type="text" />

3.2 动态属性名 #

astro
---
const attrName = "data-custom";
const attrValue = "value";
---

<div {attrName}={attrValue}></div>
<!-- 等同于 <div data-custom="value"></div> -->

3.3 展开属性 #

astro
---
const attrs = {
  class: "btn primary",
  disabled: true,
  "data-action": "submit"
};
---

<button {...attrs}>按钮</button>
<!-- 等同于 <button class="btn primary" disabled data-action="submit">按钮</button> -->

3.4 布尔属性 #

astro
---
const isDisabled = true;
const isRequired = false;
---

<button disabled={isDisabled}>禁用按钮</button>
<input required={isRequired} />
<input disabled />  <!-- 简写形式 -->

3.5 class 属性 #

astro
---
const isActive = true;
const isPrimary = false;
---

<!-- 使用 class:list 指令 -->
<div class:list={['btn', { active: isActive, primary: isPrimary }]}>
  按钮
</div>
<!-- 输出: <div class="btn active">按钮</div> -->

<!-- 也可以使用普通 class -->
<div class={`btn ${isActive ? 'active' : ''}`}>
  按钮
</div>

四、HTML 注释 #

4.1 模板注释 #

astro
---
const name = "张三";
---

<!-- 这是 HTML 注释,会出现在输出中 -->
<p>{name}</p>

{/* 这是 Astro 注释,不会出现在输出中 */}
<p>{name}</p>

4.2 条件注释 #

astro
---
const showComment = true;
---

{showComment && <!-- 条件显示的注释 -->}

五、Fragment #

5.1 使用 Fragment #

当需要返回多个元素而不添加额外 DOM 节点时:

astro
---
const Fragment = Astro.fragment;
---

<Fragment>
  <h1>标题</h1>
  <p>段落</p>
</Fragment>

<!-- 简写形式 -->
<>
  <h1>标题</h1>
  <p>段落</p>
</>

5.2 条件渲染中使用 #

astro
---
const showContent = true;
---

{showContent && (
  <>
    <h2>标题</h2>
    <p>内容</p>
  </>
)}

六、转义 #

6.1 转义 HTML #

默认情况下,变量内容会被转义:

astro
---
const htmlContent = "<strong>加粗</strong>";
---

<p>{htmlContent}</p>
<!-- 输出: <p>&lt;strong&gt;加粗&lt;/strong&gt;</p> -->

6.2 插入原始 HTML #

使用 set:html 指令插入原始 HTML:

astro
---
const htmlContent = "<strong>加粗</strong>";
---

<p set:html={htmlContent} />
<!-- 输出: <p><strong>加粗</strong></p> -->

6.3 安全警告 #

astro
---
// ⚠️ 危险:不要直接插入用户输入的 HTML
const userInput = "<script>alert('XSS')</script>";
---

<!-- 不安全 -->
<p set:html={userInput} />

<!-- 安全:先清理 HTML -->
import { sanitize } from 'sanitize-html';
const safeHtml = sanitize(userInput);
<p set:html={safeHtml} />

七、模板指令 #

7.1 set:html #

设置元素的 innerHTML:

astro
---
const content = "<em>斜体文字</em>";
---

<div set:html={content} />

7.2 set:text #

设置元素的 textContent:

astro
---
const content = "<em>斜体文字</em>";
---

<div set:text={content} />
<!-- 输出: <div>&lt;em&gt;斜体文字&lt;/em&gt;</div> -->

7.3 is:raw #

将子内容作为原始字符串处理:

astro
---
const code = "<div>Not parsed</div>";
---

<code is:raw>
  {code}
</code>

7.4 is:inline #

保持样式/脚本内联:

astro
<style is:inline>
  /* 这个样式会内联到 HTML 中 */
  .inline-style { color: red; }
</style>

<script is:inline>
  // 这个脚本会内联到 HTML 中
  console.log('inline script');
</script>

八、动态标签 #

8.1 动态元素类型 #

astro
---
const Tag = 'h1';
---

<Tag>这是标题</Tag>
<!-- 输出: <h1>这是标题</h1> -->

8.2 条件标签 #

astro
---
const isLarge = true;
const Heading = isLarge ? 'h1' : 'h2';
---

<Heading>标题</Heading>

九、模板最佳实践 #

9.1 保持 Frontmatter 简洁 #

astro
---
// ✅ 好的做法:将复杂逻辑抽取到函数
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

const items = await getItems();
const total = calculateTotal(items);
---

<p>总价: {total}</p>

---
// ❌ 不好的做法:在模板中写复杂逻辑
const items = await getItems();
---

<p>总价: {items.reduce((sum, item) => sum + item.price, 0)}</p>

9.2 使用类型定义 #

astro
---
interface Props {
  title: string;
  items: string[];
}

const { title, items }: Props = Astro.props;
---

<h1>{title}</h1>
<ul>
  {items.map(item => <li>{item}</li>)}
</ul>

9.3 避免深层嵌套 #

astro
---
// ✅ 好的做法:使用组件拆分
import Card from './Card.astro';

const posts = await getPosts();
---

{posts.map(post => <Card post={post} />)}

---
// ❌ 不好的做法:深层嵌套
const posts = await getPosts();
---

{posts.map(post => (
  <article>
    <header>
      <h2>{post.title}</h2>
      <p>{post.date}</p>
    </header>
    <div>
      <p>{post.excerpt}</p>
      <a href={`/blog/${post.slug}`}>阅读更多</a>
    </div>
  </article>
))}

十、总结 #

模板基础核心要点:

text
┌─────────────────────────────────────────────────────┐
│                 模板基础要点                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  📝 Frontmatter   脚本区域,定义变量和逻辑          │
│                                                     │
│  🔤 文本插值      {变量} 插入动态值                 │
│                                                     │
│  📎 属性绑定      动态绑定 HTML 属性                │
│                                                     │
│  📦 Fragment      无额外节点的元素包装              │
│                                                     │
│  🔧 指令          set:html、set:text 等             │
│                                                     │
└─────────────────────────────────────────────────────┘

下一步,让我们学习 表达式,深入了解模板中的表达式使用!

最后更新:2026-03-28