更多内容在 rubyonrails.org:

在 Rails 中使用 JavaScript

本指南介绍了将 JavaScript 功能集成到 Rails 应用程序中的选项,包括使用外部 JavaScript 包的选项以及如何在 Rails 中使用 Turbo。

阅读本指南后,您将了解

  • 如何在不需要 Node.js、Yarn 或 JavaScript 捆绑器的情况下使用 Rails。
  • 如何使用导入映射、Bun、esbuild、Rollup 或 Webpack 捆绑 JavaScript 创建新的 Rails 应用程序。
  • 什么是 Turbo,以及如何使用它。
  • 如何使用 Rails 提供的 Turbo HTML 助手。

1 导入映射

导入映射 允许您使用映射到来自浏览器的版本化文件的逻辑名称导入 JavaScript 模块。导入映射是 Rails 7 的默认设置,允许任何人在不需要转译或捆绑的情况下使用大多数 npm 包构建现代 JavaScript 应用程序。

使用导入映射的应用程序不需要 Node.jsYarn 才能正常运行。如果您计划将 Rails 与 importmap-rails 一起使用来管理您的 JavaScript 依赖项,则无需安装 Node.js 或 Yarn。

使用导入映射时,不需要单独的构建过程,只需使用 bin/rails server 启动您的服务器,就可以开始运行了。

1.1 安装 importmap-rails

Rails 7+ 中的新应用程序会自动包含 Rails 的导入映射,但您也可以在现有应用程序中手动安装它

$ bin/bundle add importmap-rails

运行安装任务

$ bin/rails importmap:install

1.2 使用 importmap-rails 添加 npm 包

要将新包添加到您的导入映射驱动的应用程序,请从您的终端运行 bin/importmap pin 命令

$ bin/importmap pin react react-dom

然后,像往常一样将包导入 application.js

import React from "react"
import ReactDOM from "react-dom"

2 使用 JavaScript 捆绑器添加 npm 包

导入映射是新 Rails 应用程序的默认设置,但如果您更喜欢传统的 JavaScript 捆绑,您可以使用您选择的 BunesbuildWebpackRollup.js 创建新的 Rails 应用程序。

要在新的 Rails 应用程序中使用捆绑器而不是导入映射,请将 --javascript-j 选项传递给 rails new

$ rails new my_new_app --javascript=bun
OR
$ rails new my_new_app -j bun

这些捆绑选项都带有简单的配置,并通过 jsbundling-rails gem 与资产管道集成。

使用捆绑选项时,请使用 bin/dev 启动 Rails 服务器并为开发构建 JavaScript。

2.1 安装 JavaScript 运行时

如果您使用 esbuild、Rollup.js 或 Webpack 在您的 Rails 应用程序中捆绑 JavaScript,则必须安装 Node.js 和 Yarn。如果您使用 Bun,那么您只需要安装 Bun,因为它既是 JavaScript 运行时也是捆绑器。

2.1.1 安装 Bun

Bun 网站 上找到安装说明,并使用以下命令验证它是否已正确安装并在您的路径中

$ bun --version

应打印出您的 Bun 运行时版本。如果显示类似 1.0.0 的内容,则表示 Bun 已正确安装。

如果没有,您可能需要在当前目录中重新安装 Bun 或重新启动终端。

2.1.2 安装 Node.js 和 Yarn

如果您使用 esbuild、Rollup.js 或 Webpack,则需要 Node.js 和 Yarn。

Node.js 网站 上找到安装说明,并使用以下命令验证它是否已正确安装

$ node --version

应打印出您的 Node.js 运行时版本。确保它大于 8.16.0

要安装 Yarn,请按照 Yarn 网站 上的安装说明进行操作。运行此命令应打印出 Yarn 版本

$ yarn --version

如果显示类似 1.22.0 的内容,则表示 Yarn 已正确安装。

3 在导入映射和 JavaScript 捆绑器之间进行选择

创建新的 Rails 应用程序时,您需要在导入映射和 JavaScript 捆绑解决方案之间进行选择。每个应用程序都有不同的要求,您应该在选择 JavaScript 选项之前仔细考虑您的要求,因为将一个选项迁移到另一个选项对于大型复杂应用程序来说可能很费时。

导入映射是默认选项,因为 Rails 团队相信导入映射在减少复杂性、改善开发人员体验和提供性能提升方面具有潜力。

对于许多应用程序,尤其是那些主要依赖 Hotwire 堆栈来满足其 JavaScript 需求的应用程序,导入映射将是长期内的正确选择。您可以在这里阅读更多关于在 Rails 7 中将导入映射设置为默认选项的原因 这里.

其他应用程序可能仍然需要传统的 JavaScript 捆绑器。表明您应该选择传统捆绑器的需求包括

  • 如果您的代码需要转译步骤,例如 JSX 或 TypeScript。
  • 如果您需要使用包含 CSS 或以其他方式依赖 Webpack 加载器 的 JavaScript 库。
  • 如果您完全确定需要 摇树优化.
  • 如果您将通过 cssbundling-rails gem 安装 Bootstrap、Bulma、PostCSS 或 Dart CSS。除了 Tailwind 和 Sass 之外,此 gem 提供的所有选项都将在您未在 rails new 中指定其他选项的情况下自动为您安装 esbuild

4 Turbo

无论您选择导入映射还是传统的捆绑器,Rails 都附带了 Turbo 来加快应用程序速度,同时显着减少您需要编写的 JavaScript 代码量。

