Στο βιβλίο μου Maintainable React, παρουσίασα ένα χαρακτηριστικό που είχα δουλέψει πριν από λίγο καιρό. Αυτή η δυνατότητα είναι ενδιαφέρουσα από πολλές απόψεις και την επέλεξα καθώς περιλαμβάνει πολλές καταστάσεις στην προβολή — που είναι ένας από τους λόγους για τους οποίους η δημιουργία διεπαφής χρήστη είναι περίπλοκη.

Δεν είναι τυπικές καταστάσεις διεπαφής χρήστη αλλά καταστάσεις κρυφής μνήμης διακομιστή, όπως περιγράφεται στο άρθρο του Kent εδώ. Όλα τα αιτήματα δικτύου θα μπορούσαν να πάνε στραβά, να λήξουν το χρονικό όριο, πάρα πολλά αιτήματα ή ακόμα και χρόνο διακοπής της υπηρεσίας. Στις προβολές, πρέπει να αντικατοπτρίζουμε αυτές τις καταστάσεις δικτύου αντίστοιχα.

Διαφορετική κατάσταση

Η δυνατότητα Άμεση εκκίνηση εδώ είναι ότι όταν ο χρήστης προσγειωθεί στη σελίδα στην αρχή, πρέπει να ελέγξουμε την τρέχουσα κατάσταση μιας παραγγελίας. Εάν είναι σε εξέλιξη, πρέπει να εμφανίσουμε ένα απενεργοποιημένο κουμπί I'm here και μερικά μηνύματα υπόδειξης. Και κάποια στιγμή, όταν η παραγγελία είναι έτοιμη για παραλαβή, το κουμπί πρέπει να είναι ενεργοποιημένο, ώστε να μπορούμε να το κάνουμε κλικ για να ειδοποιήσουμε το κατάστημα. Όταν κάνετε κλικ στο κουμπί, αποστέλλεται μια ειδοποίηση και η παραγγελία θα παραδοθεί απευθείας στο πορτμπαγκάζ του οχήματός σας. Επιπλέον, αν κάτι πάει στραβά, δείχνουμε έναν αριθμό ως εναλλακτικό για να καλέσουμε το κατάστημα τηλεφωνικά.

Πρέπει λοιπόν να λάβουμε υπόψη τουλάχιστον τα εξής:

  • Λήψη δεδομένων από τον διακομιστή
  • Δοκιμάστε ξανά εάν κάτι πάει στραβά στο δίκτυο
  • Δοκιμάστε ξανά εάν επιστραφούν τα δεδομένα αλλά όχι αυτό που θέλαμε
  • Σταματήστε να προσπαθείτε ξανά όταν αποτύχαμε πάρα πολλές φορές
  • Ενημέρωση δεδομένων στον διακομιστή
  • Χειρισμός σφαλμάτων για κάθε αίτημα δικτύου

Αν σχεδιάσουμε ένα statechart για την παραπάνω περιγραφή, θα είναι κάτι σαν το παρακάτω διάγραμμα. Προτού η παραγγελία είναι ready, πρέπει να προσπαθήσουμε ξανά μερικές φορές. Και η ειδοποίηση μπορεί να έχει έναν άλλο μετρητή επανάληψης (δεν το κάνουμε σε αυτό το γράφημα). Επίσης, κάθε αίτημα δικτύου θα μπορούσε να οδηγήσει σε σφάλμα. Σημειώστε ότι η ευτυχής διαδρομή (αδράνεια → έτοιμη → ειδοποιημένη) είναι μόνο ένα από τα διακλάδια.

Το χαρούμενο μονοπάτι

Μπορούμε να ξεκινήσουμε με το ευτυχισμένο μονοπάτι καθώς αυτό είναι το πιο εύκολο βήμα και το πιο σημαντικό πράγμα που θέλουμε να διασφαλίσουμε ότι θα συμβεί.

Στη δοκιμή, η ευτυχής διαδρομή για (αρχικοποιημένο → έτοιμο) μπορεί να περιγραφεί ως:

Χλευάζοντας το αίτημα για ευτυχισμένο μονοπάτι

