out также передаёт аргумент по ссылке, но переменная может быть не инициализирована, так как метод обязан присвоить ей значение.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥2
Для реализации отношения "один ко многим" (one-to-many) в C# с использованием Entity Framework Core, нужно определить две модели, где одна модель будет содержать коллекцию объектов другой модели.
Предположим, у нас есть две модели:
Author и Book. Один автор может написать много книг, поэтому у нас будет отношение "один ко многим". public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
// Связь один ко многим
public ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; }
// Навигационное свойство
public Author Author { get; set; }
}
Создаем контекст данных, который наследуется от
DbContext. using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("YourConnectionStringHere");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Настройка связи один ко многим
modelBuilder.Entity<Author>()
.HasMany(a => a.Books)
.WithOne(b => b.Author)
.HasForeignKey(b => b.AuthorId);
}
}
Для создания и применения миграций выполните следующие команды в консоли диспетчера пакетов или CLI.
Использование Package Manager Console (PMC)
Add-Migration InitialCreate
Update-Database
Использование .NET CLI
dotnet ef migrations add InitialCreate
dotnet ef database update
Теперь можно добавить данные в базу данных и протестировать отношение "один ко многим".
public class Program
{
public static void Main()
{
using (var context = new AppDbContext())
{
var author = new Author
{
Name = "George Orwell",
Books = new List<Book>
{
new Book { Title = "1984" },
new Book { Title = "Animal Farm" }
}
};
context.Authors.Add(author);
context.SaveChanges();
}
}
}
Теперь можно извлечь данные и проверить отношение.
using (var context = new AppDbContext())
{
var authors = context.Authors.Include(a => a.Books).ToList();
foreach (var author in authors)
{
Console.WriteLine($"Author: {author.Name}");
foreach (var book in author.Books)
{
Console.WriteLine($" Book: {book.Title}");
}
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
readonly — это поле, значение которого можно установить только при инициализации или в конструкторе, но оно может быть вычислено во время выполнения.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥6
Для реализации связи "многие ко многим" (many-to-many) в реляционных базах данных используется промежуточная (связующая) таблица, которая содержит внешние ключи, ссылающиеся на обе связанные таблицы. Эта промежуточная таблица позволяет управлять отношениями между записями двух таблиц.
Предположим, у нас есть две таблицы:
Students (студенты) и Courses (курсы). Один студент может быть записан на несколько курсов, и один курс может включать нескольких студентов.Таблица
Students для хранения данных о студентах. Таблица Courses для хранения данных о курсах.Таблица
StudentCourses, которая содержит внешние ключи, ссылающиеся на Students и Courses.StudentsCREATE TABLE Students (
StudentId INT PRIMARY KEY,
StudentName VARCHAR(100)
);
CoursesCREATE TABLE Courses (
CourseId INT PRIMARY KEY,
CourseName VARCHAR(100)
);
StudentCoursesCREATE TABLE StudentCourses (
StudentId INT,
CourseId INT,
PRIMARY KEY (StudentId, CourseId),
FOREIGN KEY (StudentId) REFERENCES Students(StudentId),
FOREIGN KEY (CourseId) REFERENCES Courses(CourseId)
);
Вставка данных в таблицу
Students: INSERT INTO Students (StudentId, StudentName) VALUES (1, 'John Doe');
INSERT INTO Students (StudentId, StudentName) VALUES (2, 'Jane Smith');
Вставка данных в таблицу
CoursesINSERT INTO Courses (CourseId, CourseName) VALUES (1, 'Mathematics');
INSERT INTO Courses (CourseId, CourseName) VALUES (2, 'Science');
Вставка данных в таблицу
StudentCourses (связывающая таблица): INSERT INTO StudentCourses (StudentId, CourseId) VALUES (1, 1); -- John Doe записан на Mathematics
INSERT INTO StudentCourses (StudentId, CourseId) VALUES (1, 2); -- John Doe записан на Science
INSERT INTO StudentCourses (StudentId, CourseId) VALUES (2, 1); -- Jane Smith записана на Mathematics
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Связь "один к одному" (one-to-one) и связь "многие ко многим" (many-to-many) представляют разные типы логических отношений между записями в реляционных базах данных. Основные отличия между этими двумя типами связей заключаются в их природе, реализации и сценариях использования.
Каждая запись в одной таблице соответствует ровно одной записи в другой таблице. Используется для разделения информации на связанные, но отдельные части, например, для повышения безопасности или упрощения структуры данных. Можно использовать уникальный внешний ключ или один и тот же первичный ключ в обеих таблицах.
Пользователи и их профили.
Клиенты и их учетные данные.
CREATE TABLE Users (
UserId INT PRIMARY KEY,
UserName VARCHAR(100)
);
CREATE TABLE Profiles (
ProfileId INT PRIMARY KEY,
UserId INT UNIQUE,
ProfileData VARCHAR(255),
FOREIGN KEY (UserId) REFERENCES Users(UserId)
);
Каждая запись в одной таблице может соответствовать множеству записей в другой таблице, и наоборот. Используется для представления сложных взаимосвязей, таких как студенты и курсы, где каждый студент может записаться на множество курсов, и каждый курс может включать множество студентов. Требуется промежуточная (связующая) таблица, содержащая два внешних ключа, ссылающихся на связанные таблицы. Студенты и курсы. Авторы и книги. Таблицы
Students и Courses с промежуточной таблицей StudentCourses. CREATE TABLE Students (
StudentId INT PRIMARY KEY,
StudentName VARCHAR(100)
);
CREATE TABLE Courses (
CourseId INT PRIMARY KEY,
CourseName VARCHAR(100)
);
CREATE TABLE StudentCourses (
StudentId INT,
CourseId INT,
PRIMARY KEY (StudentId, CourseId),
FOREIGN KEY (StudentId) REFERENCES Students(StudentId),
FOREIGN KEY (CourseId) REFERENCES Courses(CourseId)
);
Каждая запись в одной таблице соответствует ровно одной записи в другой таблице.
Каждая запись в одной таблице может соответствовать множеству записей в другой таблице и наоборот.
Используется уникальный внешний ключ или одинаковый первичный ключ в обеих таблицах.
Требуется промежуточная таблица для хранения связей между записями.
Пользователь и его профиль, клиент и его учетные данные.
Студенты и курсы, авторы и книги.
Получение данных пользователя и его профиля
SELECT Users.UserName, Profiles.ProfileData
FROM Users
JOIN Profiles ON Users.UserId = Profiles.UserId;
Получение всех курсов, на которые записан студент
SELECT Students.StudentName, Courses.CourseName
FROM Students
JOIN StudentCourses ON Students.StudentId = StudentCourses.StudentId
JOIN Courses ON StudentCourses.CourseId = Courses.CourseId;
Получение всех студентов, записанных на определенный курс
SELECT Courses.CourseName, Students.StudentName
FROM Courses
JOIN StudentCourses ON Courses.CourseId = StudentCourses.CourseId
JOIN Students ON StudentCourses.StudentId = Students.StudentId;
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
1. Как работает: GC отслеживает объекты, на которые есть активные ссылки, и удаляет те, которые недоступны.
2. Цель: предотвращение утечек памяти и избавление разработчика от необходимости вручную освобождать память.
3. GC работает асинхронно и может вызывать паузы в работе приложения, известные как "stop-the-world".
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🔥2
Наследование в объектно-ориентированном программировании, является мощным инструментом для создания иерархий классов и повторного использования кода. Однако его использование должно быть осознанным и оправданным.
Наследование используется для создания обобщенных базовых классов и специализированных производных классов.
public class Animal
{
public void Eat() { /*...*/ }
}
public class Dog : Animal
{
public void Bark() { /*...*/ }
}
Наследование позволяет повторно использовать код базового класса в производных классах, избегая дублирования.
public class Vehicle
{
public int Speed { get; set; }
public void Move() { /*...*/ }
}
public class Car : Vehicle
{
public void Honk() { /*...*/ }
}
Наследование позволяет использовать объекты производных классов через ссылки на базовые классы, обеспечивая гибкость и расширяемость кода.
public class Shape
{
public virtual void Draw() { /*...*/ }
}
public class Circle : Shape
{
public override void Draw() { /*...*/ }
}
public void DrawShape(Shape shape)
{
shape.Draw();
}
Слишком глубокие иерархии усложняют понимание и поддержку кода. В таких случаях рассмотрите использование композиции.
Интерфейсы и абстрактные классы могут обеспечить большую гибкость и возможность для реализации различных вариантов поведения.
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly() { /*...*/ }
}
Композиция может быть предпочтительнее наследования, так как позволяет более гибко комбинировать функциональности.
public class Engine
{
public void Start() { /*...*/ }
}
public class Car
{
private Engine _engine = new Engine();
public void Start() { _engine.Start(); }
}
На практике наследование может использоваться реже, чем композиция, особенно в современных подходах к проектированию, таких как микросервисы или системы, ориентированные на интерфейсы. Тем не менее, оно остается важным инструментом, особенно когда необходимо обеспечить полиморфизм или когда существуют естественные иерархии объектов.
Наследование широко используется в библиотеках и фреймворках для предоставления базовых классов, от которых пользователи могут наследовать свои классы.
public abstract class ControllerBase
{
public abstract void Execute();
}
public class HomeController : ControllerBase
{
public override void Execute() { /*...*/ }
}
Наследование полезно при моделировании реальных объектов и их иерархий.
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
public class Manager : Employee
{
public int NumberOfSubordinates { get; set; }
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍1👾1
1. ref: позволяет передавать ссылочный тип по ссылке, чтобы изменения в методе влияли на оригинальный объект.
o Это нужно, когда требуется изменить ссылку (например, перенаправить на новый объект) внутри метода.
2. out: используется для передачи данных из метода наружу, не требуя их предварительной инициализации.
o Это упрощает возврат нескольких значений из метода через параметры, особенно для ссылочных типов.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Это два различных механизма в C# для изменения поведения методов, свойств или событий, унаследованных от базового класса.
Переопределение используется для предоставления новой реализации метода, свойства или события в производном классе, который уже был объявлен в базовом классе как
virtual, abstract или override.Ключевое слово
override.Требование
Метод базового класса должен быть объявлен с ключевым словом
virtual или abstract.public class BaseClass
{
public virtual void Display()
{
Console.WriteLine("BaseClass Display");
}
}
public class DerivedClass : BaseClass
{
public override void Display()
{
Console.WriteLine("DerivedClass Display");
}
}
Использование
BaseClass obj = new DerivedClass();
obj.Display(); // Выведет: "DerivedClass Display"
Сокрытие используется для создания новой реализации метода, свойства или события в производном классе, который уже существует в базовом классе, но без использования
virtual и override. При сокрытии используется ключевое слово new.Ключевое слово
new.Требование
Метод базового класса не обязательно должен быть объявлен с ключевым словом
virtual.public class BaseClass
{
public void Display()
{
Console.WriteLine("BaseClass Display");
}
}
public class DerivedClass : BaseClass
{
public new void Display()
{
Console.WriteLine("DerivedClass Display");
}
}
Использование
BaseClass obj1 = new BaseClass();
obj1.Display(); // Выведет: "BaseClass Display"
DerivedClass obj2 = new DerivedClass();
obj2.Display(); // Выведет: "DerivedClass Display"
BaseClass obj3 = new DerivedClass();
obj3.Display(); // Выведет: "BaseClass Display"
Переопределение: Базовый метод должен быть объявлен как virtual, abstract или override. Сокрытие: Базовый метод не обязательно должен быть виртуальным.
Переопределение: Используется ключевое слово override. Сокрытие: Используется ключевое слово new.
Переопределение: Изменяет указатель в виртуальной таблице базового класса, что позволяет полиморфизм. Сокрытие: Создает новый метод, который скрывает метод базового класса, но не изменяет виртуальную таблицу.
Переопределение: Поддерживает полиморфизм. Вызовы методов производятся на основе реального типа объекта. Сокрытие: Не поддерживает полиморфизм. Вызовы методов зависят от типа ссылки, а не от реального типа объекта.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
2. Ссылочные: классы, интерфейсы, массивы, строки.
3. Обобщённые (Generic): позволяют работать с типами данных, указанными при создании экземпляра (например, List<T>).
4. Пользовательские: структуры, перечисления (enum), типы, созданные программистом.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6👾1
Блок
using используется для управления временем жизни объектов, которые потребляют неуправляемые ресурсы, такие как файлы, соединения с базой данных или сетевые соединения. Основное преимущество использования блока using заключается в автоматическом освобождении этих ресурсов, что помогает предотвратить утечки ресурсов и улучшить управление памятью.Блок
using гарантирует, что метод Dispose() будет вызван автоматически, когда выполнение кода выйдет из блока using, даже если возникнет исключение. Это освобождает программиста от необходимости вручную вызывать Dispose() и уменьшает вероятность ошибок. using (StreamReader reader = new StreamReader("file.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
// StreamReader автоматически закрывается и освобождает ресурсы после выхода из блока using. Использование блока
using уменьшает количество необходимого кода для обеспечения правильного освобождения ресурсов. using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Работа с базой данных
}
// SqlConnection автоматически закрывается и освобождает ресурсы после выхода из блока using.
Блок
using повышает надежность кода, так как гарантирует, что ресурсы будут освобождены даже в случае возникновения исключений. try
{
using (StreamWriter writer = new StreamWriter("file.txt"))
{
writer.WriteLine("Hello, World!");
}
}
catch (Exception ex)
{
Console.WriteLine($"Произошла ошибка: {ex.Message}");
}
// StreamWriter автоматически закрывается и освобождает ресурсы после выхода из блока using, даже если произошла ошибка.
Код, использующий блок
using, выглядит более чистым и упрощает понимание и сопровождение. using (MemoryStream memoryStream = new MemoryStream())
{
// Работа с MemoryStream
}
// MemoryStream автоматически освобождается после выхода из блока using.
Работа с файлами
using System;
using System.IO;
class Program
{
static void Main()
{
using (StreamReader reader = new StreamReader("example.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
// StreamReader автоматически закрывается и освобождает ресурсы после выхода из блока using.
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
1. Поколения памяти: объекты делятся на поколения (0, 1, 2) для оптимизации очистки.
2. Триггер срабатывания: GC запускается при нехватке памяти или явным вызовом.
3. Сбор поколений: сначала очищаются молодые объекты (0 поколение), а затем старшие.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Когда браузер отправляет запрос и получает ответ от API, это включает несколько шагов, которые следуют HTTP-протоколу. Этот процесс включает в себя создание и отправку HTTP-запроса, обработку на сервере и получение HTTP-ответа.
Обычно запрос инициируется через JavaScript с использованием встроенных функций, таких как
XMLHttpRequest, fetch или сторонние библиотеки, такие как Axios. fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Метод: Указывает тип запроса (например, GET, POST, PUT, DELETE). URL: Указывает на какой URL отправляется запрос. Заголовки (Headers): Включают информацию о типе содержимого, авторизации и других метаданных. Тело (Body): Содержит данные, которые отправляются с запросом (для методов POST, PUT и PATCH).
Браузер использует сетевые протоколы для отправки сформированного HTTP-запроса на указанный сервер через интернет.
Сервер получает HTTP-запрос.
Сервер обрабатывает запрос, выполняя соответствующие действия, такие как чтение данных из базы данных, выполнение логики приложения или взаимодействие с другими сервисами.
Статус код (Status Code): Указывает результат обработки запроса (например, 200 OK, 404 Not Found, 500 Internal Server Error). Заголовки (Headers): Могут включать метаданные о содержимом ответа, кэшировании и других параметрах. Тело (Body): Содержит данные, которые отправляются обратно клиенту, часто в формате JSON или XML.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85
{
"id": 1,
"name": "Example",
"description": "This is an example response"
}
Браузер получает HTTP-ответ от сервера.
Статус код: Браузер или JavaScript-код проверяет статус код, чтобы определить, был ли запрос успешным. Заголовки: Заголовки могут быть использованы для получения дополнительной информации о ответе. Тело: Браузер разбирает тело ответа, если это необходимо, например, преобразует JSON-данные в объект JavaScript.
Полученные данные могут быть использованы в приложении для обновления интерфейса пользователя, хранения в локальном хранилище или выполнения других действий.
Отправка запроса
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
}
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json();
})
.then(data => {
console.log('Data received:', data);
// Используем данные для обновления UI или других целей
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤1
2. Исполнительный (динамический): достигается через переопределение методов (overriding) и виртуальные функции.
3. Полиморфизм позволяет обрабатывать объекты разных типов единообразно, например, через интерфейсы.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
DNS (Domain Name System) переводит доменные имена (например,
www.example.com) в IP-адреса (например, 192.0.2.1), чтобы компьютеры могли общаться. Браузер отправляет запрос к DNS-серверу.
DNS-сервер обращается к корневым, TLD и авторитетным серверам.
Авторитетный сервер возвращает IP-адрес.
Браузер использует IP-адрес для соединения с веб-сайтом.
IPv4-адрес.
IPv6-адрес.
Псевдоним.
Почтовый сервер.
Текстовые данные.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
2. O: Принцип открытости/закрытости (Open/Closed Principle).
3. L: Принцип подстановки Барбары Лисков (Liskov Substitution Principle).
4. I: Принцип разделения интерфейсов (Interface Segregation Principle).
5. D: Принцип инверсии зависимостей (Dependency Inversion Principle).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Маршрутизация в ASP.NET Core сопоставляет URL-запросы с контроллерами и действиями.
Определение маршрутов
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Пример контроллера
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
public class ProductsController : Controller
{
public IActionResult Index() => View();
public IActionResult Details(int id) => View();
}
Атрибуты маршрутизации
[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return View();
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
2. Усложнение взаимодействия: требуется настройка API и их маршрутизации.
3. Трудности тестирования и деплоя: распределённые системы сложнее тестировать и синхронизировать.
4. Затраты на ресурсы: каждый сервис требует выделения собственных ресурсов, что увеличивает их потребление.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
HTTP-запросы делятся на несколько типов, каждый из которых предназначен для выполнения определенных операций. Основные типы HTTP-запросов включают GET, POST, PUT, DELETE, PATCH и другие. Данные в этих запросах могут передаваться через URL, заголовки или тело запроса в зависимости от типа запроса и его назначения.
Запрос для получения данных с сервера. Данные передаются в URL в виде параметров запроса.
GET /api/products?category=electronics&page=2 HTTP/1.1
Host: example.com
Запрос для отправки данных на сервер для создания ресурса. Данные передаются в теле запроса.
POST /api/products HTTP/1.1
Host: example.com
Content-Type: application/json
{
"name": "New Product",
"price": 19.99
}
Запрос для отправки данных на сервер для обновления существующего ресурса. Данные передаются в теле запроса.
PUT /api/products/1 HTTP/1.1
Host: example.com
Content-Type: application/json
{
"name": "Updated Product",
"price": 29.99
}
Запрос для удаления ресурса на сервере. Данные могут передаваться в URL или заголовках, но тело запроса обычно не используется.
DELETE /api/products/1 HTTP/1.1
Host: example.com
Запрос для частичного обновления ресурса на сервере. Данные передаются в теле запроса.
PATCH /api/products/1 HTTP/1.1
Host: example.com
Content-Type: application/json
{
"price": 24.99
}
Запрос, аналогичный GET, но сервер возвращает только заголовки ответа без тела. Данные передаются в URL, как и в GET-запросе.
HEAD /api/products HTTP/1.1
Host: example.com
Запрос для получения поддерживаемых сервером методов HTTP для указанного ресурса. Данные передаются в URL.
OPTIONS /api/products HTTP/1.1
Host: example.com
GET-запрос с использованием fetch API в JavaScript
fetch('https://example.com/api/products?category=electronics&page=2')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));POST-запрос с использованием fetch API в JavaScript
fetch('https://example.com/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'New Product',
price: 19.99
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2