我正在围绕pusher-js
创建一个包装钩子库,以发布到野外。对于每个钩子(即useChannel
,usePresenceChannel
,useTrigger
),我需要保留对Pusher实例的引用,即存储在上下文提供程序中的new Pusher()。我允许传递第三方身份验证,因此我需要即时创建Pusher实例。我不确定是否应该将其存储在useState或useRef中。
eslint-plugin-react-hooks
规则抱怨useState和useRef的各种组合来存储它。在尝试正确清理每个副作用时,我也看到了不良的副作用。我不确定什么是最佳做法。
这是具有重要细节的简化实现。有关我的问题,请参见下面的注释1. 2.和3.。
// PusherContext.jsx
export const PusherContext = React.createContext();
export function PusherProvider({key, config, ...props}){
// 1. Should I store third party libraries like this?
const clientRef = useRef();
// vs const [client, setClient] = useState();
// when config changes, i.e. config.auth, re-create instance
useEffect(() => {
clientRef.current && clientRef.current.disconnect();
clientRef.current = new Pusher(key, {...config});
}, [clientRef, config]);
return <PusherContext.Provider value={{ client }} {...props} />
}
// useChannel.js
export function useChannel(
channelName,
eventName,
callback,
callbackDeps
){
const { client } = useContext(PusherContext);
const callbackRef = useCallback(callback, callbackDeps);
// 2. Or should I be using state instead?
const [channel, setChannel] = useState();
// vs. const channelRef = useRef();
useEffect(() => {
if(client.current){
const pusherChannel = client.current.subscribe(channelName);
pusherChannel.bind(eventName, callbackRef.current);
setChannel(pusherChannel);
}
// can't cleanup here because callbackRef changes often.
// 3. eslint: Mutable values like 'client.current' aren't valid dependencies because mutating them doesn't re-render the component
}, [client.current, callbackRef])
// cleanup for unbinding the event
// re-bind makes sense with an updated callback ref
useEffect(() => {
channel.unbind(eventName)
}, [client, channel, callbackRef, eventName]);
// cleanup for unsubscribing from the channel
useEffect(() => {
clientRef.unsubscribe(channelName);
}, [client, channelName])
}
任何建议,过去的例子或模式都非常感谢,因为我想钉上这个!
答案 0 :(得分:1)
我会使用ref将Pusher
的新实例保存为recommended by Dan。
您最初不需要通过内部检查来进行空检查和断开连接(clientRef.current && clientRef.current.disconnect()
进行清理,因为每当运行useEffect
时,React在返回时处理它都会断开连接声明。
export function PusherProvider({ key, config, ...props }) {
// 1. Should I store third party libraries like this?
const clientRef = useRef();
// vs const [client, setClient] = useState();
// when config changes, i.e. config.auth, re-create instance
// useEffect(() => {
// clientRef.current && clientRef.current.disconnect();
// clientRef.current = new Pusher(key, { ...config });
// }, [clientRef, config]);
// Create an instance, and disconnect on the next render
// whenever clientRef or config changes.
useEffect(() => {
clientRef.current = new Pusher(key, { ...config });
// React will take care of disconnect on next effect run.
return () => clientRef.current.disconnect();
}, [clientRef, config]);
return <PusherContext.Provider value={{ client }} {...props} />;
}
对于第二种情况,我尽力在下面的内联中写建议。
要点是un/subscription
是相关事件,因此应该以相同的效果进行处理(与PusherProvider
的情况一样)。
// useChannel.js
export function useChannel(channelName, eventName, callback, callbackDeps) {
const { client } = useContext(PusherContext);
const callbackRef = useCallback(callback, callbackDeps);
// 2. Or should I be using state instead?
// I believe a state makes sense as un/subscription depends on the channel name.
// And it's easier to trigger the effect using deps array below.
const [channel, setChannel] = useState();
useEffect(() => {
// do not run the effect if we don't have the Pusher available.
if (!client.current) return;
const pusherChannel = client.current.subscribe(channelName);
pusherChannel.bind(eventName, callbackRef.current);
setChannel(pusherChannel);
// Co-locate the concern by letting React
// to take care of un/subscription on each channel name changes
return () => client.current.unsubscribe(channelName);
// Added `channelName` in the list as the un/subscription occurs on channel name changes.
}, [client.current, callbackRef, channelName]);
// This.. I am not sure... ?
// cleanup for unbinding the event
// re-bind makes sense with an updated callback ref
useEffect(() => {
channel.unbind(eventName);
}, [client, channel, callbackRef, eventName]);
// Moved it up to the first `useEffect` to co-locate the logic
// // cleanup for unsubscribing from the channel
// useEffect(() => {
// clientRef.unsubscribe(channelName);
// }, [client, channelName]);
}