Στο βιβλίο μου, χρησιμοποίησα τον msw ως κοροϊδευτικό διακομιστή και λειτούργησε αρκετά καλά. Χρησιμοποιώ το mirage.js εδώ για απλότητα. Επίσης μου αρέσει το μέρος της μοντελοποίησης δεδομένων της βιβλιοθήκης. Δεν έχει μεγάλη σημασία εδώ, και μπορείτε να χρησιμοποιήσετε οποιοδήποτε από τα δύο.

Για παράδειγμα, μπορούμε να ορίσουμε ένα get API για τον έλεγχο της κατάστασης παραγγελίας στη δοκιμή. Αναχαιτίζει αιτήματα που αποστέλλονται στο /api/orders/<id> και επιστρέφει πάντα ένα αντικείμενο με κατάσταση ready.

import { createServer } from "miragejs";

const createMockServer = () => createServer({
  routes() {
    this.get("/api/orders/:id", (schema, request) => {
      return {
        id: request.params.id,
        status: "ready",
      };
    });
  }
})

Στη συνέχεια, δημιουργούμε έναν διακομιστή στην αρχή κάθε δοκιμής και τον τερματίζουμε στο τέλος.

describe("Direct To Boot", () => {
  beforeEach(() => {
    server = createMockServer();
  })
  
  afterEach(() => {
    server.shutdown()
  })
  
  //...
});

Το API φαίνεται διαισθητικό και λειτουργεί όπως αναμενόταν. Στο στοιχείο μας, μπορούμε να στέλνουμε αιτήματα και να καταναλώνουμε την απάντηση όπως θα έκανε για τα πραγματικά API.

Υλοποίηση ανάκτησης με react-query

Στο προηγούμενο άρθρο μου, συζήτησα λεπτομερώς πόσα ασήμαντα πράγματα πρέπει να λάβετε υπόψη για να κάνετε ένα απλό αίτημα δικτύου. Επίσης, πόσο πιο απλό είναι αν χρησιμοποιήσετε το react-query αντί να το εφαρμόσετε με μη αυτόματο τρόπο.

Για να χρησιμοποιήσουμε το react-query, πρώτα θα πρέπει να ορίσουμε μια συνάρτηση query. Σημειώστε εδώ εάν το res.data.status δεν είναι ready, θα εμφανίσει σφάλμα. Και το react-query μπορεί να εντοπίσει αυτό το σφάλμα και να ενεργοποιήσει ένα refetch εάν έχει ρυθμιστεί.

const fetchOrder = (orderId: string) => {
  return axios.get(`/api/orders/${orderId}`).then((res) => {
    if (res.data.status === "ready") {
      return res.data;
    } else {
      throw new Error("fetch error");
    }
  });
};

Τώρα με τη συνάρτηση fetchQuery, μπορώ να καλέσω το useQuery και να τυλίξω ολόκληρη τη λογική μέσα σε ένα άγκιστρο useOrder

const useOrder = (orderId: string) => {
  const [status, setStatus] = useState<string>("initialised");

  useQuery(["fetchOrder"], () => fetchOrder(orderId), {
    retry: 5,
    onError: () => setStatus("error"),
    onSuccess: () => setStatus("ready"),
  });
    return { status }
}

Ρύθμισα την επανάληψη ως 5, οπότε όποτε συνέβη ένα πραγματικό σφάλμα (ας πούμε, 500 από την πλευρά του διακομιστή) ή όταν το res.data.status δεν είναι ready, ο react-query θα προσπαθήσει ξανά. Και δεν ξαναπροσπαθεί αμέσως, αλλά περιμένει για λίγο ως καθυστέρηση μεταξύ κάθε αποτυχίας.

Προσομοίωση σφάλματος

Στο mirage.js, η προσομοίωση ενός σφάλματος για να πιάσει το τεστ είναι αρκετά απλή. Βρήκα επίσης χρήσιμο να υπάρχουν πολλά ids που θα ενεργοποιούσαν σφάλματα, ώστε να μπορείτε να δοκιμάσετε διαφορετική λογική διαχείρισης σφαλμάτων.

Για παράδειγμα, μπορούμε να ορίσουμε μια λίστα με ids που υποδεικνύουν σφάλματα όταν χρησιμοποιούνται.

this.get("/api/orders/:id", (schema, request) => {
  if(['error-id', 'timeout-id'].includes(request.params.id)) {
    return new Response(500, {}, {error: "something went wrong"});
  }
  
  return {
    id: request.params.id,
    status: "ready",
  };
});

