Чаты окружают нас повсюду, от Whatsapp до Facebook и Instagram, почти каждая платформа предлагает чат в той или иной вариации.

В современном цифровом мире мы все стали мобильными! Незадолго до этой статьи я написал другу сообщение в Whatsapp.

Чаты — это весело, вы отправляете сообщение человеку или группе, они видят сообщение и отвечают вам. Простой, но сложный.
Чтобы разработать приложение для чата, вам нужно узнавать о новых сообщениях сразу после их поступления.

В этой статье мы сделаем нечто, немного отличающееся от предыдущего руководства по React. Мы создадим экран входа, где вы сможете ввести свое имя, создать группы, к которым люди могут присоединиться, и показывать сообщения в реальном времени между людьми в группе.

Получение оперативной информации с сервера ℹ️

Есть два способа получить оперативную информацию с вашего сервера о новой ставке:

Используйте HTTP-запрос с длительным опросом, в основном HTTP-запрос каждые 5–10 секунд для получения информации о новой ставке.

Используйте открытый сокет (веб-сокеты), чтобы получать информацию непосредственно с сервера при поступлении новой заявки.

В этой статье я расскажу о веб-сокетах и, в частности, о библиотеке Node.js — Socket.io.

Socket.io — это популярная библиотека JavaScript, которая позволяет нам создавать двустороннюю связь в режиме реального времени между программными приложениями и сервером Node.js.

Novu — первая инфраструктура уведомлений с открытым исходным кодом

Просто краткая справка о нас. Novu — первая инфраструктура уведомлений с открытым исходным кодом. Мы в основном помогаем управлять всеми уведомлениями о продуктах. Это может быть In-App (значок колокольчика, как у вас в сообществе разработчиков — веб-сокеты), электронные письма, SMS и так далее.

Я был бы очень рад, если бы вы могли дать нам звезду! Это поможет мне делать больше статей каждую неделю 🚀
https://github.com/novuhq/novu

Мы отправляем классные подарки во время Хактоберфеста 😇

Как создать соединение в реальном времени между React Native и Socket.io

В этом руководстве мы создадим приложение для чата с помощью Expo — фреймворка с открытым исходным кодом, который позволяет нам создавать нативные приложения для IOS и Android путем написания кода React и JavaScript.

Установка Экспо

Expo избавляет нас от сложных конфигураций, необходимых для создания собственного приложения с помощью React Native CLI, что делает его самым простым и быстрым способом создания и публикации приложений React Native.

Убедитесь, что на вашем компьютере установлены Expo CLI, Node.js и Git. Затем создайте папку проекта и приложение Expo React Native, запустив приведенный ниже код.

mkdir chat-app
cd chat-app
expo init app

Expo позволяет нам создавать нативные приложения, используя управляемый или голый рабочий процесс. В этом руководстве мы будем использовать пустой управляемый рабочий процесс, поскольку все необходимые настройки уже выполнены.

? Choose a template: › - Use arrow-keys. Return to submit.
    ----- Managed workflow -----
❯   blank               a minimal app as clean as an empty canvas
    blank (TypeScript)  same as blank but with TypeScript configuration
    tabs (TypeScript)   several example screens and tabs using react-navigation and TypeScript
    ----- Bare workflow -----
    minimal             bare and minimal, just the essentials to get you started

Установите клиентский API Socket.io в приложение React Native.

cd app
expo install socket.io-client

Создайте socket.js в папке utils и скопируйте приведенный ниже код в файл.

mkdir utils
touch socket.js
//👇🏻 Paste within socket.js file

И добавить

import { io } from "socket.io-client";
const socket = io.connect("http://localhost:4000");
export default socket;

Приведенный выше фрагмент кода создает подключение в реальном времени к серверу, размещенному по этому URL-адресу. (Мы создадим сервер в следующем разделе).

Создайте файл styles.js в папке utils и скопируйте приведенный ниже код в файл. Он содержит все стили для приложения чата.