Turbo 允许您的服务器直接提供 HTML,作为替代流行的前端框架,这些框架将 Rails 应用程序的服务器端简化为一个简单的 JSON API。

4.1 Turbo Drive

Turbo Drive 通过避免每次导航请求时进行完整的页面拆卸和重建来加快页面加载速度。 Turbo Drive 是 Turbolinks 的改进和替代品。

4.2 Turbo 框架

Turbo 框架 允许根据请求更新页面预定义的部分,而不会影响页面其余内容。

您可以使用 Turbo 框架构建无需任何自定义 JavaScript 的就地编辑功能,延迟加载内容,以及轻松创建服务器渲染的选项卡式界面。

Rails 提供 HTML 助手,通过 turbo-rails gem 简化 Turbo 框架的使用。

使用此 gem,您可以使用 turbo_frame_tag 助手将 Turbo 框架添加到您的应用程序中,如下所示

<%= turbo_frame_tag dom_id(post) do %>
  <div>
     <%= link_to post.title, post_path(post) %>
  </div>
<% end %>

4.3 Turbo 流

Turbo 流 将页面更改作为包装在自执行 <turbo-stream> 元素中的 HTML 片段传递。 Turbo 流允许您通过 WebSockets 广播其他用户所做的更改,并在表单提交后更新页面的一部分,而无需进行完整的页面加载。

Rails 提供 HTML 和服务器端助手,通过 turbo-rails gem 简化 Turbo 流的使用。

使用此 gem,您可以从控制器操作渲染 Turbo 流

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.turbo_stream
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

Rails 会自动查找 .turbo_stream.erb 视图文件,并在找到该视图时渲染它。

Turbo 流响应也可以在控制器操作中内联渲染

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.turbo_stream { render turbo_stream: turbo_stream.prepend("posts", partial: "post") }
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

最后,Turbo 流可以使用内置的助手从模型或后台作业启动。这些广播可用于通过 WebSocket 连接更新所有用户的內容,保持页面內容新鲜并使您的应用程序活跃起来。

要从模型广播 Turbo 流,请将模型回调与以下内容结合起来

class Post < ApplicationRecord
  after_create_commit { broadcast_append_to("posts") }
end

与在应该接收更新的页面上设置的 WebSocket 连接,如下所示

<%= turbo_stream_from "posts" %>

5 Rails/UJS 功能的替代品

Rails 6 附带了一个名为 UJS(非侵入式 JavaScript)的工具。UJS 允许开发人员覆盖 <a> 标签的 HTTP 请求方法,在执行操作之前添加确认对话框,等等。UJS 是 Rails 7 之前的默认设置,但现在建议使用 Turbo。

5.1 方法

点击链接始终会产生 HTTP GET 请求。如果您的应用程序是 RESTful,那么一些链接实际上是更改服务器上数据的操作,应该使用非 GET 请求来执行。data-turbo-method 属性允许使用明确的方法(例如“post”、“put”或“delete”)标记此类链接。

Turbo 将在您的应用程序中扫描 <a> 标签以查找 turbo-method 数据属性,并在存在时使用指定的方法,覆盖默认的 GET 操作。

例如

<%= link_to "Delete post", post_path(post), data: { turbo_method: "delete" } %>

这将生成

<a data-turbo-method="delete" href="...">Delete post</a>

使用 data-turbo-method 更改链接的方法的另一种方法是使用 Rails 的 button_to 助手。出于可访问性原因,对于任何非 GET 操作,实际的按钮和表单是更好的选择。

5.2 确认

您可以通过在链接和表单上添加 data-turbo-confirm 属性来请求用户的额外确认。点击链接或提交表单时,用户将看到一个包含属性文本的 JavaScript confirm() 对话框。如果用户选择取消,则操作不会进行。

例如,使用 link_to 助手

<%= link_to "Delete post", post_path(post), data: { turbo_method: "delete", turbo_confirm: "Are you sure?" } %>

这将生成

<a href="..." data-turbo-confirm="Are you sure?" data-turbo-method="delete">Delete post</a>

当用户点击“删除帖子”链接时,他们将看到一个“您确定吗?”确认对话框。

该属性也可以与 button_to 助手一起使用,但它必须添加到 button_to 助手在内部渲染的表单中

<%= button_to "Delete post", post, method: :delete, form: { data: { turbo_confirm: "Are you sure?" } } %>

5.3 Ajax 请求

从 JavaScript 发出非 GET 请求时,需要 X-CSRF-Token 标头。如果没有此标头,Rails 不会接受请求。

Rails 需要此令牌来防止跨站点请求伪造 (CSRF) 攻击。在 安全指南 中了解更多信息。

Rails Request.JS 封装了添加 Rails 所需请求标头的逻辑。只需从包中导入 FetchRequest 类并实例化它,传入请求方法、url、选项,然后调用 await request.perform() 并使用响应进行操作。

例如

import { FetchRequest } from '@rails/request.js'

....

async myMethod () {
  const request = new FetchRequest('post', 'localhost:3000/posts', {
    body: JSON.stringify({ name: 'Request.JS' })
  })
  const response = await request.perform()
  if (response.ok) {
    const body = await response.text
  }
}

当使用其他库进行 Ajax 调用时,需要自己将安全令牌添加为默认标头。要获取令牌,请查看 <meta name='csrf-token' content='THE-TOKEN'> 标签,该标签由 csrf_meta_tags 在您的应用程序视图中打印。您可以执行类似的操作

document.head.querySelector("meta[name=csrf-token]")?.content


返回顶部