Въведение

Бих искал да ви запозная с нов подход към fullstack разработката, който може да революционизира начина, по който създавате уеб приложения. В тази статия ще демонстрираме React Server в действие чрез изграждане на приложение за чат в реално време. Чрез този практически пример ще придобиете по-задълбочено разбиране за предимствата и възможностите на React Server. Докато преминаваме през стъпка по стъпка процеса на създаване на приложение за чат, ще видите как React Server опростява разработката на бекенда и насърчава модулна структура, управлявана от компоненти, подобно на React във фронтенда. Тази практическа демонстрация ще ви даде увереността да започнете да използвате React Server във вашите собствени проекти.

Приготвяме се да започнем

Предпоставки

Преди да започнем, уверете се, че сте инсталирали следното на вашата локална машина:

  • Node.js (версия 16 или по-нова)
  • npm (версия 8 или по-нова)

В този урок ще настроим React Server, който да обработва нашата бекенд логика и ще изградим фронтенд, използвайки React, Material-UI, Apollo Client и React-Client библиотеката. React Server ще ни позволи да създадем модулна, управлявана от компоненти структура на бекенда, докато фронтендът ще използва силата на React и Material-UI за интерактивен и визуално привлекателен потребителски интерфейс. Като комбинираме тези технологии, ние ще създадем безпроблемно изживяване с пълен стек. Apollo Client ще обработва GraphQL заявки и мутации, позволявайки ефективна комуникация между фронтенда и бекенда, докато React-Client ще гарантира гладка интеграция на сървърните компоненти с приложението от страна на клиента.

Създаване на нов проект

Накарайте сървър да работи

За да настроите нов React Server, просто клонирайте хранилището „clean-starter“ от GitHub акаунта без състояние. Това репо осигурява солидна основа с необходимия бекенд код за стартиране на вашия React Server проект.

git clone https://github.com/state-less/clean-starter.git chat-app-backend
cd chat-app-backend
git remote remove origin
yarn install
yarn start

Това е всичко, вече имате работещ бекенд. Нека спрем сървъра и да преминем към интерфейса. Превключете към родителската директория и настройте клиента.

Стартирайте клиент

Създайте нов проект на Vite и изберете Reactкато рамка и TypeScriptкато вариант.

Vite е модерен инструмент за изграждане и сървър за разработка, който предлага по-бързо изживяване при разработка и подобрена производителност спрямо Create React App (CRA). Той се възползва от функции като естествени ES модули, което прави процеса на разработка по-ефективен с бързи актуализации по време на разработка и оптимизирани компилации за производство.

yarn create vite chat-app-frontend

Сега отидете в новосъздадената папка, инсталирайте зависимостите, добавете @apollo/client и @state-less/react-client към вашия проект и стартирайте сървъра.

cd chat-app-frontend
yarn
yarn add @mui/material @mui/icons-material
yarn add @emotion/react @emotion/styled
yarn add @apollo/client @state-less/react-client
yarn dev

Създайте екземпляр на GraphQl клиент

За да се свържем с нашия бекенд, трябва да създадем GraphQl клиент. Създайте нов файл под chat-app-frontend/src/lib/client.ts и поставете следното съдържание.

import { ApolloClient, InMemoryCache, split, HttpLink } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";// Create an HTTP link
const localHttp = new HttpLink({
  uri: "http://localhost:4000/graphql",
});
// Create a WebSocket link
const localWs = new WebSocketLink({
  uri: `ws://localhost:4000/graphql`,
  options: {
    reconnect: true,
  },
});
// Use the split function to direct traffic between the two links
const local = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  localWs,
  localHttp
);
// Create the Apollo Client instance
export const localClient = new ApolloClient({
  link: local,
  cache: new InMemoryCache(),
});

export default localClient;

Това настройва нов клиент на GraphQl с абонаменти, които ще се използват от клиента на React Server. Абонаментите са необходими, за да направите приложението си реактивно.

Изграждане на приложението

След като настроите примерния сървър и интерфейса, можем да започнем да изграждаме действителното приложение за чат.

Нека започнем с бекенда, тъй като е по-лесно да изградим предния интерфейс, след като имаме готови данни и взаимодействия.

Продължете и отворете файла под comment-app-backend/src/components/ChatRoom.tsx

Ще изградим само много основно приложение за чат, за да покажем основните характеристики на сървъра за реакция. В началото се нуждаем само от 3 функции. Стаи, потребителски списък и възможност за изпращане на съобщения до стая.

