学习Hotwire的基本指南

918 阅读6分钟

动机

BootrAils,我们根本不使用Hotwire。我们认为不使用它,启动一个MVP会更简单。也就是说,Hotwire看起来很有前途,值得特别关注,因为它是默认与Rails一起发货的。

为什么是Hotwire

Hotwire试图在编写全栈式Web应用时限制JavaScript的使用。它不是一个lib或gem,而是一组实现这一目标的功能。有趣的是,也有适用于其他服务器端框架的Hotwire,比如Django。在本教程中,我们将看到如何在Rails中使用它。

什么是Hotwire

因此,Hotwire是一套技术。

  • 涡轮,本身是由:

    • Turbo Drive:每个链接都不会在点击时触发全页面重载。相反,只有<body> 标签内的HTML会被替换。

    • 涡轮框架:将把一个页面分解成可以单独更新的内容片段。在Turbo Frames之前,一个页面=一个URL。

    • 涡轮流:与涡轮框架的想法相同,但范围更广,我们将看到如何。

    • Turbo Native:针对移动设备--在本教程中不涉及。

  • 刺激剂:

    • Stimulus是一个微小的JS工具,它不渲染任何HTML(与大多数JS前端框架相反),而是在现有的HTML之上增加一些响应性。

Rails + Hotwire教程的先决条件

$> ruby -v  
ruby 3.0.0p0 // you need at least version 3 here  
$> bundle -v  
Bundler version 2.2.11  
$> npm -v  
8.3.0 // you need at least version 7.1 here  
$> yarn -v  
1.22.10

创建新的Rails项目,从头开始

在你的shell中键入.NET技术。

mkdir railshotwire && cd railshotwire 
echo "source 'https://rubygems.org'" > Gemfile  
echo "gem 'rails', '~> 7.0.0'" >> Gemfile  
bundle install  
bundle exec rails new . --force -d=postgresql  

创建简单的文件(目前没有Hotwire:)。

继续在你的shell中键入 。

  # Create a default controller
  echo "class HomeController < ApplicationController" > app/controllers/home_controller.rb
  echo "end" >> app/controllers/home_controller.rb

  # Create another controller
  echo "class OtherController < ApplicationController" > app/controllers/other_controller.rb
  echo "end" >> app/controllers/other_controller.rb

  # Create routes
  echo "Rails.application.routes.draw do" > config/routes.rb
  echo '  get "home/index"' >> config/routes.rb
  echo '  get "other/index"' >> config/routes.rb
  echo '  root to: "home#index"' >> config/routes.rb
  echo 'end' >> config/routes.rb

  # Create a default view
  mkdir app/views/home
  echo '<h1>This is home</h1>' > app/views/home/index.html.erb
  echo '<div><%= link_to "go to other page", other_index_path %></div>' >> app/views/home/index.html.erb
    
    # Create another view
  mkdir app/views/other
  echo '<h1>This is another page</h1>' > app/views/other/index.html.erb
  echo '<div><%= link_to "go to home page", root_path %></div>' >> app/views/other/index.html.erb

  
  # Create database and schema.rb
  bin/rails db:create
  bin/rails db:migrate
  

很好!运行

bin/rails s

并打开你的浏览器,在本地看到你的应用程序。你应该看到像这样的东西。

localhost

localhost

热线:涡轮驱动器

打开你的浏览器DevTools的 "网络 "标签。

现在通过点击链接从主页导航到另一个页面。你能注意到什么?导航是通过XHR实现的。每次导航时都不重新加载CSS,也不重新加载JavaScript。事实上,唯一被重新加载的是HTMLbody 标签内的DOM差异。

turbo drive

涡轮驱动

为了什么?

  • 根据我们的经验,感知到的性能差距绝对是巨大的,每次点击都会立即响应,即使是已部署的、可生产的应用程序。这意味着不仅是在本地主机上,在你的电脑上,这些改进可能不会被注意到。
  • 然而,这也伴随着一些问题,如DOM闪烁,更少的可访问性,和一些更奇怪的行为。