import { StyleSheet } from "react-native";
export const styles = StyleSheet.create({
    loginscreen: {
        flex: 1,
        backgroundColor: "#EEF1FF",
        alignItems: "center",
        justifyContent: "center",
        padding: 12,
        width: "100%",
    },
    loginheading: {
        fontSize: 26,
        marginBottom: 10,
    },
    logininputContainer: {
        width: "100%",
        alignItems: "center",
        justifyContent: "center",
    },
    logininput: {
        borderWidth: 1,
        width: "90%",
        padding: 8,
        borderRadius: 2,
    },
    loginbutton: {
        backgroundColor: "green",
        padding: 12,
        marginVertical: 10,
        width: "60%",
        borderRadius: "50%",
        elevation: 1,
    },
    loginbuttonText: {
        textAlign: "center",
        color: "#fff",
        fontWeight: "600",
    },
    chatscreen: {
        backgroundColor: "#F7F7F7",
        flex: 1,
        padding: 10,
        position: "relative",
    },
    chatheading: {
        fontSize: 24,
        fontWeight: "bold",
        color: "green",
    },
    chattopContainer: {
        backgroundColor: "#F7F7F7",
        height: 70,
        width: "100%",
        padding: 20,
        justifyContent: "center",
        marginBottom: 15,
        elevation: 2,
    },
    chatheader: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "space-between",
    },
    chatlistContainer: {
        paddingHorizontal: 10,
    },
    chatemptyContainer: {
        width: "100%",
        height: "80%",
        alignItems: "center",
        justifyContent: "center",
    },
    chatemptyText: { fontWeight: "bold", fontSize: 24, paddingBottom: 30 },
    messagingscreen: {
        flex: 1,
    },
    messaginginputContainer: {
        width: "100%",
        minHeight: 100,
        backgroundColor: "white",
        paddingVertical: 30,
        paddingHorizontal: 15,
        justifyContent: "center",
        flexDirection: "row",
    },
    messaginginput: {
        borderWidth: 1,
        padding: 15,
        flex: 1,
        marginRight: 10,
        borderRadius: 20,
    },
    messagingbuttonContainer: {
        width: "30%",
        backgroundColor: "green",
        borderRadius: 3,
        alignItems: "center",
        justifyContent: "center",
        borderRadius: 50,
    },
    modalbutton: {
        width: "40%",
        height: 45,
        backgroundColor: "green",
        borderRadius: 5,
        alignItems: "center",
        justifyContent: "center",
        color: "#fff",
    },
    modalbuttonContainer: {
        flexDirection: "row",
        justifyContent: "space-between",
        marginTop: 10,
    },
    modaltext: {
        color: "#fff",
    },
    modalContainer: {
        width: "100%",
        borderTopColor: "#ddd",
        borderTopWidth: 1,
        elevation: 1,
        height: 400,
        backgroundColor: "#fff",
        position: "absolute",
        bottom: 0,
        zIndex: 10,
        paddingVertical: 50,
        paddingHorizontal: 20,
    },
    modalinput: {
        borderWidth: 2,
        padding: 15,
    },
    modalsubheading: {
        fontSize: 20,
        fontWeight: "bold",
        marginBottom: 15,
        textAlign: "center",
    },
    mmessageWrapper: {
        width: "100%",
        alignItems: "flex-start",
        marginBottom: 15,
    },
    mmessage: {
        maxWidth: "50%",
        backgroundColor: "#f5ccc2",
        padding: 15,
        borderRadius: 10,
        marginBottom: 2,
    },
    mvatar: {
        marginRight: 5,
    },
    cchat: {
        width: "100%",
        flexDirection: "row",
        alignItems: "center",
        borderRadius: 5,
        paddingHorizontal: 15,
        backgroundColor: "#fff",
        height: 80,
        marginBottom: 10,
    },
    cavatar: {
        marginRight: 15,
    },
    cusername: {
        fontSize: 18,
        marginBottom: 5,
        fontWeight: "bold",
    },
    cmessage: {
        fontSize: 14,
        opacity: 0.7,
    },
    crightContainer: {
        flexDirection: "row",
        justifyContent: "space-between",
        flex: 1,
    },
    ctime: {
        opacity: 0.5,
    },
});

Установите React Navigation и его зависимости. React Navigation позволяет нам переходить с одного экрана на другой в приложении React Native.

npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context

Настройка сервера Socket.io Node.js

Здесь я проведу вас через создание сервера Socket.io Node.js для связи в реальном времени с приложением React Native.

Создайте папку server в папке проекта.

cd chat-app
mkdir server

Перейдите в папку сервера и создайте файл package.json.

cd server & npm init -y

Установите Express.js, CORS, Nodemon и Socket.io Server API.