Και στη συνέχεια, στη δοκιμή μας, μπορούμε να χρησιμοποιήσουμε είτε error-id είτε timeout-id ως orderId για να προσομοιώσουμε το σφάλμα:

it("shows a fallback call the store button", async () => {
  render(<DirectToBoot orderId="error-id"/>);

  await waitFor(() =>
    expect(
      screen.getByText("Seems something went wrong...")
    ).toBeInTheDocument(), { timeout: 3000});
  
  const button = screen.getByText("04 23 33");
  await waitFor(() => expect(button).toBeInTheDocument(), {timeout: 3000})
});

Προσομοίωση επαναλήψεων όταν το αίτημα απέτυχε

Στη λειτουργία μας, θέλουμε επίσης να προσομοιώσουμε τη σειρά long-running για να βεβαιωθούμε ότι η διεπαφή χρήστη δεν θα αποκλειστεί από την αρχική ανάκτηση. Μπορούμε να το προσομοιώσουμε ορίζοντας μια μεταβλητή με κατάσταση initialised και αργότερα να χρησιμοποιήσουμε ένα χρονόμετρο για να ενημερώσουμε την τιμή.

const longRunningOrder = {
  id: 'long-running-order',
  status: "initialised",
}

//...
const createMockServer = () => createServer({
  routes() {
    this.get("/api/orders/:id", (schema, request) => {
      if(['long-running-order'].includes(id)) {
        const timerId = setTimeout(() => {
          longRunningOrder.status = 'ready'
          clearTimeout(timerId);
        }, 2000)
        return longRunningOrder;
      }
    });
  }
})

Στη συνέχεια, μετά από πολλές επαναλήψεις, η προβολή αποκτά τελικά την κατάσταση ready και είναι έτοιμη να ειδοποιήσει το κατάστημα. Σημειώστε στην κονσόλα ότι το «mirage» είχε κάνει τρεις επαναλήψεις σε αυτήν την περίπτωση.

Ο τελικός κωδικός

Έχοντας react-query για να χειριστεί όλη τη λογική που σχετίζεται με το δίκτυο, καθώς μπορείτε να φανταστείτε πόσο απλό μπορεί να απλοποιηθεί το ίδιο το τελικό στοιχείο Direct To Order.

export function DirectToBoot({ orderId }: { orderId: string }) {
  const {status, notifyStore} = useOrder(orderId);

  return (
    <div className="container">
      <h3>Direct to boot</h3>
      <p>{getMessage(status)}</p>
      <div className="buttonContainer">
        {createButton(status, notifyStore)}
      </div>
    </div>
  );
}

Ουσιαστικά λοιπόν, το στοιχείο DirectToBoot δέχεται το orderIdως παράμετρο, χρησιμοποιώντας ένα άγκιστρο για να πάρει την κατάσταση και μια συνάρτηση για να ενημερώσει την κατάσταση.

Χρησιμοποιούμε το άγκιστρο ως μηχάνημα κατάστασης, όπως ακριβώς και η statechart που παρουσιάσαμε στην αρχή του άρθρου.

Περίληψη

Σε αυτό το άρθρο, συζητήσαμε πώς να χρησιμοποιήσετε το mirage.js για να απλοποιήσετε την κοροϊδία του δικτύου είτε για αδύνατες είτε για δύσκολες περιπτώσεις, εάν μιλάτε με πραγματικά API στον κώδικα διεπαφής. Εξετάσαμε την ευτυχισμένη διαδρομή, τον χειρισμό σφαλμάτων και τις επαναλήψεις με το mirage.js, επίσης πόσο εύκολο είναι να χρησιμοποιήσετε το react-query για να απλοποιήσετε τη λογική της εφαρμογής του κώδικα που σχετίζεται με το δίκτυο.

Μπορείτε να βρείτε τον πλήρη πηγαίο κώδικα για αυτό το άρθρο εδώ. Και αν προτιμάτε βίντεο, τα έχω δημοσιεύσει στο κανάλι μου στο YouTube σε βίντεο διάρκειας 2 ωρών.

Εάν σας αρέσει η ανάγνωση, παρακαλούμε Εγγραφείτε στη λίστα αλληλογραφίας μου. Μοιράζομαι τις τεχνικές Clean Code και Refactoring κάθε εβδομάδα μέσω blogs, books και videos.