Turbo Drive非常强大,但需要时间和耐心来掌握它。

Hotwire : 涡轮框架

涡轮框架允许开发者将当前页面分割成若干块,当新的数据来自服务器时,这些块可以被单独更新。

涡轮框架的经典用例有::

  • 内联版
  • 标签式的内容
  • 搜索、排序和过滤数据

让我们看一个例子。

像这样改变app/controllers/home_controller.rb

class HomeController < ApplicationController

  # @route GET /turbo_frame_form 
  def turbo_frame_form
  end

  # @route POST /turbo_frame_submit 
  def turbo_frame_submit
    extracted_anynumber = params[:any][:anynumber]
    render :turbo_frame_form, status: :ok, locals: {anynumber: extracted_anynumber, comment: 'turbo_frame_submit ok' }
  end
  
end

用这个内容添加app/views/home/turbo_frame_form.html.erb

<section>

    <%= turbo_frame_tag 'anyframe' do %>
            
      <div>
          <h2>Frame view</h2>
          <%= form_with scope: :any, url: turbo_frame_submit_path, local: true do |form| %>
              <%= form.label :anynumber, 'Type an integer (odd or even)', 'class' => 'my-0  d-inline'  %>
              <%= form.text_field :anynumber, type: 'number', 'required' => 'true', 'value' => "#{local_assigns[:anynumber] || 0}",  'aria-describedby' => 'anynumber' %>
              <%= form.submit 'Submit this number', 'id' => 'submit-number' %>
          <% end %>
      </div>
      <div>
        <h2>Data of the view</h2>
        <pre style="font-size: .7rem;"><%= JSON.pretty_generate(local_assigns) %></pre> 
      </div>
      
    <% end %>

</section>

然后像这样改变你的routes.rb

Rails.application.routes.draw do
  get 'home/index'
  get 'other/index'

  get '/home/turbo_frame_form' => 'home#turbo_frame_form', as: 'turbo_frame_form'
  post '/home/turbo_frame_submit' => 'home#turbo_frame_submit', as: 'turbo_frame_submit'


  root to: "home#index"
end

最后像这样改变主视图,在app/views/home/index.html.erb

<h1>This is home</h1>
<div><%= link_to "go to other page", other_index_path %></div>

<%= turbo_frame_tag 'anyframe' do %>        
  <div>
      <h2>Home view</h2>
      <%= form_with scope: :any, url: turbo_frame_submit_path, local: true do |form| %>
          <%= form.label :anynumber, 'Type an integer (odd or even)', 'class' => 'my-0  d-inline'  %>
          <%= form.text_field :anynumber, type: 'number', 'required' => 'true', 'value' => "#{local_assigns[:anynumber] || 0}",  'aria-describedby' => 'anynumber' %>
          <%= form.submit 'Submit this number', 'id' => 'submit-number' %>
      <% end %>
  <div>
<% end %>

重新启动你的本地网络服务器,刷新你的本地浏览器,你应该可以在第一时间看到这个。

default view

默认视图

现在在字段中输入任何数字,并提交表格。像这样的东西应该出现。

Turbo Frame in action

运行中的涡轮框架

只有框架部分发生了变化,第一个标题和第一个链接并没有移动。

热线:涡轮流

理论

Turbo Streams通过WebSocket、SSE或响应表单提交来传递页面变化,只需使用HTML和一组类似CRUD的操作。

换句话说,你可以:

  • 在响应POST/PUT/PATCH/DELETE动作时,更新一个HTML块(GET不工作)
  • 向所有用户广播一个变化,而不需要刷新任何浏览器。

最简单的例子

像这样改变app/controllers/other_controller.rb

class OtherController < ApplicationController

  def post_something
    respond_to do |format|
      format.turbo_stream {  }
    end
  end

end

然后像这样改变你的routes.rb

