<body>
mà không reload toàn bộ trang<head>
một cách thông minh để giữ lại các tài nguyên hiện tại<%# app/views/products/index.html.erb %><h1>Sản Phẩm</h1>
<%= turbo_frame_tag "product_list" do %> <div class="products"> <%= render @products %> </div><% end %>
<%= turbo_frame_tag "product_detail" %>
<%# app/views/products/_product.html.erb %><div class="product"> <%= link_to product.name, product, data: { turbo_frame: "product_detail" } %></div>
<%# app/views/products/show.html.erb %><%= turbo_frame_tag "product_detail" do %> <div class="product-detail"> <h2><%= @product.name %></h2> <p><%= @product.description %></p> <p>Giá: <%= @product.price %></p> </div><% end %>
# app/controllers/comments_controller.rbdef create @comment = @post.comments.create!(comment_params) respond_to do |format| format.html { redirect_to @post } format.turbo_stream endend
<%# app/views/comments/create.turbo_stream.erb %><turbo-stream action="append" target="comments"> <%= render partial: "comments/comment", locals: { comment: @comment } %></turbo-stream><turbo-stream action="update" target="new_comment"> <%= render partial: "comments/form", locals: { post: @post, comment: Comment.new } %></turbo-stream>
class Comment < ApplicationRecord belongs_to :post
after_create_commit -> { broadcast_append_to post }end
<%= turbo_stream_from @post %><div id="comments"> <%= render @post.comments %></div>
<%# app/views/dashboard/show.html.erb %><%= turbo_frame_tag "dashboard", morph: true do %> <div class="dashboard"> <div class="metrics"> <%= render "metrics", data: @metrics %> </div> <div class="recent-activity"> <%= render "activities", activities: @activities %> </div> </div><% end %>
<%= link_to "Refresh", dashboard_path, data: { turbo_frame: "dashboard" } %>
# app/controllers/dashboard_controller.rbdef show @metrics = current_user.metrics @activities = current_user.recent_activities(25)end
Kỹ Thuật | Khi Nào Dùng |
---|---|
Turbo Drive | - Cải thiện điều hướng ứng dụng đa trang - Ít chỉnh sửa code hiện có |
Turbo Frames | - Cập nhật từng phần nhỏ - Master-detail, Component độc lập |
Turbo Streams | - Tính năng realtime như chat, thông báo - Cập nhật nhiều phần cùng lúc |
Turbo Morph | - UI phức tạp cần update tối ưu - Giữ trạng thái DOM khi cập nhật |
<%# app/views/posts/show.html.erb %><article class="post"> <h1><%= @post.title %></h1> <div class="content"> <%= @post.content %> </div>
<h2>Comments (<span id="comment-count"><%= @post.comments.count %></span>)</h2> <%= turbo_stream_from @post, :comments %>
<div id="comments" class="comments"> <%= render @post.comments %> </div>
<%= turbo_frame_tag "new_comment" do %> <%= render "comments/form", post: @post, comment: Comment.new %> <% end %></article>
<%# app/views/comments/_form.html.erb %><%= form_with model: [post, comment], data: { controller: "reset-form" } do |form| %> <%= form.text_area :content, placeholder: "Thêm bình luận..." %> <%= form.submit "Gửi" %><% end %>
class CommentsController < ApplicationController before_action :set_post
def create @comment = @post.comments.build(comment_params) @comment.user = current_user
respond_to do |format| if @comment.save format.turbo_stream else format.turbo_stream { render turbo_stream: turbo_stream.replace("new_comment", partial: "comments/form", locals: { post: @post, comment: @comment }) } end end end
private
def set_post @post = Post.find(params[:post_id]) end
def comment_params params.require(:comment).permit(:content) endend
<%# app/views/comments/create.turbo_stream.erb %><turbo-stream action="append" target="comments"> <%= render "comments/comment", comment: @comment %></turbo-stream><turbo-stream action="update" target="comment-count"> <%= @post.comments.count %></turbo-stream><turbo-stream action="replace" target="new_comment"> <%= render "comments/form", post: @post, comment: Comment.new %></turbo-stream>
class Comment < ApplicationRecord belongs_to :post belongs_to :user
broadcasts_to ->(comment) { [comment.post, :comments] }, inserts_by: :appendend
Yếu Tố | Lời Khuyên |
---|---|
Server Load | Nhiều yêu cầu cập nhật nhanh chóng có thể làm tăng tải server. Sử dụng cache, phân trang để tối ưu. |
Kích Thước DOM | Trang lớn nhiều Turbo Frames có thể gây tiêu tốn bộ nhớ. Turbo Morph giúp giảm áp lực này. |
Kết Nối WebSocket | Turbo Streams dựa vào ActionCable cần quản lý kết nối, đặc biệt trong môi trường production. |
Tăng Cường Tiến Bộ | Đảm bảo ứng dụng vẫn hoạt động tốt khi JavaScript bị tắt hoặc bị lỗi. |
rails new taskmanager --css=tailwindcd taskmanagerrails g scaffold Task name:string description:text completed:booleanrails db:migrate
Rails.application.routes.draw do resources :tasks do member do patch :toggle end end
root "tasks#index"end
def toggle @task = Task.find(params[:id]) @task.update(completed: !@task.completed) respond_to do |format| format.html { redirect_to tasks_path } format.turbo_stream endend
<%# app/views/tasks/toggle.turbo_stream.erb %><turbo-stream action="replace" target="<%= dom_id(@task) %>"> <%= render partial: "task", locals: { task: @task } %></turbo-stream><turbo-stream action="update" target="task-count"> <%= Task.count %> tasks, <%= Task.where(completed: true).count %> completed</turbo-stream>
<%= turbo_frame_tag dom_id(task) do %> <div class="task <%= task.completed? ? 'completed' : '' %>"> <div class="flex items-center p-4 border-b"> <%= link_to toggle_task_path(task), data: { turbo_method: :patch }, class: "mr-2" do %> <div class="w-6 h-6 border-2 rounded-full flex items-center justify-center <%= task.completed? ? 'bg-green-500 border-green-600' : 'border-gray-400' %>"> <% if task.completed? %> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> </svg> <% end %> </div> <% end %> <div class="<%= task.completed? ? 'line-through text-gray-500' : '' %>"> <h3 class="font-medium"><%= task.name %></h3> <p class="text-sm text-gray-600"><%= task.description %></p> </div> <div class="ml-auto"> <%= link_to "Sửa", edit_task_path(task), class: "text-blue-500" %> </div> </div> </div><% end %>
<div class="container mx-auto p-4"> <h1 class="text-2xl font-bold mb-4">Quản Lý Công Việc</h1> <div id="task-count" class="mb-4"> <%= Task.count %> tasks, <%= Task.where(completed: true).count %> completed </div> <div id="tasks"> <%= render @tasks %> </div> <%= turbo_frame_tag "new_task", src: new_task_path, loading: "lazy" %> <div class="mt-4"> <%= link_to "Tạo Công Việc Mới", new_task_path, class: "px-4 py-2 bg-blue-500 text-white rounded", data: { turbo_frame: "new_task" } %> </div></div>
<%= turbo_frame_tag "new_task" do %> <div class="bg-gray-100 p-4 rounded mb-4"> <h2 class="text-xl font-semibold mb-2">Công Việc Mới</h2> <%= render "form", task: @task %> <div class="mt-2"> <%= link_to "Hủy", "#", class: "text-gray-500", data: { controller: "turbo", action: "click->turbo#frameRemove" } %> </div> </div><% end %>