Connect with us

קטגוריות

Javascript

מדריך: בניית אתר עם React ושליחת מידע בזמן אמת עם Socket.IO ו-Node.js

כשתחפשו ללמוד על Socket.IO אתם תיתקלו  במדריכים שרובם ילמדו אתכם איך לבנות צ׳אט וזה בסדר גמור, אבל חדרי צ׳אט הם רק קצה הקרחון שאפשר לבנות עם Socket.IO, תחשבו על זה רגע, יש עוד מליון ואחד דברים שאפשר לבנות בזמן אמת, קחו לדוגמא רשת חברתית בזמן אמת שפיתחתי לפני כמה שנים על פלטפורמת Meteor על מנת ללמוד את הפלטפורמה שעליה אולי ארחיב במאמר נפרד.

במאמר הזה אני אלמד את השימוש הבסיסי ב-Socket.IO ואולי הפרוייקט הבא שלכם יהיה מבוסס על Socket.IO, בסיום המאמר הזה אנחנו נבנה אתר פשוט בזמן אמת.

למען הסר ספק הקוד שאנחנו הולכים לכתוב לא מתאים לסביבת production ובא רק ללמד ולהמחיש רעיון.

קדימה מתחילים!.

דרישות מקדימות

על מנת שיהיה לכם קל להבין את הנאמר אתם תצטרכו הבנה בסיסית ב-Javascript, Node.js, ו-ExpressJS.

תוכלו להוריד את הגרסה האחרונה של Node מהאתר הראשי (רצוי את גרסת ה-LTS).

ודבר אחרון, על מנת שנוכל לדבר עם ה-API של DarkSky אנחנו צריכים API key, תוכלו להירשם ולקבל אחד כזה מכאן.

על פרוטוקול ה-WebSocket ועל Socket.IO

WebSocket הוא למעשה פרוטוקול תקשורת עם פיצ׳ר נורא מעניין: הוא מספק לנו תקשורת דו-כיוונית מלאה (full-duplex) לחיבור TCP יחיד.

עם WebSockets הקליינט והסרבר יכולים ״לדבר״ בזמן אמת ממש כמו שיחת טלפון, ברגע שהקליינט מחובר הוא יכול לקבל מידע מהסרבר בזמן אמת ללא הצורך ברענון ומצד שני הסרבר יכול לקבל מידע מהקליינט על ידי אותו חיבור, מה שיותר מעניין הוא היכולת של WebSockets לעבוד עם מודל מונחה אירועים: גם הקליינט וגם הסרבר יכולים להגיב לאירועים או הודעות.

לנו המפתחים WebSocket פותח עולם חדש של הזדמנויות ואם אתם תוהים איך אנחנו הולכים לבצע את האימפלמנטציה אז התשובה היא Socket.IO, אחד המנועים (לזמן אמת) הפופולריים ביותר ל-Node.js.

Socket.IO עובד על ידי ״אירועים״ (Events), אתה יכול להאזין לאירוע של חיבור משתמשים וכשמשתמש מתחבר אתם יכולים להריץ פונקציה שמבצעת חישוב כלשהו ובסיום להנפיק הודעה (אירוע בעקרון) על גבי הסוקט, מדהים לא?.

אני אדגיש דבר חשוב: Socket.IO הוא לא הוא לא אימפלמנטציה של WebSocket, מתוך הדוקומנטציה של Socket.io:

Socket.IO משתמש ב-WebSocket כאשר זה מאופשר [..] אך קליינט המבוסס על WebSocket לא יוכל לתקשר עם שרת Socket.IO וקליינט המבוסס על Socket.IO לא יוכל לתקשר עם שרת WebSocket.

חוץ מהעובדה הזאת Socket.IO מתנהג בדיוק כמו WebSockets.

וכעת, עם הידע הבסיסי שלנו בפרוטוקול WebSocket, הגיע הזמן ללכלך את הידיים, קדימה לעבודה!.

בניית השרת: 

