Redux Bölüm 2 Kullanarak React’te Kullanıcı İnternet Bağlantısı ile WebSocket’i Senkronize Etme – JSManifest

Redux Bölüm 2 Kullanarak React'te Kullanıcı İnternet Bağlantısı ile WebSocket'i Senkronize Etme – JSManifest

İçinde Bölüm Bir Bu seride redux ile bir tepki uygulaması yapılandırdık ve güncellemeleri çağıran eylemlerle birlikte internet durum güncellemeleri için durum yapımızı tanımladık. biz yarattık useInternet Uygulamanın durumu buna göre değiştirmek ve güncellemek için eylemleri başlatmasına izin vermek için gerekli olay işleyicilerini kaydetmek için kanca.

Bu eğitimde, uygulamaya WebSocket işlevselliğini uygulayarak devam edip uygulamayı daha da geliştireceğiz. Kullanıcının internet bağlantısında bir değişiklik olduğunda, websocket istemcisinin senkronize kalmasını ve uygun şekilde yanıt vermesini sağlayacağız.

Ek olarak, websocket istemcisi beklenmedik bir şekilde kapandıktan sonra fazladan UX ekleyeceğiz. Websocket istemcisi beklenmedik bir şekilde kapandığında, kendisini yeniden canlandırmasını sağlayacağız.

Not: Bu öğretici, aşağıdakiler hakkında biraz bilgi edinmenizi gerektirir. ağ yuvası API.

Not #2: Bu eğitimin kaynak kodunu indirmek istiyorsanız, devam edip onu şuradan klonlayabilirsiniz: depo.

Redüktörleri oluşturun

Websocket durum güncellemeleri için redüktörler oluşturarak başlayacağız. Bu öğreticinin birinci bölümünden hatırlarsanız, şöyle bir şey kodlamıştık:

import { INTERNET_ONLINE, INTERNET_OFFLINE } from '../actions'

const initialState = {
  internet: {
    isOnline: true,
  },
  ws: {
    connecting: false,
    opened: false,
  },
}

const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case INTERNET_ONLINE:
      return { ...state, internet: { ...state.internet, isOnline: true } }
    case INTERNET_OFFLINE:
      return { ...state, internet: { ...state.internet, isOnline: false } }
    default:
      return state
  }
}

export default appReducer

bakarak ws durum dilimi dinleyen bir bileşene ihtiyacımız var ws.opened bu, websocket istemcimiz açıldığında veya kapandığında değişecektir.

Bir özel oluşturarak başlayacağız useWebsocket kanca ve içe aktarma useSelector bu güncellemeleri dinlemek için redux işlevi:

src/hooks/useWebsocket.js

import { useSelector } from 'react-redux'

const useWebsocket = ({ isOnline }) => {
  const opened = useSelector((state) => state.app.ws.opened)

  return {
    opened,
  }
}

export default useWebsocket

Daha sonra alacak bir UI bileşeni oluşturacağız opened pervane olarak:

kaynak/bileşenler/WebsocketConnection.js

import React from 'react'
import './styles.css'

const StatusMessage = ({ opened }) => (
  <h5>Your websocket is {opened ? 'opened' : 'not opened'}</h5>
)

const BodyContent = ({ opened }) => (
  <div>
    <p>
      {opened && 'Now go do stuff and have an amazing day!'}
      {!opened &&
        "You can't do anything right now. Make yourself a cup of coffee in the mean time."}
    </p>
  </div>
)

const WebsocketConnection = ({ opened }) => {
  return (
    <div className='wsc-container'>
      <div>
        <StatusMessage opened={opened} />
        <BodyContent opened={opened} />
      </div>
    </div>
  )
}

export default WebsocketConnection

Kullanmak App.js son öğreticiden bileşen, bunu içe aktarmak için kullanacağız useWebsocket az önce yarattığımız kanca, böylece yakalayabiliriz opened belirtin ve yeni oluşturduğumuz UI bileşenimize iletin:

kaynak/App.js

import React, { useEffect } from 'react'
import useInternet from './hooks/useInternet'
import useWebsocket from './hooks/useWebsocket'
import './App.css'
import WebsocketConnection from './components/WebsocketConnection'

const App = () => {
  const { isOnline } = useInternet()
  const { opened } = useWebsocket({ isOnline })

  useEffect(() => {
    console.log(
      `%cYou are ${isOnline ? 'online' : 'offline'}.`,
      `color:${isOnline ? 'green' : 'red'}`,
    )
  }, [isOnline])

  return (
    <div>
      <h1>Making Websocket in Sync with the User's Internet Connectivity</h1>
      <hr />
      <WebsocketConnection opened={opened} />
    </div>
  )
}

