۲۷ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

توسعه تمام پشته با جاوا، React و Spring Boot، قسمت ۱

React و Java به طور یکپارچه در این مقدمه سه قسمتی برای توسعه فول استک با React و Spring Boot کنار هم قرار می گیرند. قسمت 1 شما را با یک چارچوب برنامه کاربردی شروع می کند که می توانید در صورت نیاز آن را سفارشی کنید.

React و Java به طور یکپارچه در این مقدمه سه قسمتی برای توسعه فول استک با React و Spring Boot کنار هم قرار می گیرند. قسمت ۱ شما را با یک چارچوب برنامه کاربردی شروع می کند که می توانید در صورت نیاز آن را سفارشی کنید.

یکی از محبوب‌ترین پشته‌های امروزی ترکیب Spring Java در انتهای پشتی با یک React front end است. پیاده سازی یک برنامه Full Stack Spring Java React به تصمیمات زیادی در طول مسیر نیاز دارد. این مقاله به شما کمک می‌کند تا با چیدمان یک ساختار پروژه برای هر دو انتهای پشته، و سپس توسعه برنامه‌ای که از عملیات اولیه CRUD پشتیبانی می‌کند، شروع کنید. دو مقاله بعدی من بر مبنایی است که در اینجا با ادغام یک datastore و استقرار برنامه در محیط تولید.

شروع به کار با Spring Java و React

دلایل بسیار خوبی برای محبوبیت طولانی مدت جاوا به عنوان یک پلتفرم سمت سرور وجود دارد. این بلوغ و گستردگی بی نظیر را با سابقه طولانی و مداوم نوآوری ترکیب می کند. استفاده از اسپرینگ، قابلیت‌های زیادی را به قسمت پشتی شما اضافه می‌کند. فراگیر بودن React و اکوسیستم کاملاً تحقق یافته آن را به انتخابی آسان برای ساخت قسمت جلویی تبدیل کرده است.

برای اینکه طعم این پشته را بچشید، یک برنامه Todo ایجاد می کنیم که از هر یک از این فناوری ها استفاده می کند. اگر معرفی اخیر من در را بخوانید، ممکن است مثال آشنا باشد. HTMX برای جاوا با Spring Boot و Thymeleaf. در اینجا نگاهی به رابط کاربری برنامه Todo داریم:

عکس از برنامه React Spring Boot CRUD.

متیو تایسون

در حال حاضر، ما فقط موارد کار را در حافظه سرور ذخیره می‌کنیم.

راه اندازی Spring and React

راه‌های مختلفی برای تنظیم React و Spring با هم وجود دارد. اغلب مفیدترین رویکرد داشتن دو پروژه مجزا و تمام عیار است که هر کدام خط لوله ساخت خود را دارند. ما این کار را در اینجا انجام خواهیم داد. اگر ترجیح می دهید بر روی ساخت جاوا تمرکز کنید و بیلد React را ثانویه کنید، از JHipster.

تنظیم دو بیلد مجزا کار افراد یا تیم‌های مختلف را تنها بر روی یک جنبه از برنامه آسان‌تر می‌کند. برای شروع، یک پروژه Spring Boot جدید از خط فرمان ایجاد خواهیم کرد:

$ spring init iw-react-spring --dependencies=web --build=maven

این دستور یک پروژه اساسی را با پشتیبانی از APIهای وب ارائه می کند. همانطور که می بینید، ما از Maven برای ابزار ساخت استفاده می کنیم. اکنون می توانیم به دایرکتوری جدید برویم:


$ cd iw-react-spring

قبل از انجام هر کار دیگری، بیایید پروژه React را اضافه کنیم. می‌توانیم آن را با فراخوانی create react app از فهرست react-spring ایجاد کنیم:


/iw-react-spring/src/main$ npx create-react-app app

اکنون یک فهرست src/main/app داریم که حاوی برنامه React ما است. ویژگی‌های این راه‌اندازی این است که می‌توانیم کل برنامه را، هر دو طرف، به یک مخزن اختصاص دهیم، سپس آنها را به طور جداگانه در طول توسعه اجرا کنیم.

کد را دریافت کنید

کد برنامه تمام شده را در مخزن GitHub من پیدا کنید.

اگر آنها را امتحان کنید، خواهید دید که هر دو برنامه اجرا می شوند. می‌توانید برنامه Spring را با:

شروع کنید


/iw-react-spring$ mvn spring-boot:run

برای شروع برنامه React، وارد کنید:


/iw-react-spring/app$ npm start

Spring در localhost:8080 گوش می‌دهد در حالی که React به localhost:3030 گوش می‌دهد. Spring هنوز کاری انجام نمی دهد، و React یک صفحه خوشامدگویی عمومی به شما ارائه می دهد.

در اینجا طرح کلی پروژه تا کنون آمده است:

  • /iw-react-spring
    • /app – حاوی برنامه React است
      • /app/src – حاوی منابع واکنش است
    • /src – حاوی منابع Spring
    • است

  • /app – حاوی برنامه React است
    • /app/src – حاوی منابع واکنش است
  • /app/src – حاوی منابع واکنش است
  • /src – حاوی منابع Spring
  • است

