脚本和事件处理
你可以在不使用 UI 框架(如 React、Svelte、Vue 等)的情况下,通过标准的 HTML <script>
标签为你的 Astro 组件添加交互性。这允许你发送 JavaScript 在浏览器中运行,并为你的 Astro 组件添加功能。
客户端脚本
章节标题:“客户端脚本”脚本可以用来添加事件监听器、发送分析数据、播放动画,以及 JavaScript 在 web 上能做的所有事情。
<button data-confetti-button>Celebrate!</button>
<script> // Import npm modules. import confetti from 'canvas-confetti';
// Find our component DOM on the page. const buttons = document.querySelectorAll('[data-confetti-button]');
// Add event listeners to fire confetti when a button is clicked. buttons.forEach((button) => { button.addEventListener('click', () => confetti()); });</script>
默认情况下,Astro 会处理和打包 <script>
标签,增加了对导入 npm 模块、编写 TypeScript 等功能的支持。
在 Astro 中使用 <script>
章节标题:“在 Astro 中使用 <script>”在 .astro
文件中,你可以通过添加一个(或多个)<script>
标签来添加客户端 JavaScript。
在此示例中,将 <Hello />
组件添加到页面会在浏览器控制台中打印一条消息。
<h1>Welcome, world!</h1>
<script> console.log('Welcome, browser console!');</script>
脚本处理
章节标题:“脚本处理”默认情况下,<script>
标签由 Astro 处理。
- 任何导入都将被打包,允许你导入本地文件或 Node 模块。
- 处理后的脚本将以
type="module"
的形式注入到其声明的位置。 - 完全支持 TypeScript,包括导入 TypeScript 文件。
- 如果你的组件在页面上多次使用,该脚本将只被包含一次。
<script> // Processed! Bundled! TypeScript-supported! // Importing local scripts and Node modules works.</script>
type="module"
属性使浏览器将脚本视为 JavaScript 模块。这有几个性能优势:
- 渲染不会被阻塞。在模块脚本及其依赖项加载时,浏览器会继续处理 HTML 的其余部分。
- 浏览器会等待 HTML 处理完毕后再执行模块脚本。你不需要监听 “load” 事件。
async
和defer
属性是不必要的。模块脚本总是被延迟执行。
async
属性对于普通脚本很有价值,因为它能防止它们阻塞渲染。然而,模块脚本已经具有此行为。向模块脚本添加 async
会导致它在页面完全加载之前执行。这可能不是你想要的结果。
选择不处理
章节标题:“选择不处理”要阻止 Astro 处理脚本,请添加 is:inline
指令。
<script is:inline> // Will be rendered into the HTML exactly as written! // Local imports are not resolved and will not work. // If in a component, repeats each time the component is used.</script>
在某些情况下,Astro 不会处理你的脚本标签。特别是,向 <script>
标签添加 type="module"
或除 src
之外的任何属性,都会导致 Astro 将该标签视为带有 is:inline
指令。
<script>
标签上可用指令的更多信息,请参阅我们的指令参考页面。
在页面中引入 JavaScript 文件
章节标题:“在页面中引入 JavaScript 文件”你可能希望将脚本编写为单独的 .js
/.ts
文件,或者需要引用另一台服务器上的外部脚本。你可以通过在 <script>
标签的 src
属性中引用它们来实现。
导入本地脚本
章节标题:“导入本地脚本”何时使用: 当你的脚本位于 src/
目录中时。
Astro 将为你构建、优化这些脚本并将其添加到页面中,遵循其脚本处理规则。
<!-- relative path to script at `src/scripts/local.js` --><script src="../scripts/local.js"></script>
<!-- also works for local TypeScript files --><script src="./script-with-types.ts"></script>
加载外部脚本
章节标题:“加载外部脚本”何时使用: 当你的 JavaScript 文件位于 public/
目录或 CDN 上时。
要加载项目 src/
文件夹之外的脚本,请包含 is:inline
指令。这种方法会跳过上述导入脚本时 Astro 提供的 JavaScript 处理、打包和优化。
<!-- absolute path to a script at `public/my-script.js` --><script is:inline src="/my-script.js"></script>
<!-- full URL to a script on a remote server --><script is:inline src="https://my-analytics.com/script.js"></script>
常见的脚本模式
章节标题:“常见的脚本模式”处理 onclick
和其他事件
章节标题:“处理 onclick 和其他事件”一些 UI 框架使用自定义语法进行事件处理,例如 onClick={...}
(React/Preact) 或 @click="..."
(Vue)。Astro 更紧密地遵循标准 HTML,不使用自定义事件语法。
相反,你可以在 <script>
标签中使用addEventListener
来处理用户交互。
<button class="alert">Click me!</button>
<script> // Find all buttons with the `alert` class on the page. const buttons = document.querySelectorAll('button.alert');
// Handle clicks on each button. buttons.forEach((button) => { button.addEventListener('click', () => { alert('Button was clicked!'); }); });</script>
如果页面上有多个 <AlertButton />
组件,Astro 不会多次运行该脚本。脚本被打包并且每页只包含一次。使用 querySelectorAll
可以确保此脚本将事件监听器附加到页面上找到的每个带有 alert
类的按钮上。
使用自定义元素的 Web 组件
章节标题:“使用自定义元素的 Web 组件”你可以使用 Web Components 标准创建具有自定义行为的 HTML 元素。在 .astro
组件中定义一个自定义元素,可以让你在不需要 UI 框架库的情况下构建交互式组件。
在这个例子中,我们定义了一个新的 <astro-heart>
HTML 元素,它会跟踪你点击心形按钮的次数,并用最新的计数更新 <span>
。
<!-- Wrap the component elements in our custom element “astro-heart”. --><astro-heart> <button aria-label="Heart">💜</button> × <span>0</span></astro-heart>
<script> // Define the behaviour for our new type of HTML element. class AstroHeart extends HTMLElement { connectedCallback() { let count = 0;
const heartButton = this.querySelector('button'); const countSpan = this.querySelector('span');
// Each time the button is clicked, update the count. heartButton.addEventListener('click', () => { count++; countSpan.textContent = count.toString(); }); } }
// Tell the browser to use our AstroHeart class for <astro-heart> elements. customElements.define('astro-heart', AstroHeart);</script>
在这里使用自定义元素有两个优点:
-
你可以使用
this.querySelector()
,它只在当前自定义元素实例中搜索,而不是使用document.querySelector()
搜索整个页面。这使得一次只处理一个组件实例的子元素变得更加容易。 -
虽然
<script>
只运行一次,但浏览器每次在页面上找到<astro-heart>
时都会运行我们自定义元素的connectedCallback()
方法。这意味着你可以安全地一次只为一个组件编写代码,即使你打算在页面上多次使用这个组件。
将 frontmatter 变量传递给脚本
章节标题:“将 frontmatter 变量传递给脚本”在 Astro 组件中,frontmatter 中位于 ---
之间的代码在服务器上运行,在浏览器中不可用。要将变量从服务器发送到客户端,我们需要一种方法来存储我们的变量,然后在浏览器中运行 JavaScript 时读取它们。
一种方法是使用 data-*
属性将变量的值存储在 HTML 输出中。脚本(包括自定义元素)可以在 HTML 加载到浏览器后,使用元素的 dataset
属性读取这些属性。
在这个示例组件中,一个 message
prop 被存储在一个 data-message
属性中,因此自定义元素可以读取 this.dataset.message
并在浏览器中获取该 prop 的值。
---const { message = 'Welcome, world!' } = Astro.props;---
<!-- Store the message prop as a data attribute. --><astro-greet data-message={message}> <button>Say hi!</button></astro-greet>
<script> class AstroGreet extends HTMLElement { connectedCallback() { // Read the message from the data attribute. const message = this.dataset.message; const button = this.querySelector('button'); button.addEventListener('click', () => { alert(message); }); } }
customElements.define('astro-greet', AstroGreet);</script>
现在我们可以多次使用我们的组件,并且每次都会收到不同的问候消息。
---import AstroGreet from '../components/AstroGreet.astro';---
<!-- Use the default message: “Welcome, world!” --><AstroGreet />
<!-- Use custom messages passed as a props. --><AstroGreet message="Lovely day to build components!" /><AstroGreet message="Glad you made it! 👋" />
当你将 props 传递给使用像 React 这样的 UI 框架编写的组件时,这实际上就是 Astro 在幕后所做的事情!对于带有 client:*
指令的组件,Astro 会创建一个 <astro-island>
自定义元素,其 props
属性会将你的服务器端 props 存储在 HTML 输出中。
结合脚本和 UI 框架
章节标题:“结合脚本和 UI 框架”当 <script>
标签执行时,由 UI 框架渲染的元素可能还不可用。如果你的脚本还需要处理UI 框架组件,建议使用自定义元素。