我正在围绕 pusher-js
创建一个包装 Hook 库发布到野外。对于每个钩子(Hook)(即 useChannel
、 usePresenceChannel
、 useTrigger
),我需要保留对 Pusher 实例的引用,即 new Pusher() 存储在 Context Provider 中。我允许传入第三方身份验证,因此我需要动态创建 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])
}
任何建议、过去的例子或模式都非常感谢,因为我想确定这一点!
最佳答案
我会使用 ref 来保存 Pusher
的新实例作为 recommended by Dan .
您不需要通过在内部效果中执行空检查和断开连接( clientRef.current && clientRef.current.disconnect()
)来进行清理,因为每当 useEffect
运行时,当你在 return 语句中处理它时,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]);
}
关于javascript - React hooks 将引用存储到第三方库的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57564656/