npm install express cors nodemon socket.io

Express.js — это быстрый минималистичный фреймворк, предоставляющий несколько функций для создания веб-приложений на Node.js. CORS — это пакет Node.js, который обеспечивает связь между разными доменами.

Nodemon — это инструмент Node.js, который автоматически перезапускает сервер после обнаружения изменений в файле, а Socket.io позволяет нам настроить соединение в реальном времени на сервере.

Создайте файл index.js — точку входа на сервер Node.js.

touch index.js

Настройте простой сервер Node.js с помощью Express.js. Приведенный ниже фрагмент кода возвращает объект JSON при посещении http://localhost:4000/api в браузере.

//👇🏻 index.js
const express = require("express");
const app = express();
const PORT = 4000;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});
app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});

Импортируйте библиотеки HTTP и CORS, чтобы разрешить передачу данных между клиентским и серверным доменами.

const express = require("express");
const app = express();
const PORT = 4000;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
//👇🏻 New imports
const http = require("http").Server(app);
const cors = require("cors");
app.use(cors());
app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});
http.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});

Затем добавьте Socket.io в проект, чтобы создать соединение в реальном времени. Перед блоком app.get() скопируйте приведенный ниже код:

//👇🏻 New imports
.....
const socketIO = require('socket.io')(http, {
    cors: {
        origin: "<http://localhost:3000>"
    }
});
//👇🏻 Add this before the app.get() block
socketIO.on('connection', (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);
    socket.on('disconnect', () => {
      socket.disconnect()
      console.log('🔥: A user disconnected');
    });
});

Из приведенного выше фрагмента кода функция socket.io("connection") устанавливает соединение с приложением React, затем создает уникальный идентификатор для каждого сокета и записывает идентификатор в консоль всякий раз, когда вы обновляете приложение.

Когда вы обновляете или закрываете приложение, сокет запускает событие разъединения, показывающее, что пользователь отключился от сокета.

Настройте Nodemon, добавив команду запуска в список скриптов в файле package.json. Фрагмент кода ниже запускает сервер с помощью Nodemon.

//👇🏻 In server/package.json
"scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
    "start": "nodemon index.js"
  },

Теперь вы можете запустить сервер с помощью Nodemon, используя приведенную ниже команду.

npm start

Создание пользовательского интерфейса

Здесь мы создадим пользовательский интерфейс для приложения чата, чтобы пользователи могли входить в систему, создавать комнаты чата и отправлять сообщения. Приложение разделено на три экрана — экран входа в систему, экран чата и экран обмена сообщениями.

Во-первых, давайте настроим React Navigation.

Создайте папку screens в папке приложения, добавьте компоненты входа, чата и обмена сообщениями и визуализируйте в них текст "Hello World".

mkdir screens
touch Login.js Chat.js Messaging.js

Скопируйте приведенный ниже код в файл App.js в папке приложения.

import React from "react";
//👇🏻 app screens
import Login from "./screens/Login";
import Messaging from "./screens/Messaging";
import Chat from "./screens/Chat";
//👇🏻 React Navigation configurations
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const Stack = createNativeStackNavigator();
export default function App() {
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen
                    name='Login'
                    component={Login}
                    options={{ headerShown: false }}
                />
                <Stack.Screen
                    name='Chat'
                    component={Chat}
                    options={{
                        title: "Chats",
                        headerShown: false,
                    }}
                />
                <Stack.Screen name='Messaging' component={Messaging} />
            </Stack.Navigator>
        </NavigationContainer>
    );
}

Экран входа

Скопируйте приведенный ниже код в файл Login.js.

import React, { useState } from "react";
import {
    Text,
    SafeAreaView,
    View,
    TextInput,
    Pressable,
    Alert,
} from "react-native";
//👇🏻 Import the app styles
import { styles } from "../utils/styles";
const Login = ({ navigation }) => {
    const [username, setUsername] = useState("");
    //👇🏻 checks if the input field is empty
    const handleSignIn = () => {
        if (username.trim()) {
            //👇🏻 Logs the username to the console
            console.log({ username });
        } else {
            Alert.alert("Username is required.");
        }
    };
    return (
        <SafeAreaView style={styles.loginscreen}>
            <View style={styles.loginscreen}>
                <Text style={styles.loginheading}>Sign in</Text>
                <View style={styles.logininputContainer}>
                    <TextInput
                        autoCorrect={false}
                        placeholder='Enter your username'
                        style={styles.logininput}
                        onChangeText={(value) => setUsername(value)}
                    />
                </View>
                <Pressable onPress={handleSignIn} style={styles.loginbutton}>
                    <View>
                        <Text style={styles.loginbuttonText}>Get Started</Text>
                    </View>
                </Pressable>
            </View>
        </SafeAreaView>
    );
};
export default Login;

