paint-brush
Next.js ve SignalR: Zahmetsiz Soket Entegrasyonu ve Sorun Gidermeile@chilledcowfan
5,368 okumalar
5,368 okumalar

Next.js ve SignalR: Zahmetsiz Soket Entegrasyonu ve Sorun Giderme

ile Anton Burduzha8m2023/09/05
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Çok sayıda proje, verileri yeniden getirmeden bir arayüzün değişikliklere anında tepki vermesini sağlamak için web soketleri entegrasyonu gerektirir. Bunlardan bahsetmeyeceğiz veya daha iyi bir geliştirme deneyimi için API sağlayan 3. parti kütüphaneler arasında karşılaştırma yapmayacağız. Amacım NextJ'lerle `@microsoft/signalr`ın hızlı bir şekilde nasıl entegre edileceğini göstermektir.
featured image - Next.js ve SignalR: Zahmetsiz Soket Entegrasyonu ve Sorun Giderme
Anton Burduzha HackerNoon profile picture

Azınlık ama yine de çok sayıda proje, verileri yeniden getirmeden bir arayüzün değişikliklere anında tepki vermesini sağlamak için web soketleri entegrasyonu gerektirir.


Bu çok önemli bir şey ve biz onlar hakkında konuşmayacağız veya daha iyi bir geliştirici deneyimi için API sağlayan 3. taraf kütüphaneler arasında bir karşılaştırma yapmayacağız.


Amacım @microsoft/signalr NextJs ile hızlı bir şekilde nasıl entegre edileceğini göstermek. Ve geliştirme sırasında karşılaştığımız sorunları nasıl çözeceğimizi.


Umarım herkes NextJS projesini yerel olarak kurmuş ve konuşlandırmıştır. Benim durumumda sürüm 13.2.4 . Biraz daha önemli kütüphaneler ekleyelim: veri almak ve yerel önbellekle daha fazla çalışmak için swr (sürüm 2.1.5 ) ve web yuvaları için @microsoft/signalr (sürüm 7.0.5 ) - API.


 npm install --save @microsoft/signalr swr


REST API'mizden ilk verileri almak için basit bir fetcher işlevi ve useChatData adında yeni bir kanca oluşturmaya başlayalım. Sohbete ilişkin mesajların bir listesini, hataları algılayan alanları ve yükleme durumunu ve önbelleğe alınan verileri değiştirmeye izin veren mutate yöntemini döndürür.


 // hooks/useChatData.ts import useSWR from 'swr'; type Message = { content: string; createdAt: Date; id: string; }; async function fetcher<TResponse>(url: string, config: RequestInit): Promise<TResponse> { const response = await fetch(url, config); if (!response.ok) { throw response; } return await response.json(); } export const useChatData = () => { const { data, error, isLoading, mutate } = useSWR<Message[]>('OUR_API_URL', fetcher); return { data: data || [], isLoading, isError: error, mutate, }; };


Beklendiği gibi çalıştığını test etmek için sayfa bileşenimizi güncelleyelim. En üstteki kancamızı içe aktarın ve aşağıdaki kod parçasındaki gibi verileri çıkarın. Çalışırsa, işlenmiş verileri göreceksiniz. Gördüğünüz gibi oldukça basit.


 // pages/chat.ts import { useChatData } from 'hooks/useChatData'; const Chat: NextPage = () => { const { data } = useChatData(); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); };


Bir sonraki adım, gelecekteki sayfamızı web soketlerine bağlamayı, NewMessage olaylarını yakalamayı ve önbelleği yeni bir mesajla güncellemeyi gerektirir. Soket hizmetini ayrı bir dosyada oluşturmaya başlamayı öneriyorum.


SignalR belgelerindeki örneklere göre, olayları daha fazla dinlemek için bir bağlantı örneği oluşturmamız gerekiyor. Ayrıca yinelemeleri önlemek için bir bağlantı nesnesi ve bağlantıları başlatmak/durdurmak için iki yardımcı ekledim.


 // api/socket.ts import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; let connections = {} as { [key: string]: { type: string; connection: HubConnection; started: boolean } }; function createConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj) { console.log('SOCKET: Registering on server events ', messageType); const connection = new HubConnectionBuilder() .withUrl('API_URL', { logger: LogLevel.Information, withCredentials: false, }) .withAutomaticReconnect() .build(); connections[messageType] = { type: messageType, connection: connection, started: false, }; return connection; } else { return connections[messageType].connection; } } function startConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj.started) { connectionObj.connection.start().catch(err => console.error('SOCKET: ', err.toString())); connectionObj.started = true; } } function stopConnection(messageType: string) { const connectionObj = connections[messageType]; if (connectionObj) { console.log('SOCKET: Stoping connection ', messageType); connectionObj.connection.stop(); connectionObj.started = false; } } function registerOnServerEvents( messageType: string, callback: (payload: Message) => void, ) { try { const connection = createConnection(messageType); connection.on('NewIncomingMessage', (payload: Message) => { callback(payload); }); connection.onclose(() => stopConnection(messageType)); startConnection(messageType); } catch (error) { console.error('SOCKET: ', error); } } export const socketService = { registerOnServerEvents, stopConnection, };


Artık sayfamız kod pasajındaki gibi görünebilir. Mesaj listesiyle data alıp çıkarıyoruz ve bunları oluşturuyoruz. Ayrıca yukarıdaki useEffect , NewMessage olayını kaydeder, bir bağlantı oluşturur ve arka ucu dinler.


Olay tetiklendiğinde, kancadaki mutate yöntemi mevcut listeyi yeni bir nesneyle günceller.


 // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); };


Bana güzel görünüyor, işe yarıyor ve yeni mesajların akışta nasıl göründüğünü görüyoruz. Temel örneği sohbetle seçtim çünkü açık ve anlaşılması kolay. Ve elbette bunu kendi mantığınıza göre uygularsınız.

Küçük Bonus

Sürümlerden birini ( @microsoft/signalr ) kullanırken çoğaltmalarla ilgili bir sorunla karşılaştık. Bağımlılık dizisi olan useEffect bağlandı. Bağımlılık her değiştirildiğinde, connection.on(event, callback); geri aramayı önbelleğe aldı ve tekrar tekrar tetikledi.


 useEffect(() => { // data equals [] by default (registerOnServerEvents 1 run), // but after initial data fetching it changes (registerOnServerEvents 2 run) // each event changes data and triggers runnning of registerOnServerEvents socketService.registerOnServerEvents( 'NewMessage', // callback cached (payload: Message) => { // mutate called multiple times on each data change mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); // after getting 3 messages events, we had got 4 messages rendered lol


Bulduğumuz en hızlı ve en güvenilir çözüm, verilerin bir kopyasını React ref içinde tutmak ve bunu gelecekteki güncellemeler için useEffect içinde kullanmaktı.


 // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); const messagesRef = useRef<Message[]>([]); useEffect(() => { messagesRef.current = chatData; }, [chatData]); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { const messagesCopy = messagesRef.current.slice(); mutate(() => [...messagesCopy, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); };


Şu anda @microsoft/signalr gerekli düzeltmeleri içeren yeni bir sürümünü kullanıyoruz. Ama yine de birisi bu çözümü yararlı bulursa ve bu geçici çözümü kullanırsa mutlu olacağım. Sonuç olarak, SignalR ile olan deneyimimin oldukça olumlu olduğunu, kurulumun herhangi bir özel bağımlılık veya ayar gerektirmediğini, gayet iyi çalıştığını ve ihtiyaçlarımızı karşıladığını söylemek istiyorum.