export default App

Devam ettim ve biraz çekici görünmesi için bazı hızlı CSS stilleri uyguladım. Onları da kullanmak isterseniz burada sağladım:

kaynak/bileşenler/styles.css

div.wsc-container {
  padding: 35px;
  display: flex;
  align-items: center;
  justify-content: center;
}

div.wsc-container > div:first-child {
  text-align: center;
}

Ve şimdi sahip olduğumuz şey bu:

Şu anda, websocket istemcisi açıldığında, ekranda gösterilenlerin dışında bileşenlerimiz hiçbir şey yapmayacak. Bunun nedeni, redüktörler için henüz aksiyon yaratıcıları uygulamamış olmamızdır.

İlk önce aksiyon yaratıcıları için üç sabit oluşturacağız:

export const WS_CONNECTING = 'WS_CONNECTING'
export const WS_OPENED = 'WS_OPENED'
export const WS_CLOSED = 'WS_CLOSED'

Ardından, redüktörlerin aşağıdakilerle iletişim kurabilmesi için üç eylem yaratıcısı oluşturmamız gerekecek:

kaynak/eylemler/index.js

export const INTERNET_ONLINE = 'INTERNET_ONLINE'
export const INTERNET_OFFLINE = 'INTERNET_OFFLINE'
export const WS_CONNECTING = 'WS_CONNECTING'
export const WS_OPENED = 'WS_OPENED'
export const WS_CLOSED = 'WS_CLOSED'

export const internetOnline = () => ({
  type: INTERNET_ONLINE,
})

export const internetOffline = () => ({
  type: INTERNET_OFFLINE,
})

export const wsConnecting = () => ({
  type: WS_CONNECTING,
})

export const wsOpened = () => ({
  type: WS_OPENED,
})

export const wsClosed = () => ({
  type: WS_CLOSED,
})

Bu ayarlarla, şimdi redüktör dosyamıza gidebilir ve şu üç sabiti içe aktarabiliriz:

kaynak/redüktörler/appReducers.js

import {
  INTERNET_ONLINE,
  INTERNET_OFFLINE,
  WS_CONNECTING,
  WS_OPENED,
  WS_CLOSED,
} from '../actions'

const initialState = {
  internet: {
    isOnline: true,
  },
  ws: {
    connecting: false,
    opened: false,
  },
}

const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case INTERNET_ONLINE:
      return { ...state, internet: { ...state.internet, isOnline: true } }
    case INTERNET_OFFLINE:
      return { ...state, internet: { ...state.internet, isOnline: false } }
    default:
      return state
  }
}

export default appReducer

Çağrıldığında sonraki durumu hesaplamak için switch ifadesindeki üç sabiti tanımlayacak ve devam edecektir:

case WS_CONNECTING:
  return { ...state, ws: { ...state.ws, connecting: true } }
case WS_OPENED:
  return { ...state, ws: { ...state.ws, connecting: false, opened: true } }
case WS_CLOSED:
  return { ...state, ws: { ...state.ws, connecting: false, opened: false } }
const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case INTERNET_ONLINE:
      return { ...state, internet: { ...state.internet, isOnline: true } }
    case INTERNET_OFFLINE:
      return { ...state, internet: { ...state.internet, isOnline: false } }
    case WS_CONNECTING:
      return { ...state, ws: { ...state.ws, connecting: true } }
    case WS_OPENED:
      return { ...state, ws: { ...state.ws, connecting: false, opened: true } }
    case WS_CLOSED:
      return { ...state, ws: { ...state.ws, connecting: false, opened: false } }
    default:
      return state
  }
}

Her şey yolunda! Şimdiye kadar devam ettik ve kullanıcı arayüzünü redüktör durumuyla bağladık, ardından eylemleri redüktörlere göndermeye yardımcı olacak eylem oluşturucuları oluşturduk. Redüktörler oradan alacak ve bileşenlerin güncellenebilmesi için bir sonraki durumu hesaplayacaktır.

Şimdi yapmamız gereken bir websocket istemci örneğini başlatmak. Ancak, bağlanmak için bir websocket url’sine ihtiyacı var. Bu eğitimin uğruna, kullanabileceğimiz herkese açık bir tane sağladım:

