Toidicode.com

Toidicode.com

BASIC TO ADVANCE

Bài 7: Blade template trong Laravel 13 (Phần 2)

Ở bài trước các trang project của TaskFlow đã có layout chung bằng @extends, nhưng giao diện còn trơn. Tiếp tục với series, bài này mình sẽ giới thiệu với mọi người về Blade component trong Laravel 13, dùng nó để viết lại layout, rồi thêm Tailwind CSS qua Vite cho TaskFlow.

1. Blade component là gì?

Component là một mảnh giao diện dùng lại được (thông báo, card, cả layout), được gọi trong view giống như thẻ HTML với tiền tố x-, VD <x-alert>. Có hai loại: anonymous component chỉ gồm một file Blade, và class component có thêm một class PHP đi kèm.

Anonymous component là các file nằm trong thư mục resources/views/components, Laravel tự tìm nên không cần đăng ký. Các bạn tạo file bằng tay hoặc dùng lệnh artisan với option --view:

php artisan make:component alert --view

Lệnh trên tạo file resources/views/components/alert.blade.php, gọi bằng <x-alert>.

Nội dung đặt giữa thẻ mở và thẻ đóng của component được truyền vào biến $slot, VD <x-alert>Chưa có project nào.</x-alert> sẽ in ra thẻ div bên dưới với nội dung đó ở vị trí {{ $slot }}:

- resources/views/components/alert.blade.php

<div class="rounded-lg p-4">
    {{ $slot }}
</div>

2. Props và attributes.

Dữ liệu truyền vào component qua attribute như HTML. Giá trị cố định viết bình thường (type="error"), còn biến hoặc biểu thức PHP thì thêm dấu : phía trước (:type="$type").

Trong anonymous component, các bạn khai báo những attribute là dữ liệu (gọi là props) bằng directive @props ở đầu file, key là tên prop, value là giá trị mặc định. Các attribute còn lại (VD class, id) được gom vào biến $attributes để in ra thẻ gốc của component.

VD: Thêm prop type cho alert, mặc định là info.

- resources/views/components/alert.blade.php

@props(['type' => 'info'])

<div {{ $attributes->class([
    'rounded-lg p-4',
    'bg-blue-50 text-blue-800' => $type === 'info',
    'bg-red-50 text-red-800' => $type === 'error',
]) }}>
    {{ $slot }}
</div>
<x-alert type="error" class="mb-4">Không tìm thấy project.</x-alert>

{{-- Output:
<div class="rounded-lg p-4 bg-red-50 text-red-800 mb-4">
    Không tìm thấy project.
</div>
--}}

Trong đó:

  • $attributes->class([...]) gộp các class mặc định với class truyền vào: key là class, value là điều kiện để thêm class đó, phần tử không có key luôn được thêm.
  • Nếu không cần điều kiện, các bạn dùng $attributes->merge(['class' => '...']). Với attribute khác class, giá trị truyền vào sẽ ghi đè giá trị mặc định.

3. Class component.

Khi component cần xử lý thêm, các bạn dùng class component, tạo bằng lệnh trên nhưng bỏ option --view:

php artisan make:component ProjectCard

Lệnh này tạo hai file: class app/View/Components/ProjectCard.php và view resources/views/components/project-card.blade.php, gọi bằng <x-project-card>. Props là các tham số của constructor, mọi property và method public đều dùng được trong view.

- app/View/Components/ProjectCard.php

<?php

namespace App\View\Components;

use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;

class ProjectCard extends Component
{
    public function __construct(
        public array $project,
    ) {}

    public function taskCount(): int
    {
        return count($this->project['tasks']);
    }

    public function render(): View|Closure|string
    {
        return view('components.project-card');
    }
}

- resources/views/components/project-card.blade.php

<div {{ $attributes->merge(['class' => 'rounded-lg border border-gray-200 bg-white p-4']) }}>
    <a href="{{ route('projects.show', ['id' => $project['id']]) }}" class="font-semibold text-blue-600">
        {{ $project['name'] }}
    </a>
    <p class="text-sm text-gray-500">{{ $taskCount() }} task</p>
</div>

Vòng lặp ở trang danh sách project giờ gọn hơn nhiều:

@forelse ($projects as $project)
    <x-project-card :project="$project" class="mb-3" />
@empty
    <x-alert>Chưa có project nào.</x-alert>
@endforelse

{{-- Output: hai card "Website toidicode.com" (2 task) và "TaskFlow API" (0 task) --}}

4. Slot và layout component.

Ngoài $slot mặc định, component có thể nhận thêm các named slot bằng thẻ <x-slot:tên>, nội dung của nó vào biến $tên trong component. Nhờ vậy layout cũng viết được bằng component: $slot thay cho @yield('content'), named slot hoặc prop thay cho các @section khác.

VD: Mình thay layouts/app.blade.php của bài trước bằng component layout (và xóa file cũ đi).