Хранилището clean-starter, което току-що клонирахте, включва всичко необходимо. Отворете файла chat-app-backend/src/components/ChatRoom.tsx

Нека да разгледаме компонента Стая.

export const Room = (
    props,
    { key, context, initiator, clientProps }: RenderOptions & { key: string }
) => {
    // We will store all the messages sent to the room in an array.
    const [messages, setMessages] = useState([], {
        key: `messages-${key}`,
        scope: Scopes.Global,
    });

    // We'll also keep a list of connected clients.
    const [clients, setClients] = useState([], {
        key: `clients-${key}`,
        scope: Scopes.Global,
    });

    /* The useClientEffect hook get's called when a component 
     * get's rendered or mounted on the client */
    useClientEffect(() => {
        const user = tryGetUser(context);
        const client = {
            user,
            id: (context as ClientContext).headers['x-unique-id'],
        };

        if (initiator !== Initiator.Mount)
            return () => 
                setClients(clients.filter((c) => c.id !== client.id));
            };

        if (!clients.find((c) => c.id === client.id)) {
            // Whenever a client opens the chat app on the frontend
            // we'll add it to the list.
            setImmediate(setClients, [...clients, client]);
        }

        // You can return a cleanup function that get's called on unmount.
        return () => {
            // When a client unmounts / leaves the chat
            // we remove it from the list of clients
            setClients(clients.filter((c) => c.id !== client.id));
        };
    });

    // Let's create a function that allows clients to send messages
    // We just need to modify the messages array using setMessages
    // and the updated list will be sent to any subscribed client
    const sendMessage = (message) => {
        if (typeof message !== 'string') {
            throw new Error('Invalid message');
        }
        if (message.length > 100) {
            throw new Error('Message too long');
        }
        const user = tryGetUser(context);
        const client = {
            user,
            id: (context as ClientContext).headers['x-unique-id'],
        };

        // Let's store some meta data about the message, such as the author
        // and a timestamp.
        const messageObj = {
            author: client,
            message,
            timestamp: +new Date(),
        };
        setMessages([...messages, messageObj]);
    };

    // We can simply pass these props to the client
    return (
        <ServerSideProps
            key={`${key}-props`}
            total={messages.length}
            messages={messages.slice((clientProps?.num || 30) * -1)}
            clients={clients}
            sendMessage={sendMessage}
        />
    );
};

Това е цялата функционалност, от която се нуждаете в бекенда, за да създадете основно приложение за чат. Най-важната част е функцията sendMessage, която ви позволява да изпратите съобщение до стаята.

UseClientEffect планира ефект, който да се изпълнява всеки път, когато клиент монтира компонента на интерфейса. Това ни позволява да управляваме състоянието на текущо свързани клиенти, като ги добавяме и премахваме от състоянието.

Файлът съдържа и компонент ChatApp, който ще използваме по-късно за управление на множество стаи.

Нека стартираме бекенд сървъра, тъй като ще ни трябва след минута.

cd chat-app-backend
yarn start

Ето връзка към репото на github за бекенда: https://github.com/C5H8NNaO4/chat-app-backend

Frontend

След като имаме работещ сървър, можем да изградим потребителски интерфейс за свързване с нашия бекенд.

Това е право напред. Нека продължим оттам, откъдето спряхме по-рано. Просто трябва да инсталираме зависимостите на материалния потребителски интерфейс, в противен случай ще получим подвеждащи съобщения за грешка.

Намерете папката, създадена от vite (frontend на приложението за чат), инсталирайте зависимостите и стартирайте проекта.

cd chat-app-frontend

yarn add @mui/material @mui/icons-material
yarn add @emotion/react @emotion/styled

yarn dev

Това трябва да стартира сървър за разработка и ако отворите URL адреса в браузъра си, трябва да видите следния екран.

Ако видите приложението vite, вие сте готови да се свържете с нашия бекенд.

Създайте нов файл chat-app-frontend/src/components/ChatApp.tsx

