Laravel Livewire 3 ในปี 2026: สร้างแอปพลิเคชัน Reactive และคำถามสัมภาษณ์งาน

คู่มือฉบับสมบูรณ์สำหรับ Laravel Livewire 3 เพื่อสร้างแอปพลิเคชัน reactive ด้วย PHP ล้วน เรียนรู้เกี่ยวกับ component, validation แบบ real-time, การรวม Alpine.js และเตรียมตัวสำหรับสัมภาษณ์ทางเทคนิค

Laravel Livewire 3 reactive components และคำถามสัมภาษณ์

Laravel Livewire 3 เปลี่ยนแปลงวิธีที่นักพัฒนา PHP สร้าง interface ที่ reactive และ dynamic โดยไม่ต้องเขียน JavaScript ในฐานะวิวัฒนาการครั้งใหญ่ของ framework นี้ Livewire 3 นำเสนอการรวม Alpine.js, ประสิทธิภาพที่ดีขึ้นผ่าน morphing, และ syntax ของ component ที่สะอาดขึ้นซึ่งทำให้แอปพลิเคชัน real-time รู้สึกเป็นธรรมชาติใน ecosystem ของ Laravel

การเปลี่ยนแปลงสำคัญใน Livewire 3

Livewire 3 ใช้ Alpine.js ภายในและต้องการ directive @livewireStyles และ @livewireScripts ใน layout directive wire:model ตอนนี้ใช้ lazy binding เป็นค่าเริ่มต้น—ใช้ wire:model.live สำหรับการอัปเดต real-time

ทำความเข้าใจสถาปัตยกรรม Livewire 3 และ Reactive Binding

Livewire ทำงานผ่านแนวคิดที่เรียบง่ายแต่ทรงพลัง: component PHP ที่ render ใหม่บน server และส่ง HTML diff กลับไปยัง browser แต่ละ component รักษา state ระหว่าง request ผ่านกลไก serialization ที่เก็บรักษาค่า property

สถาปัตยกรรมนี้แตกต่างอย่างพื้นฐานจาก reactivity ที่ใช้ JavaScript แทนที่จะรักษา virtual DOM บน client Livewire จะ serialize state ของ component ส่งไปพร้อมกับแต่ละ request และ morph DOM ตาม response จาก server แนวทางนี้รักษา business logic ไว้ใน PHP ในขณะที่มอบประสบการณ์ reactive ที่ผู้ใช้คาดหวัง

app/Livewire/SearchUsers.phpphp
<?php

namespace App\Livewire;

use App\Models\User;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;

class SearchUsers extends Component
{
    use WithPagination;

    #[Url] // ซิงค์ property กับ URL query string
    public string $search = '';

    #[Url]
    public string $role = '';

    public function updatedSearch(): void
    {
        // รีเซ็ต pagination เมื่อการค้นหาเปลี่ยน
        $this->resetPage();
    }

    public function render()
    {
        return view('livewire.search-users', [
            'users' => User::query()
                ->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"))
                ->when($this->role, fn($q) => $q->where('role', $this->role))
                ->paginate(10),
        ]);
    }
}

Attribute #[Url] จะซิงค์ property กับ URL query parameters โดยอัตโนมัติ ทำให้สามารถแชร์ view ที่กรองแล้วได้โดยไม่ต้องเขียนโค้ดเพิ่ม Hook lifecycle updatedSearch จะถูกเรียกเมื่อ property search เปลี่ยน โดยรีเซ็ต pagination ไปยังหน้าหนึ่ง

Syntax ของ Component Livewire 3 พร้อม PHP 8 Attributes

Livewire 3 นำ PHP 8 attributes มาใช้สำหรับการตั้งค่า แทนที่แนวทางที่ใช้ method ก่อนหน้านี้ รูปแบบ declarative นี้ปรับปรุงความสามารถในการอ่านและลด boilerplate ใน component ต่างๆ

app/Livewire/CreatePost.phpphp
<?php

namespace App\Livewire;

use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Rule;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Computed;

