React useuse钩子如何处理多个调用

时间:2019-09-13 03:47:55

标签: javascript reactjs react-hooks

我正在使用React useScript hook (from useHooks website)。它允许轻松加载外部脚本并在加载后对其进行缓存。

它工作正常,但是我发现一个边缘情况导致我遇到了一些问题。 问题出在caching of scripts上。

如果我使用useScript在页面中将组件加载了两次,如下所示:

const ScriptDemo = src => {
  const [loaded, error] = useScript("https://hzl7l.codesandbox.io/test-external-script.js");

  return (
    <div>
      <div>
        Script loaded: <b>{loaded.toString()}</b>
      </div>
      <br />
      {loaded && !error && (
        <div>
          Script function call response: <b>{TEST_SCRIPT.start()}</b>
        </div>
      )}
    </div>
  );
};

function App() {
  return (
    <div>
      <ScriptDemo />
      <ScriptDemo />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

您可以在此处查看和复制:https://codesandbox.io/s/usescript-hzl7l

如果我的App只有一个ScriptDemo,那很好,但是拥有两个或多个会失败。

实际上,流程将是:

  • ScriptDemo->是否缓存脚本?否->将脚本添加到缓存->提取->渲染
  • ScriptDemo2->是否缓存脚本?是->渲染(但尚未完成加载 还..)

解决此问题的一种方法是更改​​useScript钩子,以仅在成功完成onScriptLoad回调后缓存脚本。 这种方法的问题是外部脚本将被调用两次。 看到这里:https://codesandbox.io/s/usescript-0yior

  • ScriptDemo->是否缓存脚本?否->获取它->将脚本添加到缓存->渲染
  • ScriptDemo->是否缓存脚本?否->获取它->将脚本添加到缓存->渲染

我考虑过缓存脚本src和一个加载布尔值,但是这意味着设置超时处理,在我看来它变得非常复杂。

那么,为了仅一次加载外部脚本但确保正确加载它,更新钩子的最佳方法是什么?

1 个答案:

答案 0 :(得分:1)

useScript模块中,我们将需要跟踪脚本的加载状态。

因此,我们现在需要保留一个代表加载状态的对象,而不是cachedScripts是简单的字符串数组。

useScript的修改后的实现将解决以下问题:

import { useState, useEffect } from 'react';

let cachedScripts = {};
export function useScript(src) {
  // Keeping track of script loaded and error state
  const [state, setState] = useState({
    loaded: false,
    error: false
  });

  useEffect(
    () => {
      const onScriptLoad = () => {
        cachedScripts[src].loaded = true;
        setState({
          loaded: true,
          error: false
        });
      };

      const onScriptError = () => {
        // Remove it from cache, so that it can be re-attempted if someone tries to load it again
        delete cachedScripts[src];

        setState({
          loaded: true,
          error: true
        });
      };

      let scriptLoader = cachedScripts[src];
      if(scriptLoader) { // Loading was attempted earlier
        if(scriptLoader.loaded) { // Script was successfully loaded
          setState({
            loaded: true,
            error: false
          });
        } else { //Script is still loading
          let script = scriptLoader.script;
          script.addEventListener('load', onScriptLoad);
          script.addEventListener('error', onScriptError);
          return () => {
            script.removeEventListener('load', onScriptLoad);
            script.removeEventListener('error', onScriptError);
          };
        }
      } else {
        // Create script
        let script = document.createElement('script');
        script.src = src;
        script.async = true;

        // Script event listener callbacks for load and error


        script.addEventListener('load', onScriptLoad);
        script.addEventListener('error', onScriptError);

        // Add script to document body
        document.body.appendChild(script);

        cachedScripts[src] = {loaded:false, script};

        // Remove event listeners on cleanup
        return () => {
          script.removeEventListener('load', onScriptLoad);
          script.removeEventListener('error', onScriptError);
        };
      }
    },
    [src] // Only re-run effect if script src changes
  );

  return [state.loaded, state.error];
}

编辑:

转到useHooks的GitHub页面以提出这一改进,并发现其中一些已经发布了类似的修复程序:

https://gist.github.com/gragland/929e42759c0051ff596bc961fb13cd93#gistcomment-2975113