const wsUrl = 'wss://echo.websocket.org'

Bu URL’yi yapıcıya ileterek bir websocket bağlantısı oluşturabilecek ve açık bırakabileceksiniz. Devam edeceğiz ve bir wsRef hangisi atanacak useRef. Websocket istemci örneğini ekleyeceğimiz yer burasıdır.

src/hooks/useWebsocket.js

import { useRef, useEffect } from 'react'
import { useSelector } from 'react-redux'

const wsUrl = 'wss://echo.websocket.org'

const useWebsocket = ({ isOnline }) => {
  const opened = useSelector((state) => state.app.ws.opened)

  const wsRef = useRef()

  
  useEffect(() => {
    if (!wsRef.current) {
      wsRef.current = new WebSocket(wsUrl)
    }
  }, [])

  return {
    opened,
    ws: wsRef.current,
  }
}

export default useWebsocket

Devam ettik ve bir useEffect bu, otomatik olarak yeni bir websocket istemcisini başlatacak ve ekleyecektir. wsRef böylece sonuçları ilk yükte görebiliriz.

Kolaylık sağlamak için, bir websocket istemcisini başlatmak için bir yöntem ve şu anda açık olan websocket istemcisini sonlandırmak için bir yöntem sağladım:

const initWebsocket = () => {
  if (wsRef.current) {
    wsRef.current.close()
  }
  wsRef.current = new WebSocket(wsUrl)
}
const endWebsocket = () => {
  if (wsRef.current) {
    wsRef.current.close()
  }
}

Sonuç:

src/hooks/useWebsocket.js

import { useRef, useEffect } from 'react'
import { useSelector } from 'react-redux'

const wsUrl = 'wss://echo.websocket.org'

const useWebsocket = ({ isOnline }) => {
  const opened = useSelector((state) => state.app.ws.opened)

  const wsRef = useRef()

  const initWebsocket = () => {
    if (wsRef.current) {
      wsRef.current.close()
    }
    wsRef.current = new WebSocket(wsUrl)
  }

  const endWebsocket = () => {
    if (wsRef.current) {
      wsRef.current.close()
    }
  }

  
  useEffect(() => {
    if (!wsRef.current) {
      wsRef.current = new WebSocket(wsUrl)
    }
  }, [])

  return {
    ws: wsRef.current,
    opened,
    initWebsocket,
    endWebsocket,
  }
}

export default useWebsocket

Kullanıcının websocket istemcisini manuel olarak açabileceği/kapatabileceği işlevselliği destekleyebilmemiz için kullanıcı arayüzüne iki ekstra düğme ekleyeceğiz (bu özellik bu eğitimde gerçekten kullanılmamaktadır, ancak kolayca mümkün olabilir). Bunlardan biri tıklandığında yeni bir websocket istemci örneği başlatmak için kullanılacak ve diğeri onu sonlandıracak:

kaynak/App.js

const { ws, opened, initWebsocket, endWebsocket } = useWebsocket({ isOnline })
<div className='button-controls'>
  <button type='button' onClick={initWebsocket}>
    Initiate Websocket
  </button>
  <button type='button' onClick={endWebsocket}>
    End Websocket
  </button>
</div>

Harika!

Fakat bekle. Bileşenlerin güncellenmesi için bir yol yarattık, ancak güncellenecekleri bir yere ve zamana ihtiyaçları var.

bizimkilere geri döneceğiz initWebsocket işlev ve bazı olay dinleyicilerini open ve close Etkinlikler:

src/hooks/useWebsocket.js

const initWebsocket = () => {
  if (wsRef.current) wsRef.current.close()
  wsRef.current = new WebSocket(wsUrl)
  wsRef.current.addEventListener('message', () => {})
  wsRef.current.addEventListener('open', () => {})
  wsRef.current.addEventListener('close', () => {})
  wsRef.current.addEventListener('error', () => {})
}

Bir websocket bağlantısının dört farklı dinleyiciye sahip olabileceğini unutmayın:

dinleyici Tanım
kapalı WebSocket bağlantısının readyState’i KAPALI olarak değiştiğinde çağrılır
mesaj Sunucudan bir mesaj alındığında çağrılır
açık WebSocket bağlantısının readyState’i OPEN olarak değiştiğinde çağrılır
hata WebSocket’te bir hata oluştuğunda çağrılır

Bununla birlikte, şimdi bazı işleyicileri eklemeyi dört gözle bekleyebiliriz:

const onMessage = (msg) => {
  console.log(msg)
}

const onOpen = () => {
  console.log('WS client opened')
}

const onClose = () => {
  console.log('WS client closed')
}

const onError = () => {
  console.log('WS client errored')
}

const initWebsocket = () => {
  if (wsRef.current) wsRef.current.close()
  wsRef.current = new WebSocket(wsUrl)
  wsRef.current.addEventListener('message', onMessage)
  wsRef.current.addEventListener('open', onOpen)
  wsRef.current.addEventListener('close', onClose)
  wsRef.current.addEventListener('error', onError)
}

Şu anda bizim kullanım Etkisi üzerine yeni bir websocket istemci örneği ekliyor wsRef.current ancak şimdi olay işleyicilerini kaydetme uygulaması eksik. Bu nedenle, bunun yerine kaydı yapan işleyiciyi çağırmasını sağlamak için hızlı bir güncelleme yapmamız gerekiyor:

src/hooks/useWebsocket.js


useEffect(() => {
  if (!wsRef.current) initWebsocket()
}, [initWebsocket])

Ayrıca olay dinleyicilerini başlatıcı işleyicisine kaydettiğimiz için, bellek sızıntısını önlemek için istemci kapatıldığında bunların kaldırıldığından da emin olmamız gerekir:

src/hooks/useWebsocket.js

const endWebsocket = () => {
  if (wsRef.current) {
    wsRef.current.removeEventListener('message', onMessage)
    wsRef.current.removeEventListener('open', onOpen)
    wsRef.current.removeEventListener('close', onClose)
    wsRef.current.removeEventListener('error', onError)
    wsRef.current.close()
  }
}

En başından beri amacımız, kullanıcının internet bağlantısı ile senkronize websocket. Kodumuzda şu ana kadar ne elde ettiğimize baktığımızda, artık bu işlevselliğe yaklaşmamıza izin verecek bir API kurulumumuz var.

Kullanıcının interneti çevrimdışı olduğunda, bizim ağ yuvası close olay işleyicisi çağrılmalıdır bir şekilde.

Mesele şu ki, websocket istemcisi senkronize değil internet bağlantısı ile. Bunu linux ve windows makinelerinde test ettim ve internet websocket istemcisinden çıktığında hazırDurum mülk hala sıkışmış olabilir 1 (müşterinin değeri AÇIK durum). Bu yüzden kendi kendine kapanmasına güvenemeyiz.

Farklı olana hızlı bir saygı için hazırDurum‘s’ yukarıdaki bağlantıya tıklayabilir veya bu tabloya bakabilirsiniz:

Bir websocket’in geçebileceğini unutmayın dört farklı eyalet bağlantılarının ömrü boyunca:

Değer Durum Tanım
0 BAĞLANIYOR Soket oluşturuldu. Bağlantı henüz açık değil.
1 AÇIK Bağlantı açık ve iletişim kurmaya hazır.
2 KAPANIŞ Bağlantı kapanma sürecinde.
3 KAPALI Bağlantı kapalı veya açılamadı.

İnternet çevrimdışı olursa, websocket istemcisini çağırmamız gerekir. kapat işleyici hemen. Tekrar çevrimiçi olduğunda biz de aynısını yapmalıyız. açık işleyici, aksi takdirde websocket istemcisi, internet bağlantısı kesilmiş olsa bile kullanıcının hala bağlı olduğunu gösterecektir. Çok yanıltıcı! Bunu düzeltmeliyiz.

Daha önce oluşturduğumuz aksiyon yaratıcılarına geri dönersek, onları redüktörlerimize bir sinyal göndermek için kullanabiliriz:

kaynak/eylemler/index.js

export const wsConnecting = () => ({
  type: WS_CONNECTING,
})

export const wsOpened = () => ({
  type: WS_OPENED,
})

export const wsClosed = () => ({
  type: WS_CLOSED,
})

Ve işte redüktörlerimizde yapılan son güncelleme:

kaynak/redüktörler/appReducers.js

import {
  INTERNET_ONLINE,
  INTERNET_OFFLINE,
  WS_CONNECTING,
  WS_OPENED,
  WS_CLOSED,
} from '../actions'

const initialState = {
  internet: {
    isOnline: true,
  },
  ws: {
    connecting: false,
    opened: false,
  },
}

