ข้ามไปยังเนื้อหา

Astro Syntax Deep Dive

ที่ไหนก็ได้ใน template { } จะฝัง JavaScript expression แล้ว render ค่าที่ได้ออกมา ตัวแปรจาก frontmatter ใช้ได้เลย

---
const name = "Ada";
const count = 3;
---
<h1>Hello, {name}</h1>
<p>You have {count} messages.</p>
<p>Total: {count * 2}</p>

ข้างใน { } ใส่ได้เฉพาะ expression เท่านั้น — อะไรที่ประเมินออกมาเป็นค่า ส่วน statement (บล็อก if, loop for) ต้องอยู่ใน frontmatter ใน template คุณเขียน control flow ในรูปของ expression ตามที่จะเห็นด้านล่าง

ใช้สำนวน logical-AND เพื่อ render บางอย่างเฉพาะตอนเงื่อนไขเป็นจริง และใช้ ternary เพื่อเลือกระหว่างสองทาง

---
const isLoggedIn = true;
const user = { name: "Ada", role: "admin" };
---
{isLoggedIn && <p>Welcome back, {user.name}</p>}
{user.role === "admin"
? <a href="/admin">Admin panel</a>
: <a href="/account">Your account</a>}

{cond && <div/>} จะ render element เมื่อ cond เป็น truthy และไม่ render อะไรเลยถ้าไม่ใช่ ส่วน ternary จะ render หนึ่งในสองทางเสมอ

ไม่มี directive สำหรับ loop โดยเฉพาะ — คุณ map array ให้เป็น markup เหมือน JSX เป๊ะ ๆ

---
const posts = [
{ id: 1, title: "First" },
{ id: 2, title: "Second" },
];
---
<ul>
{posts.map((post) => <li>{post.title}</li>)}
</ul>

Astro ไม่ต้องใช้ prop key แบบที่ React ต้องใช้ — นี่คือ server rendering ไม่มี reconciliation แค่ map, return element, จบ

เวลาที่คุณต้อง return หลาย element โดยไม่อยากเพิ่ม DOM node ห่อ ให้ใช้ Fragment Astro รองรับทั้งแบบมีชื่อ <Fragment> และแบบย่อ <>

---
const items = ["a", "b"];
---
<Fragment>
<dt>Term</dt>
<dd>Definition</dd>
</Fragment>
{items.map((x) => (
<>
<span>{x}</span>
<br />
</>
))}

Fragment ยังเป็นที่วาง directive set:* เวลาที่คุณไม่มี element ให้แปะด้วย (เดี๋ยวจะพูดถึง)

โดย default expression ที่ให้ค่าออกมาเป็น string จะถูก escape — Astro render เป็น text ดังนั้น <b>hi</b> จะโชว์ tag ออกมาตรง ๆ นั่นปลอดภัย เมื่อไหร่ที่คุณมี string HTML ที่เชื่อถือได้และอยากให้ render เป็น markup จริง ๆ (เช่น HTML จาก CMS ที่คุณคุมเอง) ให้ใช้ set:html

---
const fromCms = "<strong>Bold from the CMS</strong>";
const plain = "<not a tag>";
---
<div set:html={fromCms} /> <!-- renders as real bold markup -->
<div set:text={plain} /> <!-- renders the characters, escaped -->
<Fragment set:html={fromCms} /> <!-- no wrapper element -->

set:html ข้าม escaping — ใช้กับ content ที่เชื่อถือได้เท่านั้น ไม่งั้นคุณเปิดช่องโหว่ XSS ส่วน set:text บังคับให้ค่าถูกมองเป็น plain text (คือ default ที่ปลอดภัยเขียนออกมาให้ชัด)

เมื่อคุณมี object ของ attribute ให้ spread ลงบน element ด้วย {...obj} — สะดวกเวลาส่ง props ต่อ

---
const attrs = { id: "cta", class: "btn", "data-track": "signup" };
---
<a href="/join" {...attrs}>Join</a>

คุณคำนวณได้ว่าจะ render element ตัวไหน กำหนดตัวแปรที่ขึ้นต้นด้วยตัวใหญ่ให้เป็นชื่อ tag แล้วเรียกใช้ตัวแปรนั้นได้เลย

---
const level = 2;
const Heading = `h${level}`; // "h2"
---
<Heading>Dynamic heading</Heading> <!-- renders <h2>Dynamic heading</h2> -->

ตัวแปรต้อง ขึ้นต้นด้วยตัวใหญ่ เพื่อให้ Astro มองว่าเป็น reference ไปยัง component/tag ไม่ใช่ HTML tag ที่เขียนตรง ๆ

flowchart LR
  expr["expression in curly braces"] --> eval["evaluated on the server"]
  eval --> esc["string values are escaped by default"]
  esc --> html["safe HTML output"]
  eval --> raw["set:html bypasses escaping for trusted HTML"]
  raw --> html
template expression กลายเป็น HTML ได้ยังไง

จุดต่างที่ละเอียดแต่สำคัญจาก React: template ของ Astro ใช้ ชื่อ attribute แบบ HTML มาตรฐาน เป็น class ไม่ใช่ className; เป็น for ไม่ใช่ htmlFor คุณกำลังเขียน superset ของ HTML ดังนั้นชื่อแบบ HTML คือชื่อจริง

คุณ render list จาก array ใน template ของ Astro ยังไง?
`set:html` ทำอะไร และเสี่ยงอะไร?
คุณอยากคืน element พี่น้องสองตัวโดยไม่เพิ่ม wrapper node ใช้อะไร?
ชื่อ attribute ไหนถูกต้องใน template ของ Astro?