+7

Inertiajs - Xây dựng Single Page App không cần API

Tiêu đề là mình lấy từ trang chủ của https://inertiajs.com/ chứ không phải mình tự nghĩ ra đâu nhé :v. Lâu lâu rồi chưa động tới Laravel (dự án cuối cùng mình code là ở ver 5.8), thế nên một ngày đẹp trời lượn vào đọc docs ver 8.x tí và thấy cái gọi là Laravel Jetstream. Ngay ở phần Introduction mình đã đọc được 2 thứ nghe khá mới lạ đối với mình là LivewireInertia (có lẽ với nhiều bạn thì nó không mới lắm). Sau khi xem qua thì mình cảm thấy thích Inertia hơn nên quyết định tìm hiểu về nó. Inertia cho phép chúng ta dễ dàng tạo ra single page app một cách đơn giản hơn.

Cách hoạt động

Chúng ta vẫn sẽ xây dựng một app với model, controller, middleware,... như cách mà chúng ta thường làm khi xây dựng web với laravel. Điều khác ở đây là tầng view. Thay vì việc sử dụng server-side render, nay view sẽ là Javascript components. Nhưng mọi chuyện không chỉ dừng ở đây. Để mang lại trải nghiệm của SPA (single page app) thì khi click vào 1 link, nếu browser thực hiện load lại cả trang thì không ổn chút nào. Đó là lúc mà Inertia sẽ tham gia vào. Về cơ bản thì Inertia là một thư viện giúp thực hiện truy cập trang mà không phải reload lại toàn bộ trang. Điều này thực hiện nhờ sử dụng <inertia-link> component. Khi click vào một Inertia link, Inertia sẽ chặn lại và thay thế bằng XHR. Khi mà Inertia tạo ra một XHR request, server sẽ nhận biết được và thay vì trả về HTML, nó sẽ trả về JSON cùng với tên và data của component JS. Sau đó, Inertia sẽ đổi component trước thành component mới và update vào lịch sử state.

Cài đặt

Về phần tạo project Laravel thì mình sẽ không nói nữa nhé và mình sẽ sử dụng react để build frontend nhé. Đầu tiên thì bạn cần cài đặt laravel/ui:

composer require laravel/ui

Cài đặt react

php artisan ui react

Cài các dependencies của Inertia

Server side

composer require inertiajs/inertia-laravel

Sau khi cài đặt xong thì hãy tạo ra root template nhé. Mặc định Laravel adapter sẽ sử dụng file app.blade.php

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
    <script src="{{ mix('/js/app.js') }}" defer></script>
  </head>
  <body>
    @inertia
  </body>
</html>

Tạo middleware:

php artisan inertia:middleware

Ở trong file App\Http\Kernel, bạn hãy thêm đoạn này vào cuối group

'web' => [
    // ...
    \App\Http\Middleware\HandleInertiaRequests::class,
],

Client side

Đầu tiên chạy lệnh:

npm install

Sau đó chúng ta sẽ cài thêm package liên quan

npm install @inertiajs/inertia @inertiajs/inertia-react

Ở file resources/js/app.js

import { App } from '@inertiajs/inertia-react'
import React from 'react'
import { render } from 'react-dom'

const el = document.getElementById('app')

render(
  <App
    initialPage={JSON.parse(el.dataset.page)}
    resolveComponent={name => require(`./Pages/${name}`).default}
  />,
  el
)

Giờ bạn chỉ cần khởi tạo server bằng lệnh

php artisan serve

và build giao diện bằng lệnh

npm run dev

để có thể tự động build lại mỗi lần có thay đổi thì bạn dùng lệnh

npm run watch-poll

Routing

Khi setup xong thì cta cùng tạo ra một cái view nhỏ để xem nhỉ. Muốn có view thì đầu tiện chúng ta phải có url để truy cập. Với nhưng view mà không cần data, chỉ đơn giản là render giao diện tĩnh ra thì các bạn có thể định nghĩa route trong web.php như sau:

Route::inertia('/', 'TestComponent');
import React from 'react';

const TestComponent = (props) => {
    return (
        <div>
            <h1>This is test component</h1>
        </div>
    )
}

export default TestComponent;

Giờ thì truy cập lại app bạn sẽ có kết quả như sau:

Giờ thì với view có data thì sao. Đầu tiên mình sẽ tạo ra TestController nhé:

<?php

namespace App\Http\Controllers;

use Inertia\Inertia;

class TestController extends Controller
{
    public function index()
    {
        $user = [
            [
                'id' => 1,
                'name' => 'Test 1',
            ],
            [
                'id' => 2,
                'name' => 'Test 2',
            ]
        ];

        return Inertia::render('TestComponent', [
            'users' => $user,
        ]);
    }
}

Ở đây thì mình fake tạm 1 ít dữ liệu. Sau đó sửa 1 ít ở TestComponent

import React from 'react';

const TestComponent = (props) => {
    const { users } = props;

    return (
        <div>
            <h1>List test users</h1>
            {
                users.map(user => (
                    <p>{user.name}</p>
                ))
            }
        </div>
    )
}

export default TestComponent;

Và kết quả:

Khá là đơn giản phải không nào 😄. Thay vì dữ liệu ảo kia bạn có thể lấy dữ liệu từ DB thông qua Model hoặc viết Query builder. Thế là không phải mất công viết API và làm bao nhiêu thứ khác nữa

Pages

Về cơ bản thì pages là những javascript components thôi. Ở đây thì Pages sẽ nhận data từ controller trả về và đưa coi đó như props. Ví dụ như TestComponent ở trên sẽ được coi là 1 page

Tạo và sử dụng layout

Việc tạo layout và sử dụng đi sử dụng lại chắc cũng không xa lạ gì với anh em nữa rồi. Và InertiaJS cũng hỗ trợ chúng ta làm điều đó một cách khá dễ dàng. Ở đây thì mình sẽ đưa ra một ví dụ cơ bản thôi nhé, còn thực tế mình sử dụng như thế nào mình sẽ để trong bài xong, khi mà mình sẽ làm hẳn ra 1 demo khác

Giờ sẽ tạo 1 file Layout.js


import React from 'react';
import { InertiaLink } from '@inertiajs/inertia-react'

const Layout = (props) => {
    const { children } = props;

    return (
        <main>
        <header>
          <InertiaLink href="/">Home</InertiaLink>
          <InertiaLink href="/pages">Pages</InertiaLink>
        </header>
  
        <article>{children}</article>
      </main>
    )
}

export default Layout;

ở file TestComponent thì các bạn thêm đoạn

TestComponent.layout = page => <Layout children={page} />

cụ thể:

import React from 'react';
import Layout from './Layout'

const TestComponent = (props) => {
    const { users } = props;

    return (
        <div>
            <h1>List test users</h1>
            {
                users.map(user => (
                    <p>{user.name}</p>
                ))
            }
        </div>
    )
}

TestComponent.layout = page => <Layout children={page} />

export default TestComponent;

Và giờ view của chúng ta sẽ như này:

Bạn cũng có thể tạo ra các layout lồng nhau (hay trong docs gọi là nested layout)

import React from 'react';

const NestedLayout = (props) => {
    const { children } = props;

    return (
       <div>
           This is nested layout

           <article>{children}</article>
       </div>
    )
}

export default NestedLayout;
import React from 'react';
import Layout from './Layout';
import NestedLayout from './NestedLayout';

const TestComponent = (props) => {
    const { users } = props;

    return (
        <div>
            <h1>List test users</h1>
            {
                users.map(user => (
                    <p>{user.name}</p>
                ))
            }
        </div>
    )
}


TestComponent.layout = page => (
    <Layout>
      <NestedLayout children={page} />
    </Layout>
  )

export default TestComponent;

Kết quả:

Nhưng hay dùng nested layout một cách sáng suốt nhé. Nếu tất cả các pages của bạn đều cần tới component nested đó (ví dụ giờ có SidebarComponent chẳng hạn), thì hãy để nó là một component con của Layout chính, chứ đừng file nào cũng khai báo nested layout như này 😄

Kết

Bài viết của mình đến đây thôi, cơ bản giới thiệu tới mọi người như vậy để nắm sơ sơ. Bài sau mình sẽ đi vào hẳn demo để có thể nói về những điều trong docs dễ hơn nhé 😄. Dạo này cũng hơi bận nên khả năng bài sau sẽ ra muộn nên các bạn cũng tự thử làm demo trước xem sao nha 😄

Tham khảo: https://inertiajs.com/


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.