在 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);
这样,如果您已经拥有频道,就不会重新订阅。
最初,我写了一个钩子,它将连接到推送器通道,然后将内部有效负载更新为从推送器接收到的任何消息。
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);
这样,如果您已经拥有频道,就不会重新订阅。