صفحه پشتی جاوا Spring

اولین چیزی که ما نیاز داریم یک کلاس model برای انتهای پشتی است. ما آن را به src/main/java/com/example/iwreactspring/model/TodoItem.java اضافه می کنیم:


package com.example.iwjavaspringhtmx.model;

public class TodoItem {
  private boolean completed;
  private String description;
  private Integer id;
  public TodoItem(Integer id, String description) {
    this.description = description;
    this.completed = false;
    this.id = id;
  }
  public void setCompleted(boolean completed) {
    this.completed = completed;
  }
  public boolean isCompleted() {
    return completed;
  }
  public String getDescription() {
    return description;
  }
  public Integer getId(){ return id; }
  public void setId(Integer id){ this.id = id; }
  @Override
  public String toString() {
    return id + " " + (completed ? "[COMPLETED] " : "[ ] ") + description;
  }
}

این یک POJO ساده است که داده‌های یک todo را نگهداری می‌کند. هنگامی که چهار نقطه پایانی API را که برای فهرست کردن، اضافه کردن، به‌روزرسانی و حذف کارهای انجام شده نیاز داریم، مدیریت می‌کنیم، از آن برای جابه‌جایی در اطراف موارد کار استفاده می‌کنیم. ما آن نقاط پایانی را در کنترلر خود در src/main/java/com/example/iwreactspring/controller/MyController.java مدیریت خواهیم کرد:


package com.example.iwreactspring.controller;

  private static List<TodoItem> items = new ArrayList<>();

  static {
    items.add(new TodoItem(0, "Watch the sunrise"));
    items.add(new TodoItem(1, "Read Venkatesananda's Supreme Yoga"));
    items.add(new TodoItem(2, "Watch the mind"));
  }


@RestController
public class MyController {
  private static List<TodoItem> items = new ArrayList<>();

  static {
    items.add(new TodoItem(0, "Watch the sunrise"));
    items.add(new TodoItem(1, "Read Swami Venkatesananda's Supreme Yoga"));
    items.add(new TodoItem(2, "Watch the mind"));
  }

  @GetMapping("/todos")
  public ResponseEntity<List<TodoItem>> getTodos() {
    return new ResponseEntity<>(items, HttpStatus.OK);
  }

  // Create a new TODO item
  @PostMapping("/todos")
  public ResponseEntity<TodoItem> createTodo(@RequestBody TodoItem newTodo) {
    // Generate a unique ID (simple approach for this example)
    Integer nextId = items.stream().mapToInt(TodoItem::getId).max().orElse(0) + 1;
    newTodo.setId(nextId);
    items.add(newTodo);
    return new ResponseEntity(newTodo, HttpStatus.CREATED);
  }

  // Update (toggle completion) a TODO item
  @PutMapping("/todos/{id}")
  public ResponseEntity<TodoItem> updateTodoCompleted(@PathVariable Integer id) {
    System.out.println("BEGIN update: " + id);
    Optional<TodoItem> optionalTodo = items.stream().filter(item -> item.getId().equals(id)).findFirst();
    if (optionalTodo.isPresent()) {
      optionalTodo.get().setCompleted(!optionalTodo.get().isCompleted());
      return new ResponseEntity(optionalTodo.get(), HttpStatus.OK);
    } else {
      return new ResponseEntity(HttpStatus.NOT_FOUND);
    }
  }

  // Delete a TODO item
  @DeleteMapping("/todos/{id}")
  public ResponseEntity<Void> deleteTodo(@PathVariable Integer id) {
    System.out.println("BEGIN delete: " + id);
    Optional<TodoItem> optionalTodo = items.stream().filter(item -> item.getId().equals(id)).findFirst();
    System.out.println(optionalTodo);
    if (optionalTodo.isPresent()) {
      items.removeIf(item -> item.getId().equals(optionalTodo.get().getId()));
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }
}

MyController.java

برای مشاهده کامل کنترلر، از جمله واردات، آن را پیدا کنید اینجا.

کلاس ArrayList و روش های HTTP

علاوه بر نقاط پایانی خود، یک ArrayList (اقلام) برای نگهداری کارهای انجام شده در حافظه داریم و با استفاده از یک بلوک ثابت، آن را با چند مورد از قبل پر می کنیم. ما خود کلاس را با @RestController Spring، حاشیه‌نویسی می‌کنیم. این یک روش مختصر برای گفتن به Spring Web است: روش‌های HTTP را در این کلاس مدیریت کنید و به من اجازه دهید مقادیر را از متدها به عنوان پاسخ برگردانم.

هر روش با یک حاشیه نویسی نقطه پایان تزئین شده است، مانند @DeleteMapping("/todos/{id}")، که می گوید: این روش درخواست های HTTP DELETE را در /todos/{id}، که در آن {id} هر مقداری خواهد بود که در مسیر درخواست در em>{id} موقعیت. متغیر {id} در روش با استفاده از آرگومان متد مشروح (@PathVariable Integer id) بدست می آید. این یک راه آسان برای گره زدن پارامترها در مسیر متغیرها در کد متد شما است.

منطق در هر روش نقطه پایانی ساده است و فقط در برابر فهرست آرایه items عمل می کند. نقاط پایانی از کلاس ResponseEntity برای مدل‌سازی استفاده می‌کنند. پاسخ، که به شما امکان می دهد بدنه پاسخ (در صورت نیاز) و وضعیت HTTP را نمایش دهید. @RestController application/json را به‌عنوان نوع پاسخ فرض می‌کند، این همان چیزی است که ما برای React front end خود می‌خواهیم.

فرانت اند React

اکنون که یک بک اند کار داریم، بیایید روی رابط کاربری تمرکز کنیم. به دایرکتوری /iw-react-spring/src/main/app بروید و ما روی فایل App.js کار خواهیم کرد که تنها کد جلویی است. ما نیاز داریم (به جز کمی CSS معمولی در App.css>). بیایید فایل /iw-react-spring/src/main/app/App.js را در دو بخش در نظر بگیریم: کد و نشانه گذاری الگو، که با علامت گذاری شروع می شود:


<div className="App">
  <header className="App-header"><h1>My TODO App</h1></header>
    <input id="todo-input" type="text" placeholder="Add a TODO" />
          <button onClick={(e) => addTodo(document.getElementById('todo-input').value)}>Add TODO</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input type="checkbox" checked={todo.completed} onChange={() => toggleTodoComplete(todo.id)} />
            {todo.description}
            <button onClick={() => deleteTodo(todo.id)}>🗑</button>
      </li>
    ))}
  </ul>