Фрагмент кода принимает имя пользователя от пользователя и регистрирует его в консоли.

Давайте обновим код и сохраним имя пользователя с помощью Async Storage, чтобы пользователям не нужно было входить в приложение каждый раз, когда они запускают приложение.

💡 *Async Storage — это пакет React Native, используемый для хранения строковых данных в нативных приложениях. Оно похоже на локальное хранилище в Интернете и может использоваться для хранения токенов и различных данных в строковом формате.*

Запустите приведенный ниже код, чтобы установить Async Storage.

expo install @react-native-async-storage/async-storage

Обновите функцию handleSignIn, чтобы сохранить имя пользователя через AsyncStorage.

import AsyncStorage from "@react-native-async-storage/async-storage";
const storeUsername = async () => {
        try {
            //👇🏻 async function - saves the username to AsyncStorage
            //   redirecting to the Chat page
            await AsyncStorage.setItem("username", username);
            navigation.navigate("Chat");
        } catch (e) {
            Alert.alert("Error! While saving username");
        }
    };
    const handleSignIn = () => {
        if (username.trim()) {
            //👇🏻 calls AsyncStorage function
            storeUsername();
        } else {
            Alert.alert("Username is required.");
        }
    };

Чат

Здесь мы обновим пользовательский интерфейс для экрана Chat, чтобы отображать доступные комнаты чата, позволять пользователям создавать их и переходить к экрану Messaging при выборе каждой комнаты.

Скопируйте приведенный ниже код в файл Chat.js.

import React from "react";
import { View, Text, Pressable, SafeAreaView, FlatList } from "react-native";
import { Feather } from "@expo/vector-icons";
import ChatComponent from "../component/ChatComponent";
import { styles } from "../utils/styles";
const Chat = () => {
    //👇🏻 Dummy list of rooms
    const rooms = [
        {
            id: "1",
            name: "Novu Hangouts",
            messages: [
                {
                    id: "1a",
                    text: "Hello guys, welcome!",
                    time: "07:50",
                    user: "Tomer",
                },
                {
                    id: "1b",
                    text: "Hi Tomer, thank you! 😇",
                    time: "08:50",
                    user: "David",
                },
            ],
        },
        {
            id: "2",
            name: "Hacksquad Team 1",
            messages: [
                {
                    id: "2a",
                    text: "Guys, who's awake? 🙏🏽",
                    time: "12:50",
                    user: "Team Leader",
                },
                {
                    id: "2b",
                    text: "What's up? 🧑🏻‍💻",
                    time: "03:50",
                    user: "Victoria",
                },
            ],
        },
    ];
    return (
        <SafeAreaView style={styles.chatscreen}>
            <View style={styles.chattopContainer}>
                <View style={styles.chatheader}>
                    <Text style={styles.chatheading}>Chats</Text>
            {/* 👇🏻 Logs "ButtonPressed" to the console when the icon is clicked */}
                    <Pressable onPress={() => console.log("Button Pressed!")}>
                        <Feather name='edit' size={24} color='green' />
                    </Pressable>
                </View>
            </View>
            <View style={styles.chatlistContainer}>
                {rooms.length > 0 ? (
                    <FlatList
                        data={rooms}
                        renderItem={({ item }) => <ChatComponent item={item} />}
                        keyExtractor={(item) => item.id}
                    />
                ) : (
                    <View style={styles.chatemptyContainer}>
                        <Text style={styles.chatemptyText}>No rooms created!</Text>
                        <Text>Click the icon above to create a Chat room</Text>
                    </View>
                )}
            </View>
        </SafeAreaView>
    );
};
export default Chat;
  • Из фрагмента кода выше:
  • Я создал фиктивный список комнат, затем отрендерил их через FlatList в ChatComponent. (еще не создано)
  • Поскольку комнаты могут быть либо пустыми, либо заполненными, условный оператор определяет отображаемый компонент.

