ReactJS: Sử dụng React trong ứng dụng Laravel


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Sử dụng React trong ứng dụng Laravel

Vue.js chắc chắn là framework JavaScript được ưa thích trong cộng đồng Laravel. Trên thực tế, một ứng dụng Laravel mới đi kèm với Vue.js đã được thiết lập sẵn. Bạn muốn sử dụng React thay thế? Vậy, bạn đang ở đúng nơi, vì chúng ta sẽ xem xét cách sử dụng React trong ứng dụng Laravel trong bài hướng dẫn này.

Điều kiện tiên quyết

Hướng dẫn này giả định những điều sau:

  • Kiến thức cơ bản về PHP và Laravel
  • Kiến thức cơ bản về JavaScript và React
  • PHP được cài đặt trên máy tính của bạn
  • Composer được cài đặt trên máy tính của bạn
  • Trình cài đặt Laravel được cài đặt trên máy tính của bạn
  • SQLite được cài đặt trên máy tính của bạn

Những gì chúng ta sẽ xây dựng

Với mục đích trình bày cách sử dụng React trong ứng dụng Laravel, chúng ta sẽ xây dựng một ứng dụng quản lý tác vụ. Dưới đây là bản demo hoạt động của ứng dụng cuối cùng:

Bản giới thiệu ứng dụng

Lập kế hoạch ứng dụng

Ứng dụng quản lý tác vụ của chúng ta sẽ bao gồm hai thành phần chính: nhiệm vụ và dự án. Hãy chia nhỏ từng thành phần này.

  • Nhiệm vụ: nhiệm vụ là một hạng mục cần được thực hiện bởi người dùng và thường bao gồm một tiêu đề ngắn gọn và súc tích về những việc cần phải làm để hoàn thành nhiệm vụ đó. Các nhiệm vụ cũng cần cho biết chúng đã được hoàn thành hay chưa. Cuối cùng, một nhiệm vụ thường được liên kết với một dự án có chứa các nhiệm vụ tương tự hoặc có liên quan.
  • Dự án: dự án nhóm các nhiệm vụ liên quan lại với nhau và thường có tên mô tả và mô tả liên quan đến chúng. Chúng ta cũng cần phải cho biết liệu một dự án đã hoàn thành hay chưa.

Như đã nói, ứng dụng của chúng ta sẽ có các bảng và trường sau:

Bảng Trường
tasks id, title, project_id, is_completed, create_at, updated_at
projects id, name, description, is_completed, create_at, updated_at

Hãy bắt đầu nào!

Bắt đầu

Chúng ta sẽ bắt đầu bằng cách tạo một ứng dụng Laravel mới:

  laravel new tasksman

Sau khi hoàn tất, chúng ta cần hoán đổi scaffolding Vue.js mặc định với React. May mắn cho chúng ta, có một lệnh Artisan preset mà chúng ta có thể sử dụng cho việc đó. Lệnh preset đã được thêm vào Laravel 5.5, cho phép chúng ta chỉ định framework JavaScript mà chúng ta lựa chọn. Để chỉ định chúng ta muốn sử dụng React thay cho Vue.js, chúng ta sẽ chạy lệnh dưới đây:

  cd tasksman
  php artisan preset react

Bây giờ chúng ta sẽ có một file Example.js bên trong resources/assets/js/components, đó là một component React cơ bản. Ngoài ra, resources/assets/js/app.js đã được cập nhật để sử dụng component Example.

Tiếp theo, chạy lệnh bên dưới để cài đặt các dependency ứng dụng của chúng ta:

  npm install

Tạo mô hình ứng dụng và di chuyển

Như đã trình bày trong phần lập kế hoạch ứng dụng, chúng ta cần tạo hai mô hình: Task và Project:

  php artisan make:model Task -m
  php artisan make:model Project -m

Thêm cờ -m vào lệnh make:model sẽ tạo ra migration kèm theo cho mô hình.

Tiếp theo, hãy mở app/Task.php và cập nhật nội dung của nó như bên dưới:

    // app/Task.php

    <?php

    namespace App;

    use Illuminate\Database\Eloquent\Model;

    class Task extends Model
    {
      protected $fillable = ['title', 'project_id'];
    }

