Lập trình · 22/09/2026

Học Laravel 13 – Bài 06: Blade, Component và Vite trong Laravel 13

Bài 6 — Blade, Component và Vite: biến trang TaskFlow đơn giản thành giao diện có layout dùng lại, component trạng thái rỗng và CSS được build. Tiếp nối bài 5, giữ nguyên controller, middleware và dữ liệu; chỉ thay cách trình bày. Chưa có danh sách task thật nên không dựng số liệu giả để làm dashboard trông hoàn chỉnh.

Blade, Component và Vite trong Laravel 13

Bài 6 — Blade, Component và Vite: biến trang TaskFlow đơn giản thành giao diện có layout dùng lại, component trạng thái rỗng và CSS được build. Tiếp nối bài 5, giữ nguyên controller, middleware và dữ liệu; chỉ thay cách trình bày. Chưa có danh sách task thật nên không dựng số liệu giả để làm dashboard trông hoàn chỉnh.

1. Chia giao diện theo trách nhiệm

Layout giữ document HTML, title, điều hướng và khai báo asset. View trang chọn nội dung cụ thể. Component empty-state diễn đạt một phần UI có thể xuất hiện ở nhiều màn hình. Chỉ tách khi có trách nhiệm rõ ràng; không cần biến từng thẻ HTML thành một component riêng.

Blade chạy phía server và tạo HTML; Vite xử lý tài nguyên frontend. Không cần JavaScript để một Blade component hiển thị. Theo tài liệu Blade, component có thể nhận props, attributes và slot; ở đây chúng ta dùng anonymous component, không cần class PHP riêng.

2. Tạo layout dùng lại

Tạo resources/views/components/layout.blade.php:

@props(['title' => 'TaskFlow'])
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ $title }}</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    <a href="#main-content">Skip to content</a>
    <nav aria-label="Main"><a href="{{ route('taskflow.overview') }}">TaskFlow overview</a></nav>
    <main id="main-content" class="taskflow-shell">{{ $slot }}</main>
</body>
</html>

title có giá trị mặc định, còn slot là phần HTML do view gọi component cung cấp. Link bỏ qua điều hướng dẫn tới main-content; route() giữ liên kết phụ thuộc tên route thay vì hardcode /taskflow. Component slot là nội dung template đã render, không phải cơ chế làm sạch HTML tùy ý từ người dùng.

3. Tạo trạng thái rỗng rồi ghép trang

Tạo resources/views/components/empty-state.blade.php:

@props(['title'])
<section {{ $attributes->class(['taskflow-empty']) }}>
    <h2>{{ $title }}</h2>
    <p>{{ $slot }}</p>
</section>

Thay toàn bộ resources/views/taskflow/overview.blade.php bằng:

<x-layout :title="$projectName">
    <h1>{{ $projectName }}</h1>
    <p>Milestone: {{ $milestone }}</p>
    <x-empty-state title="No tasks yet" data-testid="empty-state">
        No tasks have been loaded yet.
    </x-empty-state>
</x-layout>

Dấu hai chấm trong :title="$projectName" truyền giá trị biểu thức PHP; title="No tasks yet" truyền chuỗi cố định. @props lấy thuộc tính làm dữ liệu; các thuộc tính còn lại như data-testid nằm trong attribute bag. class() ghép lớp mặc định với lớp được truyền thêm. Chỉ truyền tên thuộc tính do lập trình viên kiểm soát, không biến đầu vào người dùng thành event handler.

Dùng {{ }} cho tên và nội dung văn bản không tin cậy. Không đổi sang {!! !!} để cho phép người dùng tự nhập HTML. Escape HTML cũng không thay thế kiểm tra URL, quyền truy cập hoặc encoding trong ngữ cảnh JavaScript; mỗi ngữ cảnh cần cách xử lý phù hợp.

4. Thêm CSS và build bằng Vite

Thêm CSS sau vào resources/css/app.css, giữ các khai báo sẵn có của skeleton:

body { font-family: system-ui, sans-serif; line-height: 1.6; color: #172033; background: #f4f7fb; padding: 1rem; }
a { color: #1549ad; text-decoration: underline; }
a:focus-visible { outline: 3px solid #1549ad; outline-offset: 3px; }
.taskflow-shell { max-width: 60rem; margin: 2rem auto; }
.taskflow-shell h1 { font-size: 2rem; font-weight: 700; }
.taskflow-empty { margin-top: 1rem; padding: 1.5rem; border: 1px solid #9aa8ba; border-radius: .75rem; background: white; }
.taskflow-empty h2 { font-size: 1.25rem; font-weight: 600; }

vite.config.js của TaskFlow đã khai báo hai entry resources/css/app.css và resources/js/app.js. Layout phải dùng đúng các entry đó. Không tham chiếu tên CSS có hash bằng tay vì tên có thể đổi sau mỗi build. Tích hợp Vite của Laravel dùng directive @vite để chọn tài nguyên phù hợp.

npm run build
php artisan test
php artisan serve --host=127.0.0.1 --port=8000

Build tạo manifest trong public/build. Khi chỉnh giao diện liên tục, có thể chạy npm run dev ở terminal riêng. Không cần chạy cả dev server và build để phục vụ cùng một chế độ; hiểu mình đang kiểm tra asset phát triển hay asset build. Trong production, không dùng Vite dev server làm máy chủ tài nguyên công khai.

5. Kiểm thử output và giới hạn của test

Tạo tests/Feature/TaskFlowBladeTest.php:

<?php
namespace Tests\Feature;

use Tests\TestCase;

class TaskFlowBladeTest extends TestCase
{
    public function test_layout_escapes_the_name_and_renders_component_attributes(): void
    {
        $this->withoutVite();
        config(['taskflow.name' => '<script>alert(1)</script>']);

        $this->get('/taskflow')->assertOk()
            ->assertSee('&lt;script&gt;alert(1)&lt;/script&gt;', escape: false)
            ->assertDontSee('<script>alert(1)</script>', escape: false)
            ->assertSee('data-testid="empty-state"', escape: false)
            ->assertSeeText('No tasks yet')
            ->assertSee('id="main-content"', escape: false);
    }
}

withoutVite() chỉ áp dụng trong test này để tập trung vào HTML, không yêu cầu manifest cho chính test đó. Những test HTTP khác vẫn dùng Vite thực nên chạy build trước toàn bộ suite. Hai assertion về script so sánh HTML thô; escape: false ở assertion không có nghĩa tắt escape trong Blade.

Kết quả đã kiểm chứng: build Vite thành công, toàn bộ suite pass 9 test với 24 assertions. Cảnh báo fontaine tùy chọn từ skeleton vẫn có nhưng không làm build thất bại. Test này không chạy JavaScript, không đo độ tương phản và không xác nhận asset trả 200 qua web server. Trên trình duyệt, kiểm tra Network, focus bàn phím, màn hình hẹp và lỗi console riêng.

6. Checklist tự thực hành

  • Thử thêm class vào x-empty-state và kiểm tra class mặc định vẫn tồn tại.
  • Đổi tên hiển thị bằng config; title và h1 phải cùng nhận giá trị, chuỗi có dấu ngoặc nhọn không trở thành HTML thực thi.
  • Nếu báo component not found, so tên x-empty-state với file components/empty-state.blade.php.
  • Nếu báo manifest missing, build trong đúng project; nếu tìm dev server đã tắt, kiểm tra file hot và tiến trình Vite.
  • Nếu layout cũ còn xuất hiện sau sửa, kiểm tra đúng server và dùng view:clear khi cần, không xóa vendor.

Điều hướng: Bài 5 · Lộ trình. Bài tiếp theo thêm form, validation và CSRF trước khi thiết kế database.

Điều hướng khóa học Laravel 13

Bài trước (05) · Bài sau (07) · Mục lục trọn bộ 37 bài

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.