如何阻止第三方 Javascript 加载清理功能?

How can you stop third party Javascript from loading in a cleanup function?

我一直在尝试在我的 Gatsby 网站上加载 Marketo 表单,并且我在 Stack Overflow 上找到了一些关于如何执行此操作的有用提示,但在所有示例中,都存在一个我无法解决的问题'好像解决不了。

组件如下:

import React from 'react'
import useMarketo from '../../hooks/useMarketo'

export default function MarketoForm({ formId }) {
    const baseUrl = '//XXX-XXX.marketo.com'
    const munchkinId = 'XXX-XXX-XXX'

    useMarketo(baseUrl, munchkinId, formId)

    return <form id={`mktoForm_${formId}`} />
}

这里是钩子:

import { useState, useEffect } from 'react'

function appendScript(baseUrl, setScriptLoaded) {
    if (window.MktoForms2) return setScriptLoaded(true)

    const script = document.createElement('script')
    script.src = `${baseUrl}/js/forms2/js/forms2.min.js`
    script.onload = () => (window.MktoForms2 ? setScriptLoaded(true) : null)
    document.body.appendChild(script)
}

export default function useMarketo(baseUrl, munchkinId, formId) {
    const [formIsLoaded, setFormIsLoaded] = useState(false)
    const [scriptLoaded, setScriptLoaded] = useState(false)

    useEffect(() => {
        if (scriptLoaded && !formIsLoaded) {
            const windowGlobal = typeof window !== 'undefined' && window
            windowGlobal.MktoForms2.loadForm(baseUrl, munchkinId, formId).whenRendered(setFormIsLoaded(true))
            return
        }
        appendScript(baseUrl, setScriptLoaded)
    }, [formIsLoaded, scriptLoaded, baseUrl, munchkinId, formId])
}

问题是,如果我离开页面,并且 return 很快,表单将加载多次。

Marketo Form appears many times

我不确定如何在 useEffect 函数中清除它。问题是 loadForm 函数还没有 returned,当我离开页面然后返回时,它实际上得到 return,然后发送出去另一个请求。

我似乎找不到使用状态或清理功能解决此问题的方法。 AbortController() 看起来很有前途,但我也找不到解决方案。不过,这似乎应该很容易解决。我不是第一个遇到此问题的人,因为它以前曾出现在此处以及 Marketo 论坛上 - 但我尚未找到合适的解决方案。

提前感谢您的任何建议!

const scripts = {};
function importScript(url) {
   if (!scripts[url]) {
      const script = document.createElement('script');
      scripts[url] = {
         loaded: false,
         fail: false,
         resolves: [],
         rejects: [],
      }    
      script.onload = ()=> {
         scripts[url].loaded = true;
         scripts[url].resolves.forEach(r=> r());
      }
      script.onerror = (err)=> {
          scripts[url].fail = true;
          scripts[url].rejects.forEach(r=> r());
      }
   }
   if (scripts[url].fail) return Promise.reject();
   if (scripts[url].loaded) return Promise.resolve();
   return new Promise((r, j)=> {
      scripts[url].resolves.push(r);
      scripts[url].rejects.push(j);
   });
}

export default function useMarketo(baseUrl, munchkinId, formId) {

    useEffect(() => {
       importScript(baseUrl).then(()=> {
          windowGlobal.MktoForms2.loadForm(baseUrl, munchkinId, formId);
       }, console.error);
    }, [formIsLoaded, scriptLoaded, baseUrl, munchkinId, formId])
}

MkToForms2 支持 4th parameter 回调,在 loadForm 完成后随表单一起调用。我的建议是使用效果清理功能来跟踪何时卸载 useMarketo 挂钩,如果卸载它则使用 loadForm 回调来删除表单。

        windowGlobal.MktoForms2.loadForm(
          baseUrl,
          munchkinId,
          formId,
          (form) => {
            const $form = form.getFormElem();
            // if this was cancelled do not show it and remove it
            if (cancelled) {
              $form.remove();
              return;
            }
            // if not cancelled this is still valid, lets show it and update form loading status
            setFormIsLoaded(true);
          }
        );

此外,通过使 appendScript return 成为一个承诺(不需要),您可以清理一些代码并删除一些您可能不需要的对 loadForm 效果的额外依赖需要。以下是我的推介。

function appendScript(baseUrl) {
  return new Promise((resolve, reject) => {
    const existingScript = document.body.getElementById(baseUrl);

    if (existingScript) {
      return resolve(true);
    }

    if (window.MktoForms2) {
      resolve(true);
    }

    const script = document.createElement("script");
    script.src = `${baseUrl}/js/forms2/js/forms2.min.js`;
    script.onload = () => resolve(window.MktoForms2);
    script.onerror = reject;
    document.body.appendChild(script);
  });
}

export default function useMarketo(baseUrl, munchkinId, formId) {
  const [formIsLoaded, setFormIsLoaded] = useState(false);
  useEffect(() => {
    let cancelled = false;
    const loadForm = async () => {
      const scriptLoaded = await appendScript(baseUrl);
      if (scriptLoaded) {
        const windowGlobal = typeof window !== "undefined" && window;
        windowGlobal.MktoForms2.loadForm(
          baseUrl,
          munchkinId,
          formId,
          (form) => {
            const $form = form.getFormElem();
            // if this was cancelled do not show it and remove it
            if (cancelled) {
              $form.remove();
              return;
            }
            // if not cancelled this is still valid, lets show it and update form loading status
            setFormIsLoaded(true);
          }
        );
      }
    };
    loadForm();
    return () => {
      cancelled = true;
    };
  }, [baseUrl, munchkinId, formId]);
}

我不确定表格删除是否会显示添加表格然后删除表格的闪光,但如果确实如此,那么您应该添加默认值 css class 以隐藏所有添加的表格,如果取消则不删除表格,如果未取消则显示它们:

  if (!cancelled) {
     $form.show();
  }

这不会阻止 loadForm 在来回导航时执行,但它应该确保只允许当前 loadForm 显示和保留页面上的表单。