在 React 应用程序中记忆 PusherJS 订阅

Memoizing PusherJS subscriptions in a react app

最初,我写了一个钩子,它将连接到推送器通道,然后将内部有效负载更新为从推送器接收到的任何消息。

export const ChannelHook = <T>(
  channelName: string,
  messageType: string,
  skip = false
) => {
  const [payload, setPayload] = useState<T | null>(null)
  const [isLoading, setIsLoading] = useState(true)
  useEffect(() => {
    if (!skip) {
      setIsLoading(true)
      const channel = Pusher.subscribe(channelName)
      channel.bind(messageType, (data: T) => {
        setPayload(data)
      })
      channel.bind('pusher:subscription_succeeded', () => setIsLoading(false))
      return () => {
        channel.disconnect()
        channel.unsubscribe()
        Pusher.unsubscribe(channelName)
      }
    }
  }, [channelName, messageType, skip])
  return {
    payload,
    isLoading,
  }
}

遵循与 rtk 查询非常相似的设计模式。

现在,当从其他组件连接到同一频道时,我想避免额外的订阅调用,因为每个订阅请求都需要 auth API 调用。主要思想是在页面之间路由时也防止订阅。

你不能在 React 钩子上使用 useMemo(如果可能的话会很棒)但是有没有办法将推送器与 redux 存储集成?

由于我的推送器通道按两个参数分类,a) 房间类型 b) 房间名称,并且房间类型的数据类型是固定的。我用 redux toolkit 写了类似下面的东西

export const Pusher = new pusherJs(key, {
  cluster,
  channelAuthorization,
})

const initialState: PusherState = {
  typeAChannelData: {},
  typeBChannelData: {},
}


export const subscribe = <T>(
  roomName: string,
  messageKey: string,
  dataCallback: (data: T) => void
): Promise<Channel> => {
  return new Promise((res, rej) => {
    const channel = Pusher.subscribe(roomName)
    channel.bind('pusher:subscription_succeeded', () => res(channel))
    channel.bind('pusher:subscription_error', (err: Error) => rej(err))
    channel.bind(messageKey, dataCallback)
  })
}

export const pusherSlice = createSlice({
  name: 'pusher',
  initialState,
  reducers: {
    connect: (
      state,
      action: PayloadAction<{ type: RoomType; roomName: string }>
    ) => {
      const { type, roomName } = action.payload
      switch (type) {
        case RoomType.TypeA:
          if (!(roomName in state.typeAChannelData)) {
            state.typeAChannelData[roomName] = {
              isLoading: true,
            }
            subscribe(roomName, 'EVENTA', (data: DataTypeA) => {
              state.typeAChannelData[roomName].data = data
            }).then((channel) => {
              state.typeAChannelData[roomName].isLoading = false
              state.typeAChannelData[roomName].channel = channel
            })
          }
          break
        case RoomType.TypeB:
          if (!(roomName in state.typeBChannelData)) {
            state.typeBChannelData[roomName] = {
              isLoading: true,
            }
            subscribe(roomName, 'EVENTB', (data: DataTypeB) => {
              state.typeBChannelData[roomName].data = data
            }).then((channel) => {
              state.typeBChannelData[roomName].isLoading = false
              state.typeBChannelData[roomName].channel = channel
            })
          }
          break
        default:
          throw new Error('Invalid Room type')
      }
    },
    disconnect: (
      state,
      action: PayloadAction<{ type: RoomType; roomName: string }>
    ) => {
      const { type, roomName } = action.payload
      switch (type) {
        case RoomType.TypeA:
          if (roomName in state.typeAChannelData) {
            state.typeAChannelData[roomName].channel?.disconnect()
            state.typeAChannelData[roomName].channel?.unsubscribe()
            Pusher.unsubscribe(roomName)
            delete state.typeAChannelData[roomName]
          }
          break
        case RoomType.TypeB:
          if (roomName in state.typeBChannelData) {
            state.typeBChannelData[roomName].channel?.disconnect()
            state.typeBChannelData[roomName].channel?.unsubscribe()
            Pusher.unsubscribe(roomName)
            delete state.typeBChannelData[roomName]
          }
          break
        default:
          throw new Error('Invalid Room type')
      }
    },
  },
})

export const { connect, disconnect } = pusherSlice.actions

export default pusherSlice.reducer

但这会在我无法调试的 redux 上引发 illegal operation attempted on a revoked proxy 错误。有更好的选择吗?

我找到了这个包 https://github.com/TheRusskiy/pusher-redux 但我需要强大的 ts 支持。

我不确定你所说的“你不能在 React 挂钩上使用 useMemo”是什么意思?但这就是我实现推送订阅频道挂钩的方式。

这不是您问题的确切答案,但我无法将其放入评论中,希望它能帮助您朝着正确的方向前进。

export const useChannel = (channelName: string, userId?: string) => {
  if (!pusher) {
    initPusher();
  }
  const channel = useMemo(() => {
    if (!channelName) return null;
    if (!pusher.channel(channelName)) { // you can use this to check if you're already subscribed to a pusher channel
      return pusher.subscribe(channelName);
    }
    // if we already have the channel we still want to return it
    return pusher.channel(channelName);
  }, [channelName]);

  return channel;
};

我有一个单独的钩子来绑定事件,但也许你可以使用这样的东西:

const channel = Pusher.channel(channelName) || Pusher.subscribe(channelName);

这样,如果您已经拥有频道,就不会重新订阅。