- resources/views/components/layout.blade.php

@props(['title'])

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ $title }} - {{ config('app.name') }}</title>

    @fonts
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-50 font-sans text-gray-900">
    @include('partials.nav')

    <main class="mx-auto max-w-3xl p-6">
        {{ $slot }}
    </main>
</body>
</html>

Thanh menu partials/nav cũng thêm class Tailwind và bỏ dấu |:

- resources/views/partials/nav.blade.php

<nav class="flex gap-4 bg-white px-6 py-4 shadow-sm">
    <a href="{{ route('home') }}">Trang chủ</a>
    <a href="{{ route('projects.index') }}">Project</a>
</nav>

@fonts, @vite mình sẽ giải thích ở phần 5. Các view project giờ bọc nội dung trong <x-layout>, $title truyền bằng attribute hoặc bằng named slot đều được:

- resources/views/projects/index.blade.php

<x-layout title="Danh sách project">
    <h1 class="mb-4 text-2xl font-bold">Danh sách project</h1>

    {{-- vòng lặp @forelse với x-project-card như ở phần 3 --}}
</x-layout>

- resources/views/projects/show.blade.php

<x-layout>
    <x-slot:title>{{ $project['name'] ?? 'Không tìm thấy project' }}</x-slot>

    @if ($project === null)
        <x-alert type="error">Không tìm thấy project.</x-alert>
    @else
        {{-- giữ nguyên như bài trước, thêm class Tailwind --}}
    @endif
</x-layout>

{{-- /projects/1 => <title>Website toidicode.com - TaskFlow</title> --}}

Cả hai cách làm layout đều hợp lệ, Laravel 13 vẫn hỗ trợ đầy đủ @extends.

Tuy nhiên component phổ biến hơn vì layout, alert, card dùng chung một cú pháp giống HTML, dữ liệu vào khai báo rõ bằng props (component không tự thấy biến của view cha như @include) và có $attributes để tùy biến từ bên ngoài.

5. Vite và Tailwind CSS.

Vite là công cụ build CSS, JavaScript cho front-end. Project Laravel 13 mới tạo đã cấu hình sẵn Vite cùng Tailwind CSS v4: package khai báo trong package.json, vite.config.js khai báo hai file đầu vào resources/css/app.css, resources/js/app.js và plugin Tailwind. File CSS mặc định như sau:

- resources/css/app.css

@import 'tailwindcss';

@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';

@theme {
    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
        'Segoe UI Symbol', 'Noto Color Emoji';
}

@import 'tailwindcss' nạp Tailwind. Tailwind tự quét các file trong project để tìm class đang dùng như bg-red-50, p-4 và chỉ sinh CSS cho các class đó, @source thêm các thư mục cần quét. @theme đổi font mặc định font-sans sang Instrument Sans, font này được Vite tải về khi build và nạp vào trang bằng directive @fonts.

Directive @vite trong layout in ra thẻ <link>, <script> cho các file đầu vào. Installer đã chạy sẵn npm install ở Bài 2 (clone project về thì chạy lại), sau đó có hai cách chạy Vite:

# chạy dev server, tự cập nhật trình duyệt mỗi khi sửa file
npm run dev

# build file CSS, JS cho production vào public/build
npm run build
# public/build/assets/app-C63f_hYW.css   38.47 kB │ gzip: 9.55 kB
  • Khi npm run dev đang chạy, @vite trỏ đến dev server (cổng 5173), VD <script type="module" src="http://localhost:5173/@vite/client">. Lệnh composer run dev ở Bài 2 đã chạy sẵn lệnh này cùng web server.
  • Sau npm run build, @vite đọc public/build/manifest.json để in ra file đã build, VD /build/assets/app-C63f_hYW.css. Mã hash đổi theo nội dung nên trình duyệt không dùng nhầm cache cũ.

Chú ý: Nếu chưa build và cũng không chạy dev server, trang có @vite sẽ báo lỗi Vite manifest not found. Thư mục public/build nằm trong .gitignore, khi deploy cần chạy lại npm run build (Bài 32).

6. Lời kết.

Như vậy chúng ta đã tìm hiểu xong phần 2 của Blade template trong Laravel 13: component, props, slot, layout component và Vite + Tailwind. Các bạn có thể xem thêm tại tài liệu chính thức. Đến đây TaskFlow mới chỉ hiển thị dữ liệu, bài sau mình sẽ giới thiệu về request và response để đọc dữ liệu người dùng gửi lên và làm form tạo project.

Đăng ký nhận tin.

Chúng tôi chỉ gửi tối đa 2 lần trên 1 tháng. Tuyên bố không spam mail!

Vũ Thanh Tài

About author
The best way to learn is to share
Xem tất cả bài đăng

0 Comments

Bài viết chưa có ai bình luận, hãy là người đầu tiên đi bạn!

Bình luận

Captcha