const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case INTERNET_ONLINE:
      return { ...state, internet: { ...state.internet, isOnline: true } }
    case INTERNET_OFFLINE:
      return { ...state, internet: { ...state.internet, isOnline: false } }
    case WS_CONNECTING:
      return { ...state, ws: { ...state.ws, connecting: true } }
    case WS_OPENED:
      return { ...state, ws: { ...state.ws, connecting: false, opened: true } }
    case WS_CLOSED:
      return { ...state, ws: { ...state.ws, connecting: false, opened: false } }
    default:
      return state
  }
}

export default appReducer

İndirgeyicilerimizin tümü, aksiyon yaratıcılarıyla bağlantılıdır ve şimdi güncellenmeleri gerekir. Bir sonraki adımımız, UI bileşenlerini güncellemektir. değiştireceğiz kullanımWebsocket UI bileşenimizin güncellenmesi için bazı işleyicileri çağırmak için kanca. Bunu yapmak, gelecekte kodumuzu okumayı ve korumayı çok daha kolay hale getirecektir.

Kancamızın içinde bir tane daha yapacağız kullanım Etkisi hangi her zaman değerini çağıracak çevrimiçi değişir. Ne zaman çevrimiçi değişiklikler falsedevam edeceğiz ve göndereceğiz wsKapalı eylem. olarak değiştiğinde true göndereceğiz wsOpened eylem. Bunu yapmak, duruma bağlı tüm bileşenlerin değişikliklere göre güncellenmesini sağlayacaktır.

src/hooks/useWebsocket.js


useEffect(() => {
  if (isOnline && !opened) {
    dispatchAction(wsOpened())
  } else if (!isOnline && opened) {
    dispatchAction(wsClosed())
  }
}, [isOnline, dispatchAction, opened])

Ayrıca kargoyu da göndermemiz gerekiyor. wsKapalı eylem uçWebsocket yardımcı olmak için işlev kullanım EtkisiKullanıcının internet bağlantısında değişiklik olduğunda senkronize kalmak için:

src/hooks/useWebsocket.js

const endWebsocket = () => {
  if (wsRef.current) {
    wsRef.current.removeEventListener('message', onMessage)
    wsRef.current.removeEventListener('open', onOpen)
    wsRef.current.removeEventListener('close', onClose)
    wsRef.current.removeEventListener('error', onError)
    wsRef.current.close()
    if (opened) dispatchAction(wsClosed())
  }
}

Redux’daki durumumuz şimdi güncellenmeli ve websocket istemcisini senkronize tutmaya çalışmalıdır. Ancak, websocket istemcisi henüz otomatik olarak kapanmaz. Bizim ihtiyacımız Bunu yapmak çağırarak kapatın kapat onClose işleyicisindeki yöntem:

src/hooks/useWebsocket.js

import { useDispatch, useSelector } from 'react-redux'
import { wsOpened, wsClosed } from '../actions'
const dispatchAction = useDispatch()
const onOpen = (e) => {
  console.log('WS client opened')
}
const onClose = (e) => {
  console.log('WS client closed')
  if (wsRef.current) {
    wsRef.current.close()
  }
}

Aksiyon yaratıcılarını açık ve kapat websocket istemcisi için olay işleyicileri, devam edip kancayı şimdi bir bileşene aktarabiliriz:

import React, { useEffect } from 'react'
import useInternet from './hooks/useInternet'
import useWebsocket from './hooks/useWebsocket'
import WebsocketConnection from './components/WebsocketConnection'
import './App.css'

const App = () => {
  const { isOnline } = useInternet()
  const { ws, opened, initWebsocket, endWebsocket } = useWebsocket({ isOnline })

  useEffect(() => {
    console.log(
      `%cYou are ${isOnline ? 'online' : 'offline'}.`,
      `color:${isOnline ? 'green' : 'red'}`,
    )
  }, [isOnline])

  return (
    <div>
      <h1>Making Websocket in Sync with the User's Internet Connectivity</h1>
      <hr />
      <WebsocketConnection opened={opened} />
      <div className='button-controls'>
        <button type='button' onClick={initWebsocket}>
          Initiate Websocket
        </button>
        <button type='button' onClick={endWebsocket}>
          End Websocket
        </button>
      </div>
    </div>
  )
}

export default App

Ve işte! İnternet bağlantınızı kesmeyi deneyin ve sonuçları görün:

Çevrimiçi durum:

2

Sonra internet bağlantısını kestim:

3

İnternete tekrar bağlandı:

4

Çözüm

Ve bu serinin sonu böyle bitiyor!

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.