</div>

برای دریافت فایل کامل، مخزن‌خانه GitHub من را ببینید.

در اینجا، اجزای اصلی یک کادر ورودی با شناسه todo-input، یک دکمه برای ارسال آن با استفاده از تابع addTodo() و یک عنصر لیست نامرتب است. که با حلقه زدن روی متغیرهای todos پر می شود. هر todo یک چک باکس متصل به تابع toggleTodoComplete، فیلد todo.description و دکمه‌ای برای حذف دریافت می‌کند که deleteTodo( را فراخوانی می‌کند. ).

در اینجا عملکردهایی برای مدیریت این عناصر رابط کاربری وجود دارد:


import './App.css';
import React, { useState, useEffect } from 'react';

function App() {
  const [todos, setTodos] = useState([]);

  // Fetch todos on component mount
  useEffect(() => {
    fetch('http://localhost:8080/todos')
      .then(response => response.json())
      .then(data => setTodos(data))
      .catch(error => console.error(error));
  }, []);

  // Function to add a new TODO item
  const addTodo = (description) => {
    fetch('http://localhost:8080/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ description }),
    })
      .then(response => response.json())
      .then(newTodo => setTodos([...todos, newTodo]))
      .catch(error => console.error(error));
  };

  // Toggle completion
  const toggleTodoComplete = (id) => {
    const updatedTodos = todos.map(todo => {
      if (todo.id === id) {
        return { ...todo, completed: !todo.completed };
      }
      return todo;
    });
    setTodos(updatedTodos);

    // Update completion 
    fetch(`http://localhost:8080/todos/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ completed: !todos.find(todo => todo.id === id).completed }),
    })
      .catch(error => console.error(error));
  };
  const deleteTodo = (id) => {
    const filteredTodos = todos.filter(todo => todo.id !== id);
    setTodos(filteredTodos);
    fetch(`http://localhost:8080/todos/${id}`, {
      method: 'DELETE'
    })
    .catch(error => console.error(error));
  };

ما توابعی برای ایجاد، تغییر وضعیت تکمیل و حذف داریم. برای بارگیری اولیه کارها، از افکت useEffect استفاده می‌کنیم تا زمانی که React برای اولین بار UI را بارگیری می‌کند، سرور را برای مجموعه اولیه کارها فراخوانی می‌کنیم. (به معرفی React Hooks برای کسب اطلاعات بیشتر در مورد useEffect.)

useState به ما امکان می دهد متغیر todos را تعریف کنیم. Fetch API تعریف تماس های پشتیبان و کنترل کننده های آنها را با سپس و catch بسیار تمیز می کند. عملگر spread نیز به مختصر نگه داشتن مطالب کمک می کند. در اینجا نحوه تنظیم فهرست جدید todos آمده است:

newTodo => setTodos([...todos, newTodo]),

در اصل، ما می گوییم: همه todoهای موجود، به علاوه newTodo را بارگیری کنید.

نتیجه گیری

جاوا و اسپرینگ همراه با React راه‌اندازی قدرتمندی را ارائه می‌دهند که می‌تواند هر چیزی را که به آن پرتاب می‌کنید کنترل کند. تاکنون، برنامه نمونه Todo ما دارای تمام اجزای ضروری برای اتصال قسمت جلویی به انتهای پشتی است. این به شما یک پایه محکم برای استفاده برای برنامه های کاربردی در هر اندازه ای می دهد. مراقب دو مقاله بعدی باشید، جایی که ما یک دیتا استور اضافه می کنیم و برنامه را برای تولید مستقر می کنیم.

پرش به قسمت ۲: توسعه برنامه برای ماندگاری با MongoDB و Spring Data.