Rails.application.routes.draw do
  get 'home/index'
  get 'other/index'

  get '/home/turbo_frame_form' => 'home#turbo_frame_form', as: 'turbo_frame_form'
  post '/home/turbo_frame_submit' => 'home#turbo_frame_submit', as: 'turbo_frame_submit'

  # Add this line below
  post '/other/post_something' => 'other#post_something', as: 'post_something'

  root to: "home#index"
end

很好!现在每次到达'/other/post_something'端点时,rails会自动尝试找到app/views/other/post_something.turbo_stream.erb模板。

添加app/views/other/post_something.turbo_stream.erb ,内容如下。

<turbo-stream action="append" target="messages">
  <template>
    <div id="message_1">This changes the existing message!</div>
  </template>
</turbo-stream>

很好 !这意味着响应将尝试附加(见 "action "属性)id为 "messages "的turbo-frame模板。

最后将app/views/other/index.html.erb ,内容如下。

<h1>This is another page</h1>
<div><%= link_to "go to home page", root_path %></div>

<div style="margin-top: 3rem;">
  <%= form_with scope: :any, url: post_something_path do |form| %>
      <%= form.submit 'Post something' %>
  <% end %>
  <turbo-frame id="messages">
    <div>An empty message</div>
  </turbo-frame>
</div>

现在启动你的本地网络服务器

bin/rails s

打开你的网络浏览器,并进入 "其他 "页面。

Other page

其他页面

点击 "Post something "按钮。

Line added

增加一行

很好 !Turbo Streams允许开发者在提交后附加一条信息,而不需要重新加载页面。这发生得相当无缝。

如果我们想 "替换 "信息,而不是 "添加 "新的信息呢?

改变app/views/other/post_something.turbo_stream.erb ,内容如下。

<turbo-stream action="replace" target="messages">
  <template>
    <div id="message_1">This changes the existing message!</div>
  </template>
</turbo-stream>

只有第1行的属性 "action "发生了变化:action现在被替换了。

你可以在你的本地浏览器中检查一切是否正常工作。

Hotwire : Turbo native

Turbo native有助于在移动设备上构建应用程序,这里将不涉及。但是我们不会删除这一段,这样你就会完全知道Turbo Native是Hotwire的一部分 :)

刺激

Hotwire实际上一个JS工具,好像Hotwire的目标就是要避免它。原因是Turbo-*工具不足以覆盖所有场景。有一些情况下,仍然需要JS。为了限制对它的需求,Stimulus认为HTML是唯一的真理来源(对于那些了解Redux的人来说)。

像这样改变app/views/other/index.html.erb

<h1>This is another page</h1>
<div><%= link_to "go to home page", root_path %></div>

<div style="margin-top: 2rem;">
  <%= form_with scope: :any, url: post_something_path do |form| %>
      <%= form.submit 'Post something' %>
  <% end %>
  <turbo-frame id="messages">
    <div>An empty message</div>
  </turbo-frame>
</div>

<div style="margin-top: 2rem;">
  <h2>Stimulus</h2>  
  <div data-controller="hello">
    <input data-hello-target="name" type="text">
    <button data-action="click->hello#greet">
      Greet
    </button>
    <span data-hello-target="output">
    </span>
  </div>
</div>

像这样修改app/javascript/controllers/hello_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

在localhost打开你的浏览器,进入 "其他 "页面,然后玩这个例子。

这个例子是直接从文档中摘取的。这足以让我们理解Stimulus的目标:在前端增加互动性。

结论性的想法

Hotwire很不错。最好的消息是,用户体验--也就是你的终端用户,推动了现在的炒作和创新。

他们说,减少JavaScript,增加响应性。

这部分是正确的。在实践中,需要一些时间来真正正确地处理它。我们遇到了一些关于可访问性的问题和一些复杂的用例,Stimulus没有能力解决。除此以外,它可能会给你节省一些jQuery/vanillaJS的麻烦。对于MVP来说,Hotwire可以被看作是可有可无的,因为它增加了一层复杂性--这就是为什么我们在bootrails中跳过它。