Day5 - Astro Series: Components

Astro 系列文第五日:基础组件

一个漂亮的渐变背景上面有一句标题:「基础组件」

前言

前面除了创建新专案之外也了解了 Astro CLI 与設定檔的大致样貌,本章节会从创建基本 Astro 组件开始,组件可以被放置在先前提到的 src 资料夹当中,建议创建一个 components 资料夹存放在内以方便管理。

创建组件

可以藉由创建一个副档名为 .astro 的档案来撰写 Astro 组件,组件中分为脚本与模板两个区块,这两个区块由栅栏(---)所区隔。

---
// 组件脚本
---
<!-- 组件模板 -->

组件脚本代表该组件在服务器端中会被如何执行,像是你可以在这里撰写 JavaScript 或 TypeScript 用于:

  1. 引入其他 Astro 组件
  2. 引入不同框架的组件
  3. 引入与索取资料
  4. 接收组件接收到的资料(props)
  5. 创建变数并在组件模板中存取
---
// 1. 导入 Astro 组件
import SomeAstroComponent from './components/SomeAstroComponent.astro';
// 2. 引入不同框架的组件
import SomeReactComponent from './components/SomeReactComponent.jsx';
// 3. 引入资料
import pokemons from '../data/pokemon.json';
// 3. 引入资料
const user = await fetch('SOME_SECRET_API_URL/users').then(r => r.json());
// 4. 透过解构获得该组件所接收到的 props
const { title } = Astro.props;
const message = 'Astro 很棒!'
---
<!-- 5. 创建变量并在组件模板中存取 -->
我认为:{ message }
{ title }

关于第 5 点,可以在服务器端 JavaScript 中创建变数使用 {} 单括弧插入到模板中,不管是属性还是作为 Props 传入:

---
import Modal from './components/Modal.astro'
const message = '你好,Astro'
---
<Modal class={message} message={`${message} Here Is My Props`} />

或者是撰写 JavaScript 表达式来产生 HTML,就像是 JSX 一样:

---
const fruits = ['苹果', '香蕉', '樱桃']
---
<ul>
{fruits.map(fruit => <li>{fruit}<li>)}
</ul>

或是选择性地显示模板内容:

---
const isOnSale = true
---
{isOnSale && <p>正在打折中!</p>}
{isOnSale ? <p>正在打折中!</p> : <p>尚无打折</p>}

或是动态的决定标签种类(须留意动态标签不支援 Hydration、并且标签命名必须大写,才能分辨出是原生标签还是客制化组件):

---
import MyComponent from "./MyComponent.astro";
const Element = 'div'
const Component = MyComponent;
---
<Element>Hello!</Element> <!-- 渲染出 <div>Hello!</div> -->
<Component /> <!-- 渲染出 <MyComponent /> -->

组件属性 Props

可以设定让组件接受外部传入的属性,只需要在组件内的服务器端脚本中从 Astro.props 中来获得,像以下的例子 greetingname 两个变数被使用解构的方式创建出来:

---
// 组件外: <GreetingHeadline greeting="Hi" name="Joe" />
const { greeting, name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

甚至可以为 Props 添加类型,让文字编辑器可以知道「该组件应该要传入什么类型的东西」:

interface Props {
greeting: string;
name: string;
}

组件插槽 Slot

除了组件属性也可以透过插槽的方式将外部的 HTML 内容传入到组件之中,举例来说:由于目前网站大多数页面都包含了导航列与页脚组件,于是你可以创建一个名为 Base 的组件作为网站的通用组件。

---
import Navbar from '../components/Navbar.astro'
import Hero from '../components/Hero.astro'
import Footer from '../components/Footer.astro'
---
<Navbar />
<Hero />
<slot />
<Footer />

并且在每个页面中引入该组件,不但可以统一管理所有页面的架构,也不用在每一页反覆的引入基本需要的组件。

---
import Base from '../layouts/Base.astro
---
<Base>
<!-- 此区间的模板将注入到 slot 中 -->
</Base>

具名插槽

可以拥有一个以上的插槽,这时候使用具名插槽来指定「要注入内容的插槽」。举例来说根据先前的范例:

<Navbar />
<slot name="before-hero" />
<Hero />
<slot />
<Footer />

就可以使用 slot 属性来指定想注入的插槽名称。

<Base>
<!-- 以下图片将会注入到 before-hero 插槽中 -->
<img src ="example.jpg" slot="before-hero">
<!-- 以下没有特别注明 slot 将注入到预设 slot 中 -->
<h1>你好世界</h1>
</Base>

插槽后备方案

当在定义插槽的位址时,可以为其添加内容,这些内容就会成为当没有任何模板传递进来时所采用的预设内容。

<slot>
<p>这是预设后备方案,当没有模板传递到 slot 时就会被采用</p>
</slot>

插槽转移

插槽可以被转移到其他的组件之中,举例来说有个组件: BaseLayout.astro

---
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<slot name="head"/>
</head>
<body>
<slot />
</body>
</html>

而它被 HomeLayout.astro 所引用:

---
import BaseLayout from "./BaseLayout.astro";
---
<BaseLayout>
<slot name="head" slot="head"/>
<slot />
</BaseLayout>

这时候再对 HomeLayout.astro 传入 head 插槽将会转移到 BaseLayouthead 插槽之中。

---
import HomeLayout from "../layouts/HomeLayout.astro";
---
<HomeLayout>
<title slot="head">Astro</title>
<h1>Astro</h1>
</HomeLayout>

总结

如果你先前有接触过其他框架或 JSX 就会发现这些「组件」间的观念都很雷同好上手,并且由于 .astro 设计主要用于服务器渲染,因此不用担心状态反应性的問題,极大地删减了复杂度!

最后会建议实际动手练习,如果过程中有问题可以参考看看我的范例🔗

  1. components 资料夹内撰写像是 NavbarFooter 之类常见的组件。
  2. pages 资料夹内的页面中导入并显示你新制作好的组件。
  3. layouts 资料夹内创建一个名为 Base 的组件,并且透过 <slot /> 让整个网站的页面都使用该组件,甚至更进一步接受 Props 让该组件提供更多弹性可被修改的空间(像是接受修改 <head> 中的 meta 标签们)。
  4. 适当的撰写 TypeScript 进行类型定义。

延伸阅读