צרו תיקייה בשם socket-io-is-awesome 

mkdir socket-io-is-awesome

היכנסו לתקייה החדשה על ידי הפקודה 

cd socket-io-is-awesome

אתחלו את הפרוייקט וצרו את הקובץ package.json עם הפקודה

npm init -y

הערה: אנחנו לא הולכים לשלוח שום מודול ל-NPM אז הוספתי את ארגומנט -y על מנת שלא נצטרך למלא שום פרט וישר ניגש לעבודה.

אנחנו צריכים להתקין את חבילת Socket.io שעליה הרחבנו, את Express, ואת Axios, החבילה של Express תעזור לנו להרים את השרת ואילו Axios יעזור לנו עם הקריאות HTTP שאנחנו נשלח ל-API של DarkSky.

npm i axios express socket.io -S

חלק ראשון: בניית שרת תומך Socket.IO פיצפון

אז הרעיון מאחורי הפרוייקט שלנו הוא פשוט: ציפי רוצה לדעת מה הטמפרטורה הנוכחית בירוחם (למה? אלוהים יודע) והיא גם רוצה את האפשרות לקבל עדכון על הטמפרטורה כל 10 שניות.

השרת ישתמש ב-Socket.IO להנפיק הודעה בכל 10 שניות והקליינט יאזין לאותה הודעה על גבי הסוקט.

יכולתי להוציא את React מהתמונה ולהשתמש ב-Pug או Jade אבל אם אפשר לשלב את React אז למה לא? React היא ספרייה מדהימה לבניית ממשקים שבבלוג הזה עוד ארחיב עליה הרבה.

צרו קובץ בשם app.js בתוך תיקיית הפרויקט, הקובץ הזה יכיל את הקוד של הסרבר.

const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const axios = require("axios");

const port = process.env.PORT || 4001;
const index = require("./routes/index");

const app = express();
app.use(index);

const server = http.createServer(app);

const io = socketIo(server); // < החלק המעניין

// TODO: getApiAndEmit

הקוד למעלה אולי מוכר לכם ברובו: קצת requires, יצירת שרת על ידי Express, והחלק המעניין פה הוא הקריאה ל socketIo לאתחל ולקבל אינסטנס (מופע) חדש על ידי העברה של אובייקט הסרבר, על ידי כך Socket.IO יוכל לעבוד יחד עם Express.

בנוסף, בחלק התחתון תוכלו לראות הערה לגבי פונקצייה שאותה עוד מעט נכתוב.

// TODO: getApiAndEmit

שימו לב שבשורה השביעית השרת מחפש את ה index route, אפילו אם השרת שלנו לא ישרת שום תוכן של HTML אנחנו עדיין צריכים route פשוט שיוכל להאזין לקריאות שנכנסות.

צרו תקייה בשם routes ובתוכה צרו קובץ בשם index.js אשר יכיל את הקוד הבא:

const express = require("express");
const router = express.Router();

router.get("/", (req, res) => {
  res.send({ response: "I am alive" }).status(200);
});

module.exports = router;

כשעובדים עם Socket.IO הפוקנצייה הראשונה (וגם הכי חשובה) שתיתקלו בה היא פונקציית on, הפונקצייה הזאת מקבלת שני ארגומנטים:

  1. השם של האירוע (במקרה שלנו זה “connection”).
  2. callback (שירוץ אחרי כל אירוע של חיבור).

דרך אגב הפונקציית on היא לא יותר מאינסטנס (מופע) של EventEmitter.

כתוצאה מהיצירה של האירוע ״connection״ חוזר אוביקט socket ומועבר ל-callback, על ידי האוביקט הזה אנחנו נוכל לשלוח לקליינט את המידע שלנו.

אם אתם זוכרים ציפי רוצה לדעת מה הטמפרטורה בירוחם כל 10 שניות, אנחנו יכולים להשתמש ב-setInterval בתוך ה-callback ובתוך ה-setInterval אנחנו נשתמש בפונקציית חץ (Arrow function) שתקרא לפונקציית getApiAndEmit שראינו למעלה, אז בואו נכתוב את זה:

let interval;

io.on("connection", socket => {
  console.log("New client connected");
  if (interval) {
    clearInterval(interval);
  }
  interval = setInterval(() => getApiAndEmit(socket), 10000);
  socket.on("disconnect", () => {
    console.log("Client disconnected");
  });
});

כמו שאנחנו יכולים להאזין לאירוע של חיבור משתמשים (connection), אנחנו יכולים להאזין לאירוע של ניתוק משתמשים (disconnect), שימו לב בשורה עשירית איך יצרנו את האירוע שבעזרתו נוכל להאזין לניתוק של המשתמשים, ב-callback אנחנו לא עושים הרבה רק מדפיסים (שנדע שהיה אירוע כזה).

כעת אנחנו יכולים לגרום לשרת להאזין לבקשות:

server.listen(port, () => console.log(`Wee wee i'm on on port ${port}`));

זוכרים את הפונקציה getApiAndEmit? היא מקבלת ארגומנט מסוג socket, ה-socket הוא בעצם הערוץ תקשורת שלנו בין הסרבר לקליינט.


const getApiAndEmit = async socket => {
  try {
    const res = await axios.get(
      "https://api.darksky.net/forecast/db057094f57e2e5e1f8d33d5e528e4b3/30.9871097,34.9408864"
    );

    let temp = (((res.data.currently.temperature - 32) * 5) / 9).toFixed(2);

    socket.emit("FromAPI", temp);
  } catch (error) {
    console.error(`Error: ${error.code}`);
  }
};

הפונקציה getApiAndEmit מקבלת ארגומנט מסוג socket ומבצעת קריאת HTTP ל-API של DarkSky (אל תשכחו להוסיף לכתובת את ה-API KEY שלכם) ולבסוף מנפיקה את ההודעה FromAPI אשר מכילה את הטמפרטורה הנוכחית בירוחם.

סיימנו עם השרת, הקוד המלא של app.js אמור להראות ככה:

const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const axios = require("axios");
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const app = express();
app.use(index);
const server = http.createServer(app);
const io = socketIo(server);

let interval;

io.on("connection", socket => {
  console.log("New client connected");
  if (interval) {
    clearInterval(interval);
  }
  interval = setInterval(() => getApiAndEmit(socket), 10000);
  socket.on("disconnect", () => {
    console.log("Client disconnected");
  });
});


const getApiAndEmit = async socket => {
  try {
    const res = await axios.get(
      "https://api.darksky.net/forecast/***PUT_YOUR_API_KEY_HERE***/30.9871097,34.9408864"
    );

    let temp = (((res.data.currently.temperature - 32) * 5) / 9).toFixed(2);

    socket.emit("FromAPI", temp);
  } catch (error) {
    console.error(`Error: ${error.code}`);
  }
};

server.listen(port, () => console.log(`Wee wee i'm on port ${port}`));

אנחנו יכולים לבדוק את השרת (חדש מהניילונים!) שלנו על ידי הרצת הפקודה

node app.js

ברגע הראשון שהשרת באוויר אנחנו אמורים לראות הודעה:

Wee wee i'm on port 4001

שמאשרת לנו שהכל עובד כמו שצריך.

חלק שני: בניית הקליינט

עכשיו כשהשרת הפיצפון שלנו למעלה זה הזמן להשתמש במידע שהוא רוצה להעביר לנו, אנחנו רוצים להציג את הטמפרטורה הנוכחית בירוחם ואנחנו נשתמש בריאקט (React) לעשות את העבודה.

התקינו את create-react-app על ידי הרצה של הפקודה

npm i -g create-react-app

בשלב הבא צרו פרוייקט ריאקטי חדש על ידי 

create-react-app socket-io-is-awesome-client

היכנסו לתקייה החדשה בעזרת הפקודה

cd socket-io-is-awesome-client

והתקינו את החבילה של Socket.IO אשר מותאמת לצד הקליינט

npm i socket.io-client -S

ואחרי שסיימנו עם כל ההכנות המקדימות, אנחנו יכולים להרים את הפרוייקט על ידי הפקודה

npm start

על מנת להשאיר את הדברים פשוטים אנחנו נשתמש בקומפוננטה App.js שנמצאת בתוך תיקיית ה-src.

פתחו את App.js אתם יכולים להחליף את כל התוכן של הקובץ עם הקוד הבא:

import React, { Component } from "react";
import socketIOClient from "socket.io-client";

class App extends Component {
  constructor() {
    super();
    this.state = {
      response: false,
      endpoint: "http://127.0.0.1:4001"
    };
  }

  componentDidMount() {
    const socket = socketIOClient(this.state.endpoint);
    socket.on("FromAPI", data => this.setState({ response: data }));
  }

  render() {
    const { response } = this.state;
    return (
      <div style={{ textAlign: "center" }}>
        {response ? (
          <p>הטמפרטורה בירוחם היא {response} מעלות</p>
        ) : (
          <p>טוען את הטמפרטורה בירוחם...</p>
        )}
      </div>
    );
  }
}

export default App;

אז אחרי שהחלפת ושמרתם גשו בעזרת הדפדפן לכתובת http://localhost:3000 וחכו בערך 10 שניות, אתם אמורים לראות את הפלט הבא:

חדי העין יבחינו כי הטמפרטורה משתנה כל 10 שניות.

ברגע שהקומפוננטה של ריאקט נטענת, פונקציית ה-componentDidMount יוצרת חיבור חדש לשרת ה-Socket.IO שלנו על ידי 

const socket = socketIOClient(endpoint);

אם אתם זוכרים ה-socket הוא ערוץ תקשורת ואנחנו יכולים להאזין לכל אירוע שקיים:

const socket = socketIOClient(endpoint);

אם תסתכלו על הקוד של השרת שלנו, האירוע FromAPI מונפק ברגע שהמשתמש החדש מתחבר לשרת.

הקליינט יכול להאזין לאירוע בעזרת הפונקציה on ולעשות משהו עם המידע שהוא מקבל, במקרה שלנו אנחנו שומרים את המידע ב-state של הקומפוננטה בעזרת this.setState.

ו…. סיימנו! ברגע שהחיבור בוצע הקליינט יכול לקבל עידכונים מהשרת ללא הצורך ברענון.

אז תגיד מני, לאיפה ממשיכים מכאן?

למרות שהדוגמא שלנו היא בסיסית ביותר זה אמור להיות ברור איך Socket.IO לא מתאימה רק לצ׳אטים אלא היא יכולה להתאים למגוון רחב של האפשרויות או בקיצור השמיים הם הגבול.

אני מציע לכם לחקור את הדוקומנטציה של Socket.IO ללמוד על Rooms, Namespaces ועוד הרבה כלים שה-API של Socket.io מציע.

בנוסף הייתי מציע לכם להבין טוב יותר את הארכיטקטורה של Node.js אשר מונחת אירועים, מקום טוב להתחיל? בדוקומנטציה של Node!

עד הפעם הבאה, פיס אנד לוב.

אם אתה חושב שיש מידע שהחסרתי או שיש דברים לא מדוייקים או לא מובנים במאמר, אשמח אם תאיר את עיניי באמצעות תגובה או דרך יצירת קשר בתפריט העליון.
תגובות

עוד ב Javascript

Meni Edri היי אני מני, מתכנת מנוסה ומאוד אוהב קוד ואת עולם התכנות, הקמתי את CodeHub על מנת שתיהיה לי פינה שבה אוכל לכתוב, ללמד וללמוד, מקווה שתימצאו את הבלוג הזה שימושי.
קריאה מהנה.

מאמרים פופולריים

נושאים פופולריים

חזרה למעלה