class CreatePost extends Component
{
    #[Locked] // ไม่สามารถแก้ไขจาก frontend ได้
    public int $userId;

    #[Rule('required|min:5|max:200')]
    public string $title = '';

    #[Rule('required|min:50')]
    public string $content = '';

    #[Rule('nullable|array')]
    public array $tags = [];

    public function mount(): void
    {
        $this->userId = auth()->id();
    }

    #[Computed]
    public function wordCount(): int
    {
        return str_word_count($this->content);
    }

    public function save(): void
    {
        $validated = $this->validate();

        Post::create([
            'user_id' => $this->userId,
            ...$validated,
        ]);

        $this->reset(['title', 'content', 'tags']);
        $this->dispatch('post-created');
    }

    public function render()
    {
        return view('livewire.create-post');
    }
}

Attribute #[Locked] ป้องกันการจัดการจาก frontend สำหรับ property ที่ sensitive—สำคัญมากสำหรับความปลอดภัยเมื่อส่ง ID หรือ permission Attribute #[Rule] กำหนด validation แบบ inline ในขณะที่ #[Computed] จะ cache การคำนวณที่ใช้ทรัพยากรมากจนกว่า dependencies จะเปลี่ยน

ข้อควรพิจารณาด้านความปลอดภัย

ควรใช้ #[Locked] สำหรับ user ID, flag สิทธิ์, และข้อมูลใดๆ ที่ไม่ควรถูกแก้ไขจาก browser Livewire จะส่ง state ของ component ในแต่ละ request ทำให้ property ที่ไม่ได้รับการป้องกันเสี่ยงต่อการถูกแก้ไข

การ Validate Form แบบ Real-Time และ Feedback ผู้ใช้

Livewire โดดเด่นในการให้ feedback validation ทันทีโดยไม่ต้องโหลดหน้าใหม่ Framework จะ validate property เมื่อผู้ใช้พิมพ์ โดยแสดง error ควบคู่กับ field ของ form แบบ real-time

app/Livewire/RegistrationForm.phpphp
<?php

namespace App\Livewire;

use App\Models\User;
use Livewire\Component;
use Livewire\Attributes\Rule;
use Livewire\Attributes\Validate;
use Illuminate\Validation\Rules\Password;

class RegistrationForm extends Component
{
    #[Rule('required|email|unique:users,email')]
    public string $email = '';

    #[Rule('required|min:2|max:50')]
    public string $name = '';

    public string $password = '';
    public string $passwordConfirmation = '';

    public function rules(): array
    {
        return [
            'password' => [
                'required',
                'confirmed',
                Password::min(8)->mixedCase()->numbers(),
            ],
        ];
    }

    public function updated(string $property): void
    {
        // Validate property เดี่ยวเมื่อมีการเปลี่ยนแปลง
        $this->validateOnly($property);
    }

    public function register(): void
    {
        $validated = $this->validate();

        $user = User::create([
            'email' => $validated['email'],
            'name' => $validated['name'],
            'password' => bcrypt($validated['password']),
        ]);

        auth()->login($user);
        $this->redirect('/dashboard');
    }

    public function render()
    {
        return view('livewire.registration-form');
    }
}

Template Blade ที่สอดคล้องกันใช้ wire:model.blur เพื่อเรียก validation เมื่อผู้ใช้ออกจาก field โดยสร้างสมดุลระหว่างความ responsive และโหลดของ server:

blade
{{-- resources/views/livewire/registration-form.blade.php --}}
<form wire:submit="register" class="space-y-4">
    <div>
        <label for="email" class="block text-sm font-medium">อีเมล</label>
        <input 
            wire:model.blur="email" 
            type="email" 
            id="email"
            class="mt-1 block w-full rounded-md border-gray-300"
        >
        @error('email')
            <p class="mt-1 text-sm text-red-600">{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label for="password" class="block text-sm font-medium">รหัสผ่าน</label>
        <input 
            wire:model.blur="password" 
            type="password" 
            id="password"
            class="mt-1 block w-full rounded-md border-gray-300"
        >
        @error('password')
            <p class="mt-1 text-sm text-red-600">{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label for="passwordConfirmation" class="block text-sm font-medium">ยืนยันรหัสผ่าน</label>
        <input 
            wire:model.blur="passwordConfirmation" 
            type="password" 
            id="passwordConfirmation"
            class="mt-1 block w-full rounded-md border-gray-300"
        >
    </div>

    <button 
        type="submit" 
        class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md"
        wire:loading.attr="disabled"
    >
        <span wire:loading.remove>ลงทะเบียน</span>
        <span wire:loading>กำลังดำเนินการ...</span>
    </button>
</form>

Pattern นี้แสดงจุดแข็งของ Livewire: logic validation ฝั่ง server พร้อมความ responsive ฝั่ง client, loading states โดยไม่ต้องใช้ JavaScript, และการแยกที่ชัดเจนระหว่าง logic ของ component และการนำเสนอ

การสื่อสารระหว่าง Component Parent-Child ด้วย Events

Interface ที่ซับซ้อนมักต้องการ component Livewire หลายตัวที่สื่อสารกัน Livewire 3 มีระบบ event ที่ปรับปรุงแล้วสำหรับจุดประสงค์นี้ โดยใช้ pattern dispatch และ attribute #[On]

app/Livewire/TaskBoard.php - Component Parentphp
<?php

namespace App\Livewire;

use App\Models\Task;
use Livewire\Component;
use Livewire\Attributes\On;

class TaskBoard extends Component
{
    public array $columns = ['todo', 'in_progress', 'done'];

    #[On('task-moved')]
    public function handleTaskMoved(int $taskId, string $newStatus): void
    {
        Task::where('id', $taskId)->update(['status' => $newStatus]);
    }

    #[On('task-created')]
    public function refreshBoard(): void
    {
        // Component จะ render ใหม่โดยอัตโนมัติ
    }

    public function render()
    {
        return view('livewire.task-board', [
            'tasks' => Task::all()->groupBy('status'),
        ]);
    }
}
app/Livewire/TaskCard.php - Component Childphp
<?php

namespace App\Livewire;

use App\Models\Task;
use Livewire\Component;

class TaskCard extends Component
{
    public Task $task;

    public function moveToStatus(string $status): void
    {
        $this->dispatch('task-moved', taskId: $this->task->id, newStatus: $status);
    }

    public function render()
    {
        return view('livewire.task-card');
    }
}

Events จะ bubble up ผ่าน hierarchy ของ component เป็นค่าเริ่มต้น สำหรับการสื่อสาร cross-component นอกความสัมพันธ์ parent-child events สามารถเจาะจง component ที่ต้องการได้โดยใช้ $this->dispatch('event-name')->to(ComponentClass::class)

พร้อมที่จะพิชิตการสัมภาษณ์ Laravel แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

การรวม Alpine.js สำหรับ Interactivity ฝั่ง Client-Side

Livewire 3 มาพร้อม Alpine.js ที่ถูก bundle ไว้ ทำให้สามารถใช้ pattern hybrid ที่บาง interaction เกิดขึ้นฝั่ง client-side ในขณะที่บางอย่าง round-trip ไปยัง server การรวมกันนี้ลดโหลดของ server สำหรับ UI toggle ง่ายๆ ในขณะที่รักษา model server-side ของ Livewire สำหรับ logic ที่ซับซ้อน

blade
{{-- resources/views/livewire/notification-dropdown.blade.php --}}
<div 
    x-data="{ 
        open: false,
        unreadCount: @entangle('unreadCount')
    }"
    class="relative"
>
    <button @click="open = !open" class="relative p-2">
        <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                  d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 10-12 0v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
        </svg>
        <span 
            x-show="unreadCount > 0"
            x-text="unreadCount"
            class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center"
        ></span>
    </button>

    <div 
        x-show="open" 
        @click.outside="open = false"
        x-transition
        class="absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-lg"
    >
        @foreach($notifications as $notification)
            <div 
                wire:click="markAsRead({{ $notification->id }})"
                class="p-4 border-b hover:bg-gray-50 cursor-pointer"
            >
                <p class="font-medium">{{ $notification->title }}</p>
                <p class="text-sm text-gray-600">{{ $notification->message }}</p>
            </div>
        @endforeach
    </div>
</div>

Directive @entangle สร้าง binding สองทางระหว่าง state ของ Alpine.js และ property ของ Livewire การเปลี่ยนแปลง unreadCount ใน Alpine หรือ Livewire จะซิงค์โดยอัตโนมัติ ทำให้สามารถทำ optimistic UI updates ได้ในขณะที่รักษาอำนาจของ server

การอัปโหลดไฟล์พร้อมการติดตามความคืบหน้า

Livewire มีการจัดการอัปโหลดไฟล์พร้อม built-in progress tracking, validation, และ temporary storage Trait WithFileUploads เพิ่มฟังก์ชันที่จำเป็นทั้งหมดให้กับ component ใดก็ได้

app/Livewire/DocumentUploader.phpphp
<?php

namespace App\Livewire;

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Rule;
use Illuminate\Support\Facades\Storage;

class DocumentUploader extends Component
{
    use WithFileUploads;

    #[Rule('required|file|mimes:pdf,docx|max:10240')] // สูงสุด 10MB
    public $document;

    public ?string $uploadedPath = null;

    public function updatedDocument(): void
    {
        $this->validate();
    }

    public function save(): void
    {
        $this->validate();

        $this->uploadedPath = $this->document->store('documents', 'public');

        $this->dispatch('document-uploaded', path: $this->uploadedPath);
        $this->reset('document');
    }

    public function render()
    {
        return view('livewire.document-uploader');
    }
}
blade
{{-- resources/views/livewire/document-uploader.blade.php --}}
<div class="space-y-4">
    <div 
        x-data="{ uploading: false, progress: 0 }"
        x-on:livewire-upload-start="uploading = true"
        x-on:livewire-upload-finish="uploading = false; progress = 0"
        x-on:livewire-upload-error="uploading = false"
        x-on:livewire-upload-progress="progress = $event.detail.progress"
    >
        <input 
            type="file" 
            wire:model="document"
            accept=".pdf,.docx"
            class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:bg-indigo-50 file:text-indigo-700"
        >

        <div x-show="uploading" class="mt-2">
            <div class="bg-gray-200 rounded-full h-2">
                <div 
                    class="bg-indigo-600 h-2 rounded-full transition-all duration-300"
                    :style="'width: ' + progress + '%'"
                ></div>
            </div>
            <p class="text-sm text-gray-600 mt-1" x-text="'กำลังอัปโหลด: ' + progress + '%'"></p>
        </div>
    </div>

    @error('document')
        <p class="text-sm text-red-600">{{ $message }}</p>
    @enderror

    @if($document)
        <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
            <span class="text-sm">{{ $document->getClientOriginalName() }}</span>
            <button wire:click="save" class="bg-indigo-600 text-white px-4 py-2 rounded-md text-sm">
                อัปโหลด
            </button>
        </div>
    @endif
</div>

Template นี้รวม Alpine.js สำหรับการแสดง progress ฝั่ง client กับ event อัปโหลดของ Livewire ไฟล์จะอัปโหลดไปยัง temporary storage ก่อน ทำให้สามารถ preview และ validate ก่อนการจัดเก็บขั้นสุดท้าย

คำถามสัมภาษณ์สำหรับนักพัฒนา Livewire 3

การสัมภาษณ์ทางเทคนิคมักจะทดสอบความเข้าใจเกี่ยวกับสถาปัตยกรรมของ Livewire, model ความปลอดภัย, และลักษณะประสิทธิภาพ คำถามต่อไปนี้มักปรากฏเมื่อรับสมัครนักพัฒนา Laravel ที่มีประสบการณ์ Livewire

จุดเน้นในการสัมภาษณ์

ผู้สัมภาษณ์ประเมินสามด้านหลัก: ความเข้าใจเกี่ยวกับ lifecycle request ของ Livewire, ความตระหนักด้านความปลอดภัยเกี่ยวกับ property สาธารณะ, และความสามารถในการปรับแต่งประสิทธิภาพ component การแสดงความรู้ว่าเมื่อไหร่ไม่ควรใช้ Livewire มีความสำคัญเท่ากับการรู้วิธีใช้มัน

Livewire รักษา state ของ component ระหว่าง request อย่างไร?

Livewire serialize property สาธารณะทั้งหมดเป็น payload ที่ส่งไปพร้อมกับแต่ละ request State ของ component ถูก encode, ลงนามเพื่อป้องกัน tampering, และส่งไปยัง browser ใน interaction ต่อมา payload นี้กลับไปที่ server ซึ่ง Livewire สร้าง instance ของ component ใหม่ก่อนประมวลผล action

มีปัญหาความปลอดภัยอะไรบ้างกับ property สาธารณะของ Livewire และ Livewire จัดการอย่างไร?

Property สาธารณะมีความเสี่ยงเพราะถูกรวมใน payload ที่ถูก serialize ส่งไปยัง browser ผู้ใช้ที่ประสงค์ร้ายอาจแก้ไข payload นี้ก่อนส่งกลับ Livewire จัดการเรื่องนี้ผ่านการตรวจสอบ checksum—การ tampering ใดๆ จะทำให้ request ล้มเหลว นอกจากนี้ attribute #[Locked] ทำเครื่องหมาย property ว่าเป็นของ server เท่านั้น ป้องกันการแก้ไขผ่าน frontend แม้ว่า checksum จะถูก compromise

อธิบายความแตกต่างระหว่าง wire:model, wire:model.live, และ wire:model.blur

ใน Livewire 3 wire:model ใช้ deferred binding เป็นค่าเริ่มต้น—property จะอัปเดตเมื่อ action ถูกเรียก (เช่น form submission) wire:model.live อัปเดต property ทุกครั้งที่ input เปลี่ยน ทำให้เกิด server roundtrip ทุกครั้งที่กดปุ่ม wire:model.blur อัปเดตเมื่อ element สูญเสีย focus สร้างสมดุลระหว่างความ responsive และการลดโหลด server Modifier .debounce เพิ่ม delay ให้ live updates: wire:model.live.debounce.500ms

เมื่อไหร่ที่นักพัฒนาควรเลือก Livewire แทน Inertia.js หรือ JavaScript framework?

Livewire เหมาะสมที่สุดเมื่อทีมมีความเชี่ยวชาญ PHP ที่แข็งแกร่งแต่ประสบการณ์ JavaScript จำกัด, เมื่อข้อกำหนด SEO ต้องการเนื้อหาที่ render จาก server, หรือเมื่อแอปพลิเคชันเกี่ยวข้องกับ form ที่ซับซ้อนพร้อม validation ฝั่ง server Inertia.js หรือ JavaScript framework เหมาะกว่าสำหรับ dashboard ที่มี interaction สูงพร้อม state ฝั่ง client ที่ซับซ้อน, แอปพลิเคชันที่ต้องการฟังก์ชัน offline, หรือทีมที่มีความเชี่ยวชาญ JavaScript ที่ชอบ mental model แบบ SPA

จะปรับแต่ง component Livewire เพื่อประสิทธิภาพอย่างไร?

การปรับแต่งหลักรวมถึง: ใช้ wire:model.blur แทน wire:model.live เพื่อลด request, ใช้ #[Computed] สำหรับการคำนวณที่ใช้ทรัพยากรมากเพื่อเปิดใช้ caching, lazy loading component ด้วย wire:init สำหรับประสิทธิภาพ render เริ่มต้น, ใช้ pagination เพื่อจำกัดข้อมูลที่ส่งต่อ request, และหลีกเลี่ยง re-render ที่ไม่จำเป็นโดย scoping property ที่เป็น reactive สำหรับ list attribute wire:key ช่วยให้ Livewire ติดตาม item อย่างมีประสิทธิภาพระหว่าง morphing

สำหรับการสำรวจระบบ event ของ Laravel ที่ใช้ร่วมกับ Livewire อย่างลึกซึ้ง ดู คำถามสัมภาษณ์ events และ listeners การเข้าใจ pattern dependency injection ก็มีคุณค่าเมื่อจัดโครงสร้าง component Livewire ที่ซับซ้อน

การทดสอบ Component Livewire อย่างมีประสิทธิภาพ

Livewire มี API testing แบบ fluent ที่รวมกับ framework testing ของ Laravel การทดสอบสามารถจำลอง interaction ของผู้ใช้, assert state ของ component, และตรวจสอบ output DOM

tests/Feature/Livewire/SearchUsersTest.phpphp
<?php

namespace Tests\Feature\Livewire;

use App\Livewire\SearchUsers;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;

class SearchUsersTest extends TestCase
{
    use RefreshDatabase;

    public function test_can_search_users_by_name(): void
    {
        User::factory()->create(['name' => 'John Doe', 'role' => 'admin']);
        User::factory()->create(['name' => 'Jane Smith', 'role' => 'user']);

        Livewire::test(SearchUsers::class)
            ->set('search', 'John')
            ->assertSee('John Doe')
            ->assertDontSee('Jane Smith');
    }

    public function test_can_filter_by_role(): void
    {
        User::factory()->create(['name' => 'Admin User', 'role' => 'admin']);
        User::factory()->create(['name' => 'Regular User', 'role' => 'user']);

        Livewire::test(SearchUsers::class)
            ->set('role', 'admin')
            ->assertSee('Admin User')
            ->assertDontSee('Regular User');
    }

    public function test_pagination_resets_on_search_change(): void
    {
        User::factory()->count(25)->create();

        Livewire::test(SearchUsers::class)
            ->call('gotoPage', 2)
            ->assertSet('paginators.page', 2)
            ->set('search', 'test')
            ->assertSet('paginators.page', 1);
    }

    public function test_url_reflects_search_parameters(): void
    {
        Livewire::withQueryParams(['search' => 'existing'])
            ->test(SearchUsers::class)
            ->assertSet('search', 'existing');
    }
}

ชุดทดสอบครอบคลุมฟังก์ชัน, การจัดการ state, และการซิงค์ URL สำหรับกลยุทธ์การทดสอบ Laravel ที่ครอบคลุมรวมถึง component Livewire บทความ best practices การทดสอบ Laravel มี pattern และเทคนิคเพิ่มเติม

สรุป

  • Livewire 3 ช่วยให้สร้าง interface reactive ด้วย PHP ล้วนผ่าน server-side rendering และ DOM morphing ลดความจำเป็นในการใช้ JavaScript framework แยกต่างหากในหลาย application Laravel
  • PHP 8 attributes (#[Rule], #[Locked], #[Computed], #[Url]) ให้การตั้งค่าแบบ declarative ที่ปรับปรุงความสามารถในการอ่านโค้ดและความปลอดภัย
  • การรวม Alpine.js ที่ bundle มาด้วยช่วยให้ใช้ pattern hybrid ที่ interaction ง่ายๆ อยู่ฝั่ง client-side ในขณะที่ logic ที่ซับซ้อนอยู่ที่ server
  • ความปลอดภัยต้องการความใส่ใจ: ใช้ #[Locked] สำหรับ property ที่ sensitive และเข้าใจว่า property สาธารณะถูกส่งในแต่ละ payload request
  • การปรับแต่งประสิทธิภาพมุ่งเน้นที่การลด roundtrip ผ่าน modifier wire:model ที่เหมาะสมและใช้ computed properties สำหรับ operation ที่ใช้ทรัพยากรมาก
  • คำถามสัมภาษณ์มุ่งเน้นที่ความเข้าใจสถาปัตยกรรม, ความตระหนักด้านความปลอดภัย, และรู้ว่าเมื่อไหร่แนวทางทางเลือก (Inertia.js, SPA) เหมาะสมกับข้อกำหนดมากกว่า

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แท็ก

#laravel
#livewire
#php
#reactive
#alpine-js
#interview

แชร์

บทความที่เกี่ยวข้อง