javascript - React hooks 将引用存储到第三方库的最佳实践

标签 javascript reactjs react-hooks

我正在围绕 pusher-js 创建一个包装 Hook 库发布到野外。对于每个钩子(Hook)(即 useChannelusePresenceChanneluseTrigger ),我需要保留对 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/

相关文章:

css - 如何在 Material ui 中为网格项添加新行?

javascript - 如何使用 useState Hook 更新对象的状态并保留现有元素?

javascript - Jquery如果$this属性选择器包含一个词

javascript - 关联数组到字符串和字符串到关联数组

asp.net - 将焦点设置在由 javascript 发起的回发上

javascript - react : Setinterval is not stopping even after clearing the timer using clearInterval

javascript - React SideEffect 脱离 useEffect 钩子(Hook)

javascript - 黑莓网络上的 Canvas Action 太慢

reactjs - React + Webpack bundle - 如果浏览器中排除了 node_modules 'require is not defined'

javascript - 父 DOM 节点更改类型时的 ReactJS 协调