Chúng ta chỉ định các trường mà chúng ta muốn có thể gán hàng loạt.

Tương tự, mở app/Project.php và cập nhật như bên dưới:

    // app/Project.php

    <?php

    namespace App;

    use Illuminate\Database\Eloquent\Model;

    class Project extends Model
    {
      protected $fillable = ['name', 'description'];

      public function tasks()
      {
        return $this->hasMany(Task::class);
      }
    }

Ngoài việc chỉ định các trường mà chúng ta muốn có thể gán hàng loạt, chúng ta cũng xác định mối quan hệ giữa các mô hình Project và Task bằng cách sử dụng phương thức tasks này. Đây là mối quan hệ một-nhiều, vì một dự án có thể có nhiều nhiệm vụ, nhưng một nhiệm vụ chỉ có thể thuộc về một dự án cụ thể.

Bạn sẽ nhận thấy rằng chúng ta đã không xác định nghịch đảo của mối quan hệ trên mô hình Task, vì chúng ta chỉ xác định những gì cần thiết cho mục đích của hướng dẫn này.

Tiếp theo, hãy cập nhật các di chuyển đã tạo cho các mô hình của chúng ta. Mở database/migrations/TIMESTAMP_create_tasks_table.php và cập nhật phương thức up như sau:

    // database/migrations/TIMESTAMP_create_tasks_table.php

    public function up()
    {
      Schema::create('tasks', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->unsignedInteger('project_id');
        $table->boolean('is_completed')->default(0);
        $table->timestamps();
      });
    }

Bạn sẽ nhận thấy rằng chúng ta đang đặt trường is_completed thành mặc định là false.

Tương tự, hãy mở database/migrations/TIMESTAMP_create_projects_table.php và cập nhật phương thức up như bên dưới:

    // database/migrations/TIMESTAMP_create_projects_table.php

    public function up()
    {
      Schema::create('projects', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->text('description');
        $table->boolean('is_completed')->default(0);
        $table->timestamps();
      });
    }

Trước khi chạy migration, hãy thiết lập cơ sở dữ liệu của chúng ta. Chúng ta sẽ sử dụng SQLite. Vì vậy, hãy tạo một file database.sqlite bên trong thư mục database sau đó cập nhật file .env như bên dưới:

    // .env

    DB_CONNECTION=sqlite
    DB_DATABASE=/full/path/to/database/database.sqlite

Chạy migration:

    $ php artisan migrate

Tạo API ứng dụng

Chúng ta sẽ bắt đầu bằng cách xác định các điểm cuối API. Mở routes/api.php và thay thế nội dung bằng đoạn mã dưới đây:

    // routes/api.php

    Route::get('projects', 'ProjectController@index');
    Route::post('projects', 'ProjectController@store');
    Route::get('projects/{id}', 'ProjectController@show');
    Route::put('projects/{project}', 'ProjectController@markAsCompleted');
    Route::post('tasks', 'TaskController@store');
    Route::put('tasks/{task}', 'TaskController@markAsCompleted');

Ở đây, chúng ta xác định các điểm cuối để tìm nạp tất cả các dự án cũng như để tìm nạp một dự án duy nhất. Sau đó, các điểm cuối để tạo các dự án và nhiệm vụ mới tương ứng. Cuối cùng, các điểm cuối để đánh dấu một dự án và nhiệm vụ là đã hoàn thành tương ứng.

Tiếp theo, hãy chuyển sang tạo bộ controller:

    $ php artisan make:controller ProjectController
    $ php artisan make:controller TaskController