Затем создайте ChatComponent в папке компонента. Он представляет собой предварительный просмотр имени каждого чата, времени, последнего отправленного сообщения и перенаправляет пользователей на компонент Messaging при нажатии.

Скопируйте приведенный ниже код в файл components/ChatComponent.js.

import { View, Text, Pressable } from "react-native";
import React, { useLayoutEffect, useState } from "react";
import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
import { styles } from "../utils/styles";
const ChatComponent = ({ item }) => {
    const navigation = useNavigation();
    const [messages, setMessages] = useState({});
    //👇🏻 Retrieves the last message in the array from the item prop
    useLayoutEffect(() => {
        setMessages(item.messages[item.messages.length - 1]);
    }, []);
    ///👇🏻 Navigates to the Messaging screen
    const handleNavigation = () => {
        navigation.navigate("Messaging", {
            id: item.id,
            name: item.name,
        });
    };
    return (
        <Pressable style={styles.cchat} onPress={handleNavigation}>
            <Ionicons
                name='person-circle-outline'
                size={45}
                color='black'
                style={styles.cavatar}
            />
            <View style={styles.crightContainer}>
                <View>
                    <Text style={styles.cusername}>{item.name}</Text>
                    <Text style={styles.cmessage}>
                        {messages?.text ? messages.text : "Tap to start chatting"}
                    </Text>
                </View>
                <View>
                    <Text style={styles.ctime}>
                        {messages?.time ? messages.time : "now"}
                    </Text>
                </View>
            </View>
        </Pressable>
    );
};
export default ChatComponent;

Поздравляю💃🏻! Теперь мы можем отобразить список комнат и перенаправить пользователя на экран Messaging.

Прежде чем мы продолжим, давайте создадим пользовательский модальный компонент, который позволяет пользователям создавать новую группу (комнату), когда мы нажимаем значок заголовка.

Создайте файл Modal.js в папке компонентов, импортируйте его на экран чата и переключайте его всякий раз, когда мы нажимаем значок заголовка.

import React from "react";
import { View, Text, Pressable, SafeAreaView, FlatList } from "react-native";
import { Feather } from "@expo/vector-icons";
import ChatComponent from "../component/ChatComponent";
import { styles } from "../utils/styles";
//👇🏻 The Modal component
import Modal from "../component/Modal";
const Chat = () => {
    const [visible, setVisible] = useState(false);
    //...other variables
    return (
        <SafeAreaView style={styles.chatscreen}>
            <View style={styles.chattopContainer}>
                <View style={styles.chatheader}>
                    <Text style={styles.chatheading}>Chats</Text>
                    {/* Displays the Modal component when clicked */}
                    <Pressable onPress={() => setVisible(true)}>
                        <Feather name='edit' size={24} color='green' />
                    </Pressable>
                </View>
            </View>
            <View style={styles.chatlistContainer}>...</View>
            {/*
                Pass setVisible as prop in order to toggle 
                the display within the Modal component.
            */}
            {visible ? <Modal setVisible={setVisible} /> : ""}
        </SafeAreaView>
    );
};
export default Chat;

Скопируйте приведенный ниже код в файл Modal.js.

import { View, Text, TextInput, Pressable } from "react-native";
import React, { useState } from "react";
import { styles } from "../utils/styles";
const Modal = ({ setVisible }) => {
    const [groupName, setGroupName] = useState("");
    //👇🏻 Function that closes the Modal component
    const closeModal = () => setVisible(false);
    //👇🏻 Logs the group name to the console
    const handleCreateRoom = () => {
        console.log({ groupName });
        closeModal();
    };
    return (
        <View style={styles.modalContainer}>
            <Text style={styles.modalsubheading}>Enter your Group name</Text>
            <TextInput
                style={styles.modalinput}
                placeholder='Group name'
                onChangeText={(value) => setGroupName(value)}
            />
            <View style={styles.modalbuttonContainer}>
                <Pressable style={styles.modalbutton} onPress={handleCreateRoom}>
                    <Text style={styles.modaltext}>CREATE</Text>
                </Pressable>
                <Pressable
                    style={[styles.modalbutton, { backgroundColor: "#E14D2A" }]}
                    onPress={closeModal}
                >
                    <Text style={styles.modaltext}>CANCEL</Text>
                </Pressable>
            </View>
        </View>
    );
};
export default Modal;

