Anatomi komponen .astro
Komponen .astro terlihat seperti PHP dengan sintaks lain. Bedanya satu dan besar: kodenya hanya berjalan sekali, di server, dan tidak pernah ikut ke browser.
Intisari
- Berkas
.astropunya dua bagian: frontmatter di antara---, dan template di bawahnya. - Frontmatter berjalan di server saja.
window,document, dan event handler tidak ada di sana. - Komponen
.astrotidak punya state dan tidak pernah dirender ulang. Ia dijalankan sekali per request atau sekali per build. Astro.propsuntuk data masuk,<slot />untuk isi yang dikirim pemanggil.<style>di dalam komponen otomatis ter-scope โ tidak bisa bocor ke komponen lain tanpa kamu memintanya.
Bentuk dasarnya
---
// FRONTMATTER โ server saja, sekali jalan
import KartuArtikel from "@/components/KartuArtikel.astro";
import { ambilArtikelTerbaru } from "@/lib/artikel";
interface Props {
kategori: string;
batas?: number;
}
const { kategori, batas = 10 } = Astro.props;
const artikel = await ambilArtikelTerbaru(kategori, batas);
---
<!-- TEMPLATE โ jadi HTML -->
<section class="daftar">
<h2>Terbaru di {kategori}</h2>
{artikel.length === 0 && <p>Belum ada artikel.</p>}
<ul>
{artikel.map((a) => (
<li><KartuArtikel artikel={a} /></li>
))}
</ul>
</section>
<style>
/* Otomatis ter-scope ke komponen ini saja */
.daftar { display: grid; gap: 1rem; }
</style>
Perhatikan await di frontmatter. Ia berada di level teratas berkas, bukan di dalam fungsi โ
dan itu sah. Setiap komponen .astro secara efektif adalah fungsi async, jadi kamu bisa
mengambil data di mana pun kamu membutuhkannya, bukan hanya di halaman.
Peta dari yang sudah kamu kenal
| Di CodeIgniter 3 | Di Astro | Bedanya yang penting |
|---|---|---|
$this->load->view('kartu', $data) |
<KartuArtikel artikel={a} /> |
Prop-nya bertipe. Salah nama prop = gagal saat astro check, bukan Undefined index saat runtime. |
<?= $judul ?> |
{judul} |
Astro meng-escape otomatis. <?= ?> tidak โ itu sumber XSS paling umum di CI3. |
<?php foreach ($rows as $r): ?> |
{rows.map((r) => (โฆ))} |
Ekspresi, bukan pernyataan. Ia menghasilkan nilai, jadi tidak ada endforeach. |
$this->load->view('header') |
<Layout>โฆ</Layout> + <slot /> |
Layout membungkus, bukan dua potongan terpisah โ jadi tidak mungkin lupa memuat footer. |
Helper di application/helpers/ |
Fungsi biasa di src/lib/ |
Tidak ada autoload ajaib. Kamu import, dan itu terlacak. |
Aturan yang menjelaskan hampir semua kebingungan
Frontmatter tidak pernah sampai ke browser. Ia berjalan di Node โ saat build untuk halaman
statis, atau saat request untuk halaman on-demand. Setelah itu hasilnya HTML, dan kodenya hilang. Karena
itu onclick={fungsiSaya} di komponen .astro tidak bekerja: tidak
ada fungsiSaya di browser untuk dipanggil.
---
const sapa = () => alert("halo");
---
<!-- TIDAK BEKERJA. Diam saja, tanpa error. -->
<button onclick={sapa}>Klik</button>
<!-- Bekerja: script sungguhan yang dikirim ke browser -->
<button id="sapa">Klik</button>
<script>
document.getElementById("sapa")?.addEventListener("click", () => {
alert("halo");
});
</script>
Untuk interaktivitas yang lebih dari beberapa baris, jangan pakai <script> โ pakai
island Vue (Fase 4). <script> cocok untuk hal kecil: menutup banner, menyalin tautan,
memasang analitik.
Slot โ isi yang dikirim pemanggil
---
// src/layouts/Artikel.astro
interface Props { judul: string; }
const { judul } = Astro.props;
---
<html lang="id">
<head><title>{judul}</title></head>
<body>
<header><slot name="header" /></header>
<main><slot /></main>
<aside><slot name="samping">Tidak ada isi samping.</slot></aside>
</body>
</html>
<Artikel judul={a.judul}>
<h1 slot="header">{a.judul}</h1>
<p>Ini masuk ke slot tanpa nama.</p>
<!-- slot "samping" tidak diisi โ isi cadangannya yang dipakai -->
</Artikel>
Slot bernama adalah pengganti langsung untuk pola $this->load->view('sidebar_' . $tipe)
di CI3, tapi tanpa risiko memuat berkas yang tidak ada.
Style yang ter-scope, dan kapan ia mengganggu
<style>
h2 { color: crimson; } /* hanya h2 di komponen INI */
</style>
<style is:global>
.prose a { text-decoration: underline; } /* bocor ke mana-mana, disengaja */
</style>
Ini akan menggigitmu saat merender badan artikel dari database. HTML dari kolom
isi disisipkan lewat set:html, dan elemen di dalamnya tidak
mendapat atribut scope โ jadi <style> p { โฆ } </style> biasa tidak akan
menyentuhnya. Bungkus dengan kelas dan pakai is:global yang dipagari:
<style is:global> .badan-artikel p { โฆ } </style>. Soal set:html dan
bahayanya dibahas di Fase 7.
Astro.props, Astro.params, Astro.url
| Objek | Isinya | Contoh |
|---|---|---|
Astro.props | Data dari komponen pemanggil | const { artikel } = Astro.props |
Astro.params | Bagian dinamis dari URL | Astro.params.slug untuk [slug].astro |
Astro.url | URL lengkap sebagai objek URL | Astro.url.searchParams.get("hal") |
Astro.request | Request standar web | Astro.request.headers.get("cf-connecting-ip") |
Astro.locals | Data yang dititipkan middleware | Astro.locals.member (Fase 5) |
Astro.request dan Astro.locals hanya berisi sesuatu di halaman yang dirender
on-demand. Di halaman statis, tidak ada request saat render โ ia dibuat jauh sebelum pembaca datang.
Mengakses Astro.request.headers di halaman statis akan memberimu nilai kosong, bukan error.
Ini penyebab bug "kok member-nya selalu null" yang paling sering (Fase 1).
Latihan: buat src/layouts/Dasar.astro dengan satu slot bernama
header dan satu slot tanpa nama, lalu src/components/KartuArtikel.astro dengan
interface Props. Rakit halaman daftar yang memakai keduanya. Lalu buktikan dua hal:
(1) tambahkan onclick={โฆ} di sebuah tombol dan konfirmasi ia diam saja; (2) render
{"<script>alert(1)</script>"} sebagai judul dan konfirmasi Astro meng-escape-nya
jadi teks biasa.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.