import {
    Card,
    CardContent,
    TextField,
    CardActions,
    Button,
    Grid,
    ListItem,
    ListItemText,
    Box,
    Avatar,
    Chip,
    Typography,
  } from '@mui/material';
  import { useComponent } from '@state-less/react-client';
  import { useEffect, useRef, useState } from 'react';

  export const ChatApp = () => {
    const [items, setItems] = useState(10);
    const [room, { loading }] = useComponent('room-global', {
      props: {
        num: items,
      },
    });
    const { messages = [], sendMessage } = room?.props || {};
    const [text, setText] = useState('');
    const ref = useRef<HTMLDivElement>(null);

    useEffect(() => {
      setTimeout(() => {
        ref.current?.scrollTo({
          top: ref.current?.scrollHeight,
        });
      }, 100);
    }, [loading]);

    return (
      <Card sx={{width: '100%'}}>
        <CardContent>
          <Grid container spacing={1}>
            <Grid item xs={9}>
              <Box sx={{ display: 'flex', width: '100%' }}>
                <Button
                  disabled={items >= room?.props?.total}
                  onClick={() => setItems(items + 10)}
                  sx={{ mx: 'auto' }}
                >
                  Load More {items}
                </Button>
              </Box>
  
              <Box
                sx={{
                  height: '500px',
                  overflowY: 'scroll',
                }}
                ref={ref}
              >
                <Box
                  sx={{
                    display: 'flex',
                    flexDirection: 'column',
                    gap: 1,
                  }}
                >
                  {messages.map((message) => {
                    return (
                      <Card>
                        <CardContent sx={{ py: '0px !important' }}>
                          <Typography>{message?.message}</Typography>
                        </CardContent>
                        <CardActions>
                          {new Date(message?.timestamp).toLocaleString()}
                          <Chip
                            sx={{ ml: 'auto' }}
                            avatar={
                              <Avatar
                                sx={{ width: 24, height: 24 }}
                                src={
                                  message?.author?.user?.strategies?.google
                                    ?.decoded?.picture
                                }
                              />
                            }
                            label={
                              message?.author?.user?.strategies?.google?.decoded
                                ?.name || message?.author?.id
                            }
                          ></Chip>
                        </CardActions>
                      </Card>
                    );
                  })}
                </Box>
              </Box>
  
              <TextField
                fullWidth
                rows={3}
                multiline
                value={text}
                onChange={(e) => setText(e.target.value)}
                sx={{ mt: 1 }}
              ></TextField>
            </Grid>
            <Grid item xs={3}>
              {room?.props?.clients?.map?.((client) => {
                return (
                  <ListItem>
                    <ListItemText
                      primary={
                        client?.user?.strategies?.google?.decoded?.name ||
                        client?.id
                      }
                    ></ListItemText>
                  </ListItem>
                );
              })}
            </Grid>
          </Grid>
        </CardContent>
        <CardActions>
          <Button
            disabled={text?.length < 3}
            onClick={() => {
              sendMessage(text);
              setText('');
            }}
          >
            Send
          </Button>
        </CardActions>
      </Card>
    );
  };

Това е дълъг блок код, но всъщност е доста прост, съдържа само контейнер за съобщения и потребителски списък. Кодът за свързване с бекенда е зад един ред

const [room, { loading }] = useComponent('room-global', {
  props: {
    num: items,
  },
});

Това ви предоставя всички свойства и функции, от които се нуждаете, за да четете и изпращате съобщения. Той е реактивен, така че ако някой друг изпрати съобщение до стаята, вашият клиент ще получи автоматично актуализираните съобщения.

Актуализирайте App.tsx

Сега трябва да посочим приложението за чат във вашето приложение. Заменете цялото съдържание на chat-app-frontend/src/App.tsxсъс следния код

import './App.css'
import localClient from './lib/client'
import { ChatApp } from './components/ChatApp'
import { ApolloProvider } from '@apollo/client'

function App() {
  return (
    <>
      <ApolloProvider client={localClient}>
        <ChatApp />
      </ApolloProvider>
    </>
  )
}

export default App

Изтриване на стилове

Изтрийте целия код в index.css и App.css ипрезаредете прозореца на браузъра.

Трябва да видите прозорец за чат с вашия уникален идентификатор, свързан.

Това е. Това е първата част от нашия урок. Можете да продължите да създавате свое собствено приложение за чат в реално време от тук.

Не забравяйте да прочетете документите на https://state-less.cloud и оставете коментар, ако успешно сте успели да създадете свое собствено приложение с React Server.

Честито кодиране!

Ето предния репо за този урок: https://github.com/C5H8NNaO4/chat-app-frontend

Повече съдържание в PlainEnglish.io.

Регистрирайте се за нашия безплатен седмичен бюлетин. Следвайте ни в Twitter, LinkedIn, YouTube и Discord .