Экран сообщений

Скопируйте приведенный ниже код в файл Messaging.js.

import React, { useLayoutEffect, useState } from "react";
import { View, TextInput, Text, FlatList, Pressable } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import MessageComponent from "../component/MessageComponent";
import { styles } from "../utils/styles";
const Messaging = ({ route, navigation }) => {
    const [chatMessages, setChatMessages] = useState([
        {
            id: "1",
            text: "Hello guys, welcome!",
            time: "07:50",
            user: "Tomer",
        },
        {
            id: "2",
            text: "Hi Tomer, thank you! 😇",
            time: "08:50",
            user: "David",
        },
    ]);
    const [message, setMessage] = useState("");
    const [user, setUser] = useState("");
    //👇🏻 Access the chatroom's name and id
    const { name, id } = route.params;
//👇🏻 This function gets the username saved on AsyncStorage
    const getUsername = async () => {
        try {
            const value = await AsyncStorage.getItem("username");
            if (value !== null) {
                setUser(value);
            }
        } catch (e) {
            console.error("Error while loading username!");
        }
    };
    //👇🏻 Sets the header title to the name chatroom's name
    useLayoutEffect(() => {
        navigation.setOptions({ title: name });
        getUsername()
    }, []);
    /*👇🏻 
        This function gets the time the user sends a message, then 
        logs the username, message, and the timestamp to the console.
     */
    const handleNewMessage = () => {
        const hour =
            new Date().getHours() < 10
                ? `0${new Date().getHours()}`
                : `${new Date().getHours()}`;
        const mins =
            new Date().getMinutes() < 10
                ? `0${new Date().getMinutes()}`
                : `${new Date().getMinutes()}`;
        console.log({
            message,
            user,
            timestamp: { hour, mins },
        });
    };
    return (
        <View style={styles.messagingscreen}>
            <View
                style={[
                    styles.messagingscreen,
                    { paddingVertical: 15, paddingHorizontal: 10 },
                ]}
            >
                {chatMessages[0] ? (
                    <FlatList
                        data={chatMessages}
                        renderItem={({ item }) => (
                            <MessageComponent item={item} user={user} />
                        )}
                        keyExtractor={(item) => item.id}
                    />
                ) : (
                    ""
                )}
            </View>
            <View style={styles.messaginginputContainer}>
                <TextInput
                    style={styles.messaginginput}
                    onChangeText={(value) => setMessage(value)}
                />
                <Pressable
                    style={styles.messagingbuttonContainer}
                    onPress={handleNewMessage}
                >
                    <View>
                        <Text style={{ color: "#f2f0f1", fontSize: 20 }}>SEND</Text>
                    </View>
                </Pressable>
            </View>
        </View>
    );
};
export default Messaging;

Приведенный выше фрагмент кода отображает сообщения в каждом чате с помощью компонента MessageComponent.

Создайте файл MessageComponent и скопируйте в него приведенный ниже код:

import { View, Text } from "react-native";
import React from "react";
import { Ionicons } from "@expo/vector-icons";
import { styles } from "../utils/styles";
export default function MessageComponent({ item, user }) {
    const status = item.user !== user;
    return (
        <View>
            <View
                style={
                    status
                        ? styles.mmessageWrapper
                        : [styles.mmessageWrapper, { alignItems: "flex-end" }]
                }
            >
                <View style={{ flexDirection: "row", alignItems: "center" }}>
                    <Ionicons
                        name='person-circle-outline'
                        size={30}
                        color='black'
                        style={styles.mavatar}
                    />
                    <View
                        style={
                            status
                                ? styles.mmessage
                                : [styles.mmessage, { backgroundColor: "rgb(194, 243, 194)" }]
                        }
                    >
                        <Text>{item.text}</Text>
                    </View>
                </View>
                <Text style={{ marginLeft: 40 }}>{item.time}</Text>
            </View>
        </View>
    );
}

Из приведенного выше фрагмента кода переменная status проверяет, совпадает ли пользовательский ключ в сообщении с текущим пользователем, чтобы определить, как выравнивать сообщения.

Мы завершили пользовательский интерфейс для приложения!🎊 Теперь давайте узнаем, как общаться с сервером Socket.io, создавать чаты и отправлять сообщения в режиме реального времени через Socket.io.

