React 钩子最佳实践将引用存储到第三方库



我正在围绕pusher-js创建一个包装钩子库以发布到野外。对于每个钩子(即useChannelusePresenceChanneluseTrigger(,我需要保留对 Pusher 实例的引用,即存储在上下文提供程序中的新 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])
}

任何建议,过去的例子或模式都非常感谢,因为我想钉住这个!

我会按照 Dan 的建议使用 ref 来保存一个新的Pusher实例。

您最初不需要通过在内部效果中进行空检查和断开连接(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]);
}

相关内容

  • 没有找到相关文章

最新更新