Вы когда-нибудь задумывались, как создаются приложения в реальном времени? Вы когда-нибудь замечали важность и варианты использования приложений реального времени?
Если вам интересны приведенные выше вопросы и вам нужен ответ, то этот пост для вас.
Во-первых, давайте определим несколько вариантов использования, требующих приложений реального времени:
- Получение обновлений местоположения вашего такси на карте в приложении для бронирования такси.
- Мгновенное получение новых сообщений в вашем любимом приложении для чата.
- Информация о заказе еды обновляется на кухне вашего любимого ресторана.
Все это обычные сценарии нашей повседневной жизни, когда мы не можем терпеть задержки в обновлении информации и, следовательно, нуждаемся в общении в реальном времени.
Технологии, которые можно использовать для связи в реальном времени:
- Короткий опрос: AJAX, создает большой трафик.
- Длительный опрос: аналогично AJAX, но сервер задерживает ответ до тех пор, пока не получит обновление. После его получения клиент отправляет другой запрос и требует, чтобы дополнительный заголовок проходил туда и обратно, вызывая дополнительные накладные расходы.
- Веб-сокеты: позволяют установить интерактивную связь между клиентом и сервером. Можно отправить запрос на сервер и получить ответы, управляемые событиями, без запроса ответа на сервере, что делает веб-сокеты лучшим выбором для нашего случая использования.
Более подробную информацию о трех вышеперечисленных технологиях можно прочитать здесь.
Мы научимся создавать приложения реального времени, рассмотрев следующий сценарий.
Представьте, что вы сидите в любимом ресторане и у вас есть цифровое меню. Вы размещаете заказ, и кухня получает информацию о вашем заказе в режиме реального времени. Когда на кухне готов заказ, они тоже обновляют его в режиме реального времени.
Подробнее об особенностях:
- Разместить заказ: интерфейс для выбора количества и размещения заказа на выбранный продукт питания на кухне.
- Кухня: интерфейс, который можно открыть на нескольких кухнях, и в режиме реального времени обновляет повара и повара в отношении общего количества созданных заказов и прогнозируемого количества продуктов, что дает им возможность обновлять его. Также есть функция загрузки отчета в виде таблицы Excel.
- Прогнозируемое изменение: интерфейс для обновления прогнозируемого количества продуктов.
Живую демонстрацию этого сценария можно найти здесь.
Для лучшего понимания откройте его на разных вкладках / устройствах одновременно, чтобы увидеть изменение данных в реальном времени.
Исходный код находится здесь. Не стесняйтесь делать что-нибудь новаторское / полезное поверх этого.
Итак, приступим.
Стек технологий:
Интерфейс: React.js, Reactstrap, Socket.io.
Серверная часть: Node.js (Express), MongoDB, Socket.io.
Структура папки:
/* Go to the root directory in the source code and find out the below-mentioned files. This architecture helps in creating a big modular App. */ backend-my-app/ /* Backend code of the app */ server.js /* Socket and backend code resides here*/ build/ /* Optional for deployment of Frontend Build */ package.json /* Backend dependency */ ... public/ src/ /* Frontend Sourcecode */ global/ /* Components getting used everywhere */ header.css header.js main/ Kitchen.js PlaceOrder.js UpdatePredicted.js App.js /* Routing logic and component assembly part */ package.json /* Frontend dependency */ ............
Объяснение исходного кода:
Внешний интерфейс:
git clone https://github.com/honey93/OrderKitchen.git cd OrderKitchen npm install npm start
Используемые пакеты:
- Reactstrap: Простые в использовании компоненты bootstrap4
- Socket.io: Socket.io - это библиотека, которая обеспечивает двустороннюю связь между браузером и сервером в реальном времени и на основе событий.
- Response-html-table-to-excel: Обеспечивает создание на стороне клиента файла Excel (.xls) из элемента таблицы HTML.
- React-router-dom: привязки DOM для реагирующего маршрутизатора. Он состоит из многих важных компонентов, таких как BrowserRouter, который используется, когда есть сервер для обработки динамических запросов, Switch, Route и т. Д.
Компонент приложения
Путь: src / App.js
Этот компонент содержит основную логику маршрутизации Frontend. Этот файл используется в src / index.js внутри модуля маршрутизатора браузера. Приведенный ниже код демонстрирует один из подходов к сохранению модульности вашего приложения.
import React, { Component } from "react";
import "./App.css";
import { Header } from "./global/header";
import { Switch, Route } from "react-router-dom";
import PlaceOrder from "./main/PlaceOrder";
import UpdatePredicted from "./main/UpdatePredicted";
import Kitchen from "./main/Kitchen";
/*The <Route> component is the main part of React Router. Anywhere that you want to only render content based on the location’s pathname, you should use a <Route> element. */
/* The Route component expects a path prop, which is a string that describes the pathname that the route matches */
/* The <Switch>
will iterate over routes and only render the first one that matches the current pathname */
class App extends Component {
render() {
return (
<div className="App">
<Header />
<Switch>
<Route exact path="/" component={PlaceOrder} />
<Route path="/updatepredicted" component={UpdatePredicted} />
<Route path="/kitchen" component={Kitchen} />
</Switch>
</div>
);
}
}
export default App;
Компонент заголовка
Путь: src / global / header.js
Этот компонент будет общим и использоваться в таких разделах, как «Заказ», «Прогноз изменений», «Кухня». Такой подход помогает избежать дублирования кода и сохраняет модульность приложения.
import React, { Component } from "react"; import { NavLink } from "react-router-dom"; import socketIOClient from "socket.io-client"; import "./header.css"; // The Header creates links that can be used to navigate // between routes. var socket; class Header extends Component { /* Creating a Socket client and exporting it at the end to be used across the Place Order, Kitchen, etc components*/ constructor() { super(); this.state = { endpoint: 'http://localhost:3001/' }; socket = socketIOClient(this.state.endpoint); } render() { return ( <header> <nav> <ul className="NavClass"> <li> <NavLink exact to="/"> Place Order </NavLink> </li> <li> <NavLink to="/updatepredicted">Change Predicted </NavLink> </li> <li> <NavLink to="/kitchen"> Kitchen </NavLink> </li > </ul> </nav> </header> ); } } export { Header, socket };
Компонент для кухни
Путь: src / main / Kitchen.js
Логика пользовательского интерфейса Kitchen Screen и HTML-код находятся в этом компоненте.
import React, { Component } from "react"; import { Button, Table, Container } from "reactstrap"; import { socket } from "../global/header"; import ReactHTMLTableToExcel from "react-html-table-to-excel"; class Kitchen extends Component { constructor() { super(); this.state = { food_data: [] // this is where we are connecting to with sockets, }; } getData = foodItems => { console.log(foodItems); this.setState({ food_data: foodItems }); }; changeData = () => socket.emit("initial_data"); /*As soon as the component gets mounted ie in componentDidMount method, firing the initial_data event to get the data to initialize the Kitchen Dashboard */ /* Adding change_data listener for listening to any changes made by Place Order and Predicted Order components*/ componentDidMount() { var state_current = this; socket.emit("initial_data"); socket.on("get_data", this.getData); socket.on("change_data", this.changeData); } /* Removing the listener before unmounting the component in order to avoid addition of multiple listener at the time revisit*/ componentWillUnmount() { socket.off("get_data"); socket.off("change_data"); } /* When Done gets clicked, this function is called and mark_done event gets emitted which gets listened on the backend explained later on*/ markDone = id => { // console.log(predicted_details); socket.emit("mark_done", id); }; getFoodData() { return this.state.food_data.map(food => { return ( <tr key={food._id}> <td> {food.name} </td> <td> {food.ordQty} </td> <td> {food.prodQty} </td> <td> {food.predQty} </td> <td> <button onClick={() => this.markDone(food._id)}>Done</button> </td> </tr> ); }); } render() { return ( <Container> <h2 className="h2Class">Kitchen Area</h2> <ReactHTMLTableToExcel id="test-table-xls-button" className="download-table-xls-button" table="table-to-xls" filename="tablexls" sheet="tablexls" buttonText="Download as XLS" /> <Table striped id="table-to-xls"> <thead> <tr> <th>Name</th> <th>Quantity</th> <th>Created Till Now</th> <th>Predicted</th> <th>Status</th> </tr> </thead> <tbody>{this.getFoodData()}</tbody> </Table> </Container> ); } } export default Kitchen;
Компонент размещения заказа
Путь: src / main / PlaceOrder.js
import React, { Component } from "react"; import { Button, Table, Container } from "reactstrap"; import { socket } from "../global/header"; class PlaceOrder extends Component { constructor() { super(); this.state = { food_data: [] // this is where we are connecting to with sockets, }; } getData = foodItems => { console.log(foodItems); foodItems = foodItems.map(food => { food.order = 0; return food; }); this.setState({ food_data: foodItems }); }; componentDidMount() { socket.emit("initial_data"); var state_current = this; socket.on("get_data", state_current.getData); } componentWillUnmount() { socket.off("get_data", this.getData); } //Function to place the order. sendOrder = id => { var order_details; this.state.food_data.map(food => { if (food._id == id) { order_details = food; } return food; }); console.log(order_details); socket.emit("putOrder", order_details); var new_array = this.state.food_data.map(food => { food.order = 0; return food; }); this.setState({ food_data: new_array }); }; // Changing the quantity in the state which is emitted to the backend at the time of placing the order. changeQuantity = (event, foodid) => { if (parseInt(event.target.value) < 0) { event.target.value = 0; } var new_array = this.state.food_data.map(food => { if (food._id == foodid) { food.order = parseInt(event.target.value); } return food; }); this.setState({ food_data: new_array }); }; // To get the initial data getFoodData() { return this.state.food_data.map(food => { return ( <tr key={food._id}> <td> {food.name} </td> <td> <input onChange={e => this.changeQuantity(e, food._id)} value={food.order} type="number" placeholder="Quantity" /> </td> <td> <button onClick={() => this.sendOrder(food._id)}>Order</button> </td> </tr> ); }); } render() { return ( <Container> <h2 className="h2Class">Order Menu</h2> <Table striped> <thead> <tr> <th>Product</th> <th>Quantity</th> <th>Order</th> </tr> </thead> <tbody>{this.getFoodData()}</tbody> </Table> </Container> ); } } export default PlaceOrder;
Еще один раздел, называемый «Прогнозируемый путь обновления»: src / main / UpdatePredicted.js, аналогичный приведенному выше разделу, находится в репозитории кода.
Бэкэнд
Запуск Backend:
cd backend-my-app npm install node server.js
Используемые пакеты:
- Monk: крошечный слой, который обеспечивает простые, но существенные улучшения удобства использования MongoDB в Node.JS.
- Socket.io: Socket.io - это библиотека, которая обеспечивает двусторонний обмен данными между браузером и сервером в реальном времени и на основе событий.
3. Экспресс: быстрый минималистичный веб-фреймворк для узла.
Основной код
Путь: backend-my-app / server.js
const express = require("express"); const http = require("http"); const socketIO = require("socket.io"); // Connection string of MongoDb database hosted on Mlab or locally var connection_string = "**********"; // Collection name should be "FoodItems", only one collection as of now. // Document format should be as mentioned below, at least one such document: // { // "_id": { // "$oid": "5c0a1bdfe7179a6ca0844567" // }, // "name": "Veg Roll", // "predQty": 100, // "prodQty": 295, // "ordQty": 1 // } const db = require("monk")(connection_string); const collection_foodItems = db.get("FoodItems"); // our localhost port const port = process.env.PORT || 3000; const app = express(); // our server instance const server = http.createServer(app); // This creates our socket using the instance of the server const io = socketIO(server); io.on("connection", socket => { // console.log("New client connected" + socket.id); //console.log(socket); // Returning the initial data of food menu from FoodItems collection socket.on("initial_data", () => { collection_foodItems.find({}).then(docs => { io.sockets.emit("get_data", docs); }); }); // Placing the order, gets called from /src/main/PlaceOrder.js of Frontend socket.on("putOrder", order => { collection_foodItems .update({ _id: order._id }, { $inc: { ordQty: order.order } }) .then(updatedDoc => { // Emitting event to update the Kitchen opened across the devices with the realtime order values io.sockets.emit("change_data"); }); }); // Order completion, gets called from /src/main/Kitchen.js socket.on("mark_done", id => { collection_foodItems .update({ _id: id }, { $inc: { ordQty: -1, prodQty: 1 } }) .then(updatedDoc => { //Updating the different Kitchen area with the current Status. io.sockets.emit("change_data"); }); }); // Functionality to change the predicted quantity value, called from /src/main/UpdatePredicted.js socket.on("ChangePred", predicted_data => { collection_foodItems .update( { _id: predicted_data._id }, { $set: { predQty: predicted_data.predQty } } ) .then(updatedDoc => { // Socket event to update the Predicted quantity across the Kitchen io.sockets.emit("change_data"); }); }); // disconnect is fired when a client leaves the server socket.on("disconnect", () => { console.log("user disconnected"); }); }); /* Below mentioned steps are performed to return the Frontend build of create-react-app from build folder of backend Comment it out if running locally*/ app.use(express.static("build")); app.use("/kitchen", express.static("build")); app.use("/updatepredicted", express.static("build")); server.listen(port, () => console.log(`Listening on port ${port}`));
База данных: MongoDB
Mlab: База данных как сервис для MongoDB
Название коллекции: FoodItems
Формат документа: в коллекции FoodItems необходим хотя бы один документ с указанным ниже форматом.
{ "name": "Veg Roll", // Food Name "predQty": 100, // Predicted Quantity "prodQty": 295, // Produced Quantity "ordQty": 1 // Total Order Quantity }
Надеюсь, вы поняли, как создать модульное приложение в реальном времени с использованием трендового стека MERN. Если вы нашли это полезным хлопать ниже, поставьте звездочки проекту репо и поделитесь им с друзьями.