Создание чатов с помощью Socket.io в React Native

В этом разделе я проведу вас через создание чатов на сервере Socket.io и их отображение в приложении.

Обновите файл Modal.js, чтобы отправить сообщение на сервер при создании нового чата.

import { View, Text, TextInput, Pressable } from "react-native";
import React, { useState } from "react";
import { styles } from "../utils/styles";
//👇🏻 Import socket from the socket.js file in utils folder
import socket from "../utils/socket";
const Modal = ({ setVisible }) => {
    const closeModal = () => setVisible(false);
    const [groupName, setGroupName] = useState("");
    const handleCreateRoom = () => {
        //👇🏻 sends a message containing the group name to the server
        socket.emit("createRoom", groupName);
        closeModal();
    };
    return (
        <View style={styles.modalContainer}>
            <Text style={styles.modalsubheading}>Enter your Group name</Text>
            <TextInput
                style={styles.modalinput}
                placeholder='Group name'
                onChangeText={(value) => setGroupName(value)}
            />
            <View style={styles.modalbuttonContainer}>
                {/* 👇🏻 The create button triggers the function*/}
                <Pressable style={styles.modalbutton} onPress={handleCreateRoom}>
                    <Text style={styles.modaltext}>CREATE</Text>
                </Pressable>
                <Pressable
                    style={[styles.modalbutton, { backgroundColor: "#E14D2A" }]}
                    onPress={closeModal}
                >
                    <Text style={styles.modaltext}>CANCEL</Text>
                </Pressable>
            </View>
        </View>
    );
};
export default Modal;

Создайте прослушиватель на внутреннем сервере, который сохраняет имя группы в массив и возвращает весь список.

//👇🏻 Generates random string as the ID
const generateID = () => Math.random().toString(36).substring(2, 10);
let chatRooms = [
    //👇🏻 Here is the data structure of each chatroom
    // {
    //  id: generateID(),
    //  name: "Novu Hangouts",
    //  messages: [
    //      {
    //          id: generateID(),
    //          text: "Hello guys, welcome!",
    //          time: "07:50",
    //          user: "Tomer",
    //      },
    //      {
    //          id: generateID(),
    //          text: "Hi Tomer, thank you! 😇",
    //          time: "08:50",
    //          user: "David",
    //      },
    //  ],
    // },
];
socketIO.on("connection", (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);
    socket.on("createRoom", (roomName) => {
        socket.join(roomName);
        //👇🏻 Adds the new group name to the chat rooms array
        chatRooms.unshift({ id: generateID(), roomName, messages: [] });
        //👇🏻 Returns the updated chat rooms via another event
        socket.emit("roomsList", chatRooms);
    });
    socket.on("disconnect", () => {
        socket.disconnect();
        console.log("🔥: A user disconnected");
    });
});

Кроме того, верните список комнат чата через маршрут API, как показано ниже:

app.get("/api", (req, res) => {
    res.json(chatRooms);
});

Обновите файл Chat.js, чтобы получить и прослушать roomsList с сервера и отобразить чаты.

const [rooms, setRooms] = useState([]);
//👇🏻 Runs when the component mounts
useLayoutEffect(() => {
    function fetchGroups() {
        fetch("http://localhost:4000/api")
            .then((res) => res.json())
            .then((data) => setRooms(data))
            .catch((err) => console.error(err));
    }
    fetchGroups();
}, []);
//👇🏻 Runs whenever there is new trigger from the backend
useEffect(() => {
    socket.on("roomsList", (rooms) => {
        setRooms(rooms);
    });
}, [socket]);

Отправка сообщений через Socket.io в React Native

В предыдущем разделе мы могли создавать новые чаты, сохранять их в виде массива на сервере и отображать в приложении. Здесь мы будем обновлять сообщения чата, добавляя новые сообщения в подмассив.

Как отображать сообщения чата

Напомним, что идентификатор каждой комнаты чата передается в компонент обмена сообщениями. Теперь давайте отправим идентификатор на сервер через Socket.io при загрузке экрана.

import React, { useEffect, useLayoutEffect, useState } from "react";
import { View, TextInput, Text, FlatList, Pressable } from "react-native";
import socket from "../utils/socket";
import MessageComponent from "../component/MessageComponent";
import { styles } from "../utils/styles";
const Messaging = ({ route, navigation }) => {
    //👇🏻 The id passed
    const { name, id } = route.params;
//...other functions
    useLayoutEffect(() => {
        navigation.setOptions({ title: name });
        //👇🏻 Sends the id to the server to fetch all its messages
        socket.emit("findRoom", id);
    }, []);
    return <View style={styles.messagingscreen}>...</View>;
};
export default Messaging;

Создайте прослушиватель событий на сервере.

socket.on("findRoom", (id) => {
    //👇🏻 Filters the array by the ID
    let result = chatRooms.filter((room) => room.id == id);
    //👇🏻 Sends the messages to the app
    socket.emit("foundRoom", result[0].messages);
});

Затем прослушайте событие foundRoom и отобразите сообщения пользователю.

//👇🏻 This runs only initial mount
useLayoutEffect(() => {
    navigation.setOptions({ title: name });
    socket.emit("findRoom", id);
    socket.on("foundRoom", (roomChats) => setChatMessages(roomChats));
}, []);
//👇🏻 This runs when the messages are updated.
useEffect(() => {
    socket.on("foundRoom", (roomChats) => setChatMessages(roomChats));
}, [socket])

Как создавать новые сообщения

Чтобы создавать новые сообщения, нам нужно обновить функцию handleNewMessage, чтобы отправить свойство сообщения на сервер и добавить его в массив messages.

const handleNewMessage = () => {
    const hour =
        new Date().getHours() < 10
            ? `0${new Date().getHours()}`
            : `${new Date().getHours()}`;
    const mins =
        new Date().getMinutes() < 10
            ? `0${new Date().getMinutes()}`
            : `${new Date().getMinutes()}`;
    socket.emit("newMessage", {
        message,
        room_id: id,
        user,
        timestamp: { hour, mins },
    });
};

Прослушайте событие на сервере и обновите массив chatRoom.

socket.on("newMessage", (data) => {
    //👇🏻 Destructures the property from the object
    const { room_id, message, user, timestamp } = data;
    //👇🏻 Finds the room where the message was sent
    let result = chatRooms.filter((room) => room.id == room_id);
    //👇🏻 Create the data structure for the message
    const newMessage = {
        id: generateID(),
        text: message,
        user,
        time: `${timestamp.hour}:${timestamp.mins}`,
    };
    //👇🏻 Updates the chatroom messages
    socket.to(result[0].name).emit("roomMessage", newMessage);
    result[0].messages.push(newMessage);
    //👇🏻 Trigger the events to reflect the new changes
    socket.emit("roomsList", chatRooms);
    socket.emit("foundRoom", result[0].messages);
});

Заключение

Итак, вы узнали, как настроить Socket.io в приложении React Native и Node.js, сохранить данные с помощью Async Storage и обмениваться данными между сервером и приложением Expo через Socket.io.

Socket.io — отличный инструмент с отличными функциями, который позволяет нам создавать эффективные приложения в режиме реального времени, такие как веб-сайты для ставок на спорт, приложения для аукционов и торговли на рынке Форекс и, конечно же, приложения для чата, создавая прочные соединения с Node.js. сервер.

Не стесняйтесь улучшать приложение:

  • добавление аутентификации
  • сохранение токена с помощью Async Storage
  • добавление базы данных в реальном времени для сообщений, и
  • добавление push-уведомлений с пакетом выставочное уведомление.

Исходный код этого руководства доступен здесь:

https://github.com/novuhq/blog/tree/main/chat-app-reactnative-socketIO

Спасибо за прочтение!💃🏻

P.S. Novu присылает потрясающие подарки на Hacktoberfest! Приходите и участвуйте! Будем рады, если вы поддержите нас, поставив звезду! ⭐️
https://github.com/novuhq/novu

Создавайте приложения с повторно используемыми компонентами, такими как Lego.

Инструмент с открытым исходным кодом Bit помогает более чем 250 000 разработчиков создавать приложения с компонентами.

Превратите любой пользовательский интерфейс, функцию или страницу в компонент многократного использования — и поделитесь им со своими приложениями. Легче сотрудничать и строить быстрее.

Подробнее

Разделите приложения на компоненты, чтобы упростить разработку приложений, и наслаждайтесь наилучшими возможностями для рабочих процессов, которые вы хотите:

Микро-интерфейсы

Система дизайна

Совместное использование кода и повторное использование

Монорепо

Узнать больше