Mở app/Http/Controllers/ProjectController.php và cập nhật nó như bên dưới:

    // app/Http/Controllers/ProjectController.php

    <?php

    namespace App\Http\Controllers;

    use App\Project;
    use Illuminate\Http\Request;

    class ProjectController extends Controller
    {
      public function index()
      {
        $projects = Project::where('is_completed', false)
                            ->orderBy('created_at', 'desc')
                            ->withCount(['tasks' => function ($query) {
                              $query->where('is_completed', false);
                            }])
                            ->get();

        return $projects->toJson();
      }

      public function store(Request $request)
      {
        $validatedData = $request->validate([
          'name' => 'required',
          'description' => 'required',
        ]);

        $project = Project::create([
          'name' => $validatedData['name'],
          'description' => $validatedData['description'],
        ]);

        return response()->json('Project created!');
      }

      public function show($id)
      {
        $project = Project::with(['tasks' => function ($query) {
          $query->where('is_completed', false);
        }])->find($id);

        return $project->toJson();
      }

      public function markAsCompleted(Project $project)
      {
        $project->is_completed = true;
        $project->update();

        return response()->json('Project updated!');
      }
    }

Phương thức index này tìm nạp tất cả các dự án chưa được đánh dấu là đã hoàn thành theo thứ tự giảm dần khi chúng được tạo. Ngoài ra, chúng ta nhận được số lượng nhiệm vụ chưa được đánh dấu là đã hoàn thành thuộc về từng dự án. Sau đó, chúng ta chuyển đổi các dự án thành JSON và trả lại chúng.

Phương thức store được sử dụng để tạo một dự án mới. Đầu tiên, nó xác thực dữ liệu yêu cầu đến dựa trên các quy tắc đã xác định cho từng trường. Sau đó, khi quá trình xác thực trôi qua, chúng ta tạo một dự án mới bằng cách sử dụng dữ liệu đã được xác thực trong cơ sở dữ liệu và trả về phản hồi JSON.

Phương thức show này tìm nạp một dự án đơn lẻ bằng id của nó. Ngoài việc tìm nạp dự án, chúng ta cũng tìm nạp tất cả các tác vụ chưa được đánh dấu là đã hoàn thành cho dự án cụ thể. Cuối cùng, chúng ta trả lại dự án trong JSON.

Phương thức markAsCompleted này chỉ cần cập nhật một dự án cụ thể bằng cách đặt is_completed thành true.

Tiếp theo, hãy mở app/Http/Controllers/TaskController.php và cập nhật nó như bên dưới:

    // app/Http/Controllers/TaskController.php

    <?php

    namespace App\Http\Controllers;

    use App\Task;
    use Illuminate\Http\Request;

    class TaskController extends Controller
    {
      public function store(Request $request)
      {
        $validatedData = $request->validate(['title' => 'required']);

        $task = Task::create([
          'title' => $validatedData['title'],
          'project_id' => $request->project_id,
        ]);

        return $task->toJson();
      }

      public function markAsCompleted(Task $task)
      {
        $task->is_completed = true;
        $task->update();

        return response()->json('Task updated!');
      }
    }

Chúng ta sẽ không xem xét từng phương thức vì chúng tương tự như những phương thức trong ProjectController.

Như vậy đến đây ta đã hoàn thành API cho ứng dụng của mình.

Bây giờ, hãy chuyển sang giao diện người dùng của ứng dụng của chúng ta.

Tạo một route ký tự đại diện

Chúng ta sẽ sử dụng React Router để xử lý định tuyến trong ứng dụng của chúng ta. Đối với điều này, chúng ta cần hiển thị một file dạng xem duy nhất cho tất cả các tuyến ứng dụng của chúng ta. Mở routes/web.php và thay thế nội dung bằng đoạn mã dưới đây:

    // routes/web.php

    Route::view('/{path?}', 'app');

Chúng ta xác định một route ký tự đại diện. Một view app.blade.php sẽ được hiển thị cho tất cả các route ứng dụng của chúng ta. Bằng cách này, chúng ta có thể hiển thị các component React của mình từ bên trong file view này và chúng ta sẽ có thể sử dụng tất cả các tính năng mà React cung cấp.

Tiếp theo, hãy tạo view app.blade.php. Chúng ta sẽ tạo view này trực tiếp trong thư mục resources/views, sau đó dán mã sau vào:

    // resources/views/app.blade.php

    <!DOCTYPE html>
    <html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- CSRF Token -->
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>Tasksman</title>
        <!-- Styles -->
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    </head>
    <body>
        <div id="app"></div>

        <script src="{{ asset('js/app.js') }}"></script>
    </body>
    </html>

Chúng ta thêm các tham chiếu vào cả tệp CSS và tệp JavaScript (chứa React và các dependency khác được đóng gói). Chúng ta có một div trống với một id của app. Đây là nơi các component React của chúng ta sẽ được hiển thị. Ngoài ra, bạn sẽ nhận thấy chúng ta có một thẻ meta chứa mã thông báo CSRF, mã này sẽ được đính kèm dưới dạng tiêu đề chung cho tất cả các yêu cầu HTTP gửi đi mà chúng ta thực hiện bằng Axios. Điều này được xác định trong resources/assets/js/bootstrap.js.

Tạo componnet App

Component App sẽ đóng vai trò là cơ sở cho các component React của chúng ta. Hãy đổi tên component Example mặc định thành App và thay thế nội dung của nó bằng đoạn mã sau:

    // resources/assets/js/components/App.js

    import React, { Component } from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter, Route, Switch } from 'react-router-dom'
    import Header from './Header'

    class App extends Component {
      render () {
        return (
          <BrowserRouter>
            <div>
              <Header />
            </div>
          </BrowserRouter>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById('app'))

Chúng ta kết xuất một component Header (mà chúng ta sẽ tạo ngay sau đây). Component Header sẽ được hiển thị cho tất cả các trang ứng dụng của chúng ta. Như bạn có thể thấy, chúng ta đang sử dụng React Router, vì vậy hãy cài đặt nó:

  npm install react-router-dom

Trong khi cài đặt, hãy mở và cập nhật resources/assets/js/app.js như bên dưới:

    // resources/assets/js/app.js

    require('./bootstrap')
    require('./components/App')

Thay vì tham chiếu đến component Example, chúng ta tham chiếu đến component App mà chúng ta vừa tạo.

Tạo component Header

Hãy tạo component Header được tham chiếu ở trên. Tạo một file Header.js mới trong thư mục resources/assets/js/components và dán mã bên dưới vào:

    // resources/assets/js/components/Header.js

    import React from 'react'
    import { Link } from 'react-router-dom'

    const Header = () => (
      <nav className='navbar navbar-expand-md navbar-light navbar-laravel'>
        <div className='container'>
          <Link className='navbar-brand' to='/'>Tasksman</Link>
        </div>
      </nav>
    )

    export default Header

Một thanh điều hướng Bootstrap cơ bản có liên kết đến trang chủ. Như bạn có thể thấy, chúng ta đang sử dụng component Link từ React Router. Điều này sẽ ngăn trang của chúng ta làm mới bất cứ khi nào chúng ta điều hướng xung quanh ứng dụng của mình.

Hiển thị tất cả các dự án chưa được hoàn thành

Để hiển thị danh sách các dự án chưa được hoàn thành, chúng ta sẽ tạo một component ProjectsList. Trong resources/assets/js/components, tạo một file ProjectsList.js mới và dán mã bên dưới vào:

    // resources/assets/js/components/ProjectsList.js

    import axios from 'axios'
    import React, { Component } from 'react'
    import { Link } from 'react-router-dom'

    class ProjectsList extends Component {
      constructor () {
        super()
        this.state = {
          projects: []
        }
      }

      componentDidMount () {
        axios.get('/api/projects').then(response => {
          this.setState({
            projects: response.data
          })
        })
      }

      render () {
        const { projects } = this.state
        return (
          <div className='container py-4'>
            <div className='row justify-content-center'>
              <div className='col-md-8'>
                <div className='card'>
                  <div className='card-header'>All projects</div>
                  <div className='card-body'>
                    <Link className='btn btn-primary btn-sm mb-3' to='/create'>
                      Create new project
                    </Link>
                    <ul className='list-group list-group-flush'>
                      {projects.map(project => (
                        <Link
                          className='list-group-item list-group-item-action d-flex justify-content-between align-items-center'
                          to={`/${project.id}`}
                          key={project.id}
                        >
                          {project.name}
                          <span className='badge badge-primary badge-pill'>
                            {project.tasks_count}
                          </span>
                        </Link>
                      ))}
                    </ul>
                  </div>
                </div>
              </div>
            </div>
          </div>
        )
      }
    }

    export default ProjectsList

Chúng ta tạo một state projects và khởi tạo nó thành một mảng trống. Sử dụng vòng đời của React componentDidMount, chúng ta thực hiện một yêu cầu HTTP bằng Axios tới điểm cuối API ứng dụng của chúng ta để tìm nạp tất cả các dự án chưa được đánh dấu là đã hoàn thành. Sau đó, chúng ta cập nhật state projects với dữ liệu phản hồi nhận được từ API ứng dụng của chúng ta.

Cuối cùng, chúng ta hiển thị danh sách các dự án bằng cách lặp lại state projects.

Trước khi tiếp tục kiểm tra điều này, ta hãy cập nhật component App như bên dưới:

    // resources/assets/js/components/App.js

    import React, { Component } from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter, Route, Switch } from 'react-router-dom'
    import Header from './Header'
    import ProjectsList from './ProjectsList'

    class App extends Component {
      render () {
        return (
          <BrowserRouter>
            <div>
              <Header />
              <Switch>
                <Route exact path='/' component={ProjectsList} />
              </Switch>
            </div>
          </BrowserRouter>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById('app'))

Ở đây, ta thêm một route mới / (trang chủ). Vì vậy, bất cứ khi nào route / được truy cập, thì component ProjectsList sẽ được hiển thị.

Tạo một dự án mới

Từ component ProjectsList, bạn sẽ nhận thấy chúng ta có một liên kết để tạo một dự án mới. Hãy thực hiện nó. Tạo một file NewProject.js mới bên trong resources/assets/js/components và dán mã bên dưới vào:

    // resources/assets/js/components/NewProject.js

    import axios from 'axios'
    import React, { Component } from 'react'

    class NewProject extends Component {
      constructor (props) {
        super(props)
        this.state = {
          name: '',
          description: '',
          errors: []
        }
        this.handleFieldChange = this.handleFieldChange.bind(this)
        this.handleCreateNewProject = this.handleCreateNewProject.bind(this)
        this.hasErrorFor = this.hasErrorFor.bind(this)
        this.renderErrorFor = this.renderErrorFor.bind(this)
      }

      handleFieldChange (event) {
        this.setState({
          [event.target.name]: event.target.value
        })
      }

      handleCreateNewProject (event) {
        event.preventDefault()

        const { history } = this.props

        const project = {
          name: this.state.name,
          description: this.state.description
        }

        axios.post('/api/projects', project)
          .then(response => {
            // redirect to the homepage
            history.push('/')
          })
          .catch(error => {
            this.setState({
              errors: error.response.data.errors
            })
          })
      }

      hasErrorFor (field) {
        return !!this.state.errors[field]
      }

      renderErrorFor (field) {
        if (this.hasErrorFor(field)) {
          return (
            <span className='invalid-feedback'>
              <strong>{this.state.errors[field][0]}</strong>
            </span>
          )
        }
      }

      render () {
        return (
          <div className='container py-4'>
            <div className='row justify-content-center'>
              <div className='col-md-6'>
                <div className='card'>
                  <div className='card-header'>Create new project</div>
                  <div className='card-body'>
                    <form onSubmit={this.handleCreateNewProject}>
                      <div className='form-group'>
                        <label htmlFor='name'>Project name</label>
                        <input
                          id='name'
                          type='text'
                          className={`form-control ${this.hasErrorFor('name') ? 'is-invalid' : ''}`}
                          name='name'
                          value={this.state.name}
                          onChange={this.handleFieldChange}
                        />
                        {this.renderErrorFor('name')}
                      </div>
                      <div className='form-group'>
                        <label htmlFor='description'>Project description</label>
                        <textarea
                          id='description'
                          className={`form-control ${this.hasErrorFor('description') ? 'is-invalid' : ''}`}
                          name='description'
                          rows='10'
                          value={this.state.description}
                          onChange={this.handleFieldChange}
                        />
                        {this.renderErrorFor('description')}
                      </div>
                      <button className='btn btn-primary'>Create</button>
                    </form>
                  </div>
                </div>
              </div>
            </div>
          </div>
        )
      }
    }

    export default NewProject

Component này hiển thị một biểu mẫu để tạo một dự án mới. Chúng ta tạo một số state name, description và errors. Sau đó, chúng ta tạo một phương thức handleFieldChange được gọi bất cứ khi nào các trường đầu vào của biểu mẫu dự án tạo mới thay đổi. Dựa trên những thay đổi này, chúng ta cập nhật các state ( name và description) cho phù hợp. Để điều này hoạt động, chúng ta thêm một sự kiện onChange vào mỗi trường.

Khi biểu mẫu được gửi, một phương thức handleCreateNewProject được gọi, phương thức này trước tiên sẽ ngăn chặn hành vi mặc định của việc gửi biểu mẫu. Sau đó, nó thực hiện một yêu cầu HTTP đến điểm cuối API ứng dụng của chúng ta, chuyển cùng dữ liệu biểu mẫu. Nếu mọi thứ diễn ra tốt đẹp, chúng ta chỉ cần chuyển hướng người dùng đến trang chủ. nếu không, chúng ta cập nhật state errors với lỗi phản hồi nhận được từ API ứng dụng của chúng ta.

Phương thức hasErrorFor kiểm tra xem trường được chỉ định có lỗi hay không và sẽ trả về true hoặc false. Phương thức renderErrorFor hiển thị thông báo lỗi cho trường được chỉ định, nếu trường có lỗi.

Cũng như chúng ta đã làm với component ProjectsList, hãy thêm component NewProject vào component App. Cập nhật component App như bên dưới:

    // resources/assets/js/components/App.js

    import React, { Component } from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter, Route, Switch } from 'react-router-dom'
    import Header from './Header'
    import NewProject from './NewProject'
    import ProjectsList from './ProjectsList'

    class App extends Component {
      render () {
        return (
          <BrowserRouter>
            <div>
              <Header />
              <Switch>
                <Route exact path='/' component={ProjectsList} />
                <Route path='/create' component={NewProject} />
              </Switch>
            </div>
          </BrowserRouter>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById('app'))

Chúng ta tạo route /create một dự án mới và component NewProject sẽ được hiển thị bất cứ khi nào route được truy cập.

Hiển thị một dự án duy nhất

Bây giờ hãy hiển thị một dự án duy nhất. Bạn sẽ nhận thấy từ component ProjectsList rằng mỗi dự án được liệt kê với một ký tự neo (đối với ID dự án) để xem dự án. Tạo một component SingleProject mới bên trong resources/assets/js/components và dán mã bên dưới vào:

    // resources/assets/js/components/SingleProject.js

    import axios from 'axios'
    import React, { Component } from 'react'

    class SingleProject extends Component {
      constructor (props) {
        super(props)
        this.state = {
          project: {},
          tasks: []
        }
      }

      componentDidMount () {
        const projectId = this.props.match.params.id

        axios.get(`/api/projects/${projectId}`).then(response => {
          this.setState({
            project: response.data,
            tasks: response.data.tasks
          })
        })
      }

      render () {
        const { project, tasks } = this.state

        return (
          <div className='container py-4'>
            <div className='row justify-content-center'>
              <div className='col-md-8'>
                <div className='card'>
                  <div className='card-header'>{project.name}</div>
                  <div className='card-body'>
                    <p>{project.description}</p>

                    <button className='btn btn-primary btn-sm'>
                      Mark as completed
                    </button>

                    <hr />

                    <ul className='list-group mt-3'>
                      {tasks.map(task => (
                        <li
                          className='list-group-item d-flex justify-content-between align-items-center'
                          key={task.id}
                        >
                          {task.title}

                          <button className='btn btn-primary btn-sm'>
                            Mark as completed
                          </button>
                        </li>
                      ))}
                    </ul>
                  </div>
                </div>
              </div>
            </div>
          </div>
        )
      }
    }

    export default SingleProject

Chúng ta tạo hai thuộc tính state: project và tasksproject sẽ nắm giữ các chi tiết của dự án được chỉ định, trong khi tasks sẽ giữ các nhiệm vụ cho dự án. Bên trong phương thức vòng đời componentDidMount, chúng ta thực hiện một yêu cầu HTTP tới API ứng dụng của chúng ta để tìm nạp dự án với ID dự án được chỉ định. ID dự án được chuyển đến URL, vì vậy chúng ta có thể lấy nó bằng cách sử dụng this.props.match.params.id. Sau đó, chúng ta cập nhật state ( projectvà tasks) với dữ liệu phản hồi nhận được từ API ứng dụng của chúng ta.

Cuối cùng, ta hiển thị các thông tin chi tiết về dự án cũng như các nhiệm vụ của dự án. Ngoài ra, chúng ta hiển thị các nút để đánh dấu dự án và các nhiệm vụ của nó là đã hoàn thành.

Tiếp theo, chúng ta hãy thêm component SingleProject vào component App. Cập nhật component App như bên dưới:

    // resources/assets/js/components/App.js

    import React, { Component } from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter, Route, Switch } from 'react-router-dom'
    import Header from './Header'
    import NewProject from './NewProject'
    import ProjectsList from './ProjectsList'
    import SingleProject from './SingleProject'
    class App extends Component {
      render () {
        return (
          <BrowserRouter>
            <div>
              <Header />
              <Switch>
                <Route exact path='/' component={ProjectsList} />
                <Route path='/create' component={NewProject} />
                <Route path='/:id' component={SingleProject} />
              </Switch>
            </div>
          </BrowserRouter>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById('app'))

Bây giờ chúng ta có thể xem một dự án cụ thể.

Đánh dấu một dự án là đã hoàn thành

Bây giờ, hãy thêm khả năng đánh dấu một dự án là đã hoàn thành. Thêm mã sau vào component SingleProject:

    // resources/assets/js/components/SingleProject.js

    // add this inside the `constructor`
    this.handleMarkProjectAsCompleted = this.handleMarkProjectAsCompleted.bind(this)

    // add these outside the `constructor`, as a standalone method
    handleMarkProjectAsCompleted () {
      const { history } = this.props

      axios.put(`/api/projects/${this.state.project.id}`)
        .then(response => history.push('/'))
    }

Sau khi nhấp vào nút đánh dấu là hoàn thành, phương thức handleMarkProjectAsCompleted sẽ được gọi. Phương thức này thực hiện một yêu cầu HTTP tới API ứng dụng của chúng ta chuyển cùng với ID của dự án mà chúng ta muốn đánh dấu là đã hoàn thành. Khi yêu cầu thành công, chúng ta chỉ cần chuyển hướng người dùng đến trang chủ.

Tiếp theo, cập nhật nút Mark as completed, bên dưới mô tả của dự án như bên dưới:

    // resources/assets/js/components/SingleProject.js

    <button
      className='btn btn-primary btn-sm'
      onClick={this.handleMarkProjectAsCompleted}
    >
      Mark as completed
    </button>

Thêm nhiệm vụ vào dự án

Hãy để khả năng thêm các nhiệm vụ mới vào một dự án. Thêm mã sau vào component SingleProject:

    // resources/assets/js/components/SingleProject.js

    this.state = {
      ...,
      title: '',
      errors: []
    }

    // add these inside the `constructor`
    this.handleFieldChange = this.handleFieldChange.bind(this)
    this.handleAddNewTask = this.handleAddNewTask.bind(this)
    this.hasErrorFor = this.hasErrorFor.bind(this)
    this.renderErrorFor = this.renderErrorFor.bind(this)

    // add these outside the `constructor`, as a standalone methods
    handleFieldChange (event) {
      this.setState({
        title: event.target.value
      })
    }

    handleAddNewTask (event) {
      event.preventDefault()

      const task = {
        title: this.state.title,
        project_id: this.state.project.id
      }

      axios.post('/api/tasks', task)
        .then(response => {
          // clear form input
          this.setState({
            title: ''
          })
          // add new task to list of tasks
          this.setState(prevState => ({
            tasks: prevState.tasks.concat(response.data)
          }))
        })
        .catch(error => {
          this.setState({
            errors: error.response.data.errors
          })
        })
    }

    hasErrorFor (field) {
      return !!this.state.errors[field]
    }

    renderErrorFor (field) {
      if (this.hasErrorFor(field)) {
        return (
          <span className='invalid-feedback'>
            <strong>{this.state.errors[field][0]}</strong>
          </span>
        )
      }
    }

handleFieldChangehasErrorFor và renderErrorFor giống nhau từ component NewProject, vì vậy chúng ta sẽ không xem xét lại chúng nữa. Phương thức handleAddNewTask này cũng tương tự như phương thức handleCreateNewProject từ component NewProject, vì vậy chúng ta sẽ chỉ xem xét phần mới. Nếu yêu cầu HTTP thành công, trước tiên chúng ta xóa đầu vào biểu mẫu, sau đó chúng ta cập nhật state tasks bằng cách thêm tác vụ mới vào danh sách tác vụ.

Tiếp theo, thêm mã bên dưới bên trong phương thức render ngay bên dưới <hr />:

    // resources/assets/js/components/SingleProject.js

    <form onSubmit={this.handleAddNewTask}>
      <div className='input-group'>
        <input
          type='text'
          name='title'
          className={`form-control ${this.hasErrorFor('title') ? 'is-invalid' : ''}`}
          placeholder='Task title'
          value={this.state.title}
          onChange={this.handleFieldChange}
        />
        <div className='input-group-append'>
          <button className='btn btn-primary'>Add</button>
        </div>
        {this.renderErrorFor('title')}
      </div>
    </form>

Điều này sẽ hiển thị biểu mẫu để thêm nhiệm vụ mới.

Đánh dấu một công việc là đã hoàn thành

Đối với tính năng cuối cùng của ứng dụng quản lý công việc của chúng ta, chúng ta sẽ thêm khả năng đánh dấu một công việc là đã hoàn thành. Điều này sẽ rất giống với những gì chúng ta đã làm với việc đánh dấu một dự án là đã hoàn thành. Thêm mã sau vào component SingleProject:

    // resources/assets/js/components/SingleProject.js

    handleMarkTaskAsCompleted (taskId) {
      axios.put(`/api/tasks/${taskId}`).then(response => {
        this.setState(prevState => ({
          tasks: prevState.tasks.filter(task => {
            return task.id !== taskId
          })
        }))
      })
    }

Không giống như phương thức handleMarkProjectAsCompleted, hàm handleMarkTaskAsCompleted chấp nhận ID của nhiệm vụ được đánh dấu là đã hoàn thành dưới dạng một đối số. Sau đó, với ID tác vụ, nó thực hiện một yêu cầu HTTP tới API ứng dụng của chúng ta. Khi yêu cầu thành công, chúng ta cập nhật state tasks bằng cách lọc ra tác vụ có ID được chuyển cho phương thức.

Cuối cùng, cập nhật nút Mark as Completed bên cạnh mỗi nhiệm vụ như bên dưới:

    // resources/assets/js/components/SingleProject.js

    <button
      className='btn btn-primary btn-sm'
      onClick={this.handleMarkTaskAsCompleted.bind(this,task.id)}
    >
      Mark as completed
    </button>

Khi nút được nhấp, chúng ta gọi phương thức handleMarkTaskAsCompleted chuyển tới nó là ID tác vụ.

Thử nghiệm ứng dụng

Trước khi thử nghiệm ứng dụng của mình, chúng ta cần biên dịch các tệp JavaScript bằng Laravel Mix bằng cách sử dụng:

  npm run dev

Sau đó, chúng ta cần khởi động ứng dụng:

  php artisan serve

Ứng dụng sẽ chạy trên http://127.0.0.1:8000. Sau đó, chúng ta có thể bắt đầu kiểm tra nó.

Trang chủ ứng dụng

Phần kết luận

Trong hướng dẫn này, chúng ta đã biết cách sử dụng React trong một ứng dụng Laravel. Chúng ta cũng đã biết cách hiển thị thông báo lỗi xác thực Laravel trong một component React. Làm theo cách tiếp cận được sử dụng trong hướng dẫn này cho phép chúng ta có tất cả mã nguồn của mình ở một nơi.

» Tiếp: Cách cài đặt Node.js và tạo môi trường phát triển cục bộ trên macOS
« Trước: Chiến lược SEO React và các phương pháp hay nhất
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!