简体   繁体   English

使用 React 钩子在 Next.js 中只加载一次脚本?

[英]Load Script only once in Next.js using React hooks?

I want to use Utterances for my blog.我想在我的博客中使用Utterances It should only load when someone scrolled to the bottom of the post so I use react-intersection-observer for that.它应该只在有人滚动到帖子底部时加载,所以我使用react-intersection-observer I have made the following hook.我做了以下钩子。

useUtterances.ts useUtterances.ts

import React from 'react'
import { useTheme } from 'next-themes'

import { siteMetadata } from '@/_data/index'

export const useUtterances = (commentNodeId: string) => {
    const config = siteMetadata.comment.utterancesConfig
    // username/repo format
    const REPO_NAME = config.repo as string

    const { theme, resolvedTheme } = useTheme()
    const utterancesTheme =
        theme === 'light' || resolvedTheme === 'light' ? config.theme : config.darkTheme

    React.useEffect(() => {
        const scriptParentNode = document.getElementById(commentNodeId)
        if (!scriptParentNode) return

        // docs - https://utteranc.es/
        const script = document.createElement('script')
        script.src = 'https://utteranc.es/client.js'
        script.async = true
        script.setAttribute('repo', REPO_NAME)
        script.setAttribute('issue-term', 'pathname')
        script.setAttribute('label', 'comment :speech_balloon:')
        script.setAttribute('theme', utterancesTheme)
        script.setAttribute('crossorigin', 'anonymous')

        scriptParentNode.appendChild(script)

        return () => {
            // cleanup - remove the older script with previous theme
            scriptParentNode.removeChild(scriptParentNode.firstChild as Node)
        }
    }, [REPO_NAME, commentNodeId, utterancesTheme])
}

Utterances.tsx话语.tsx

import React from 'react'
import { useInView } from 'react-intersection-observer'

import { useUtterances } from '@/hooks/useUtterances'

export const Utterances = () => {
    const COMMENTS_NODE_ID = 'comments'
    const { ref, inView } = useInView({ threshold: 0, triggerOnce: true })

    useUtterances(inView ? COMMENTS_NODE_ID : '')

    return (
        <div ref={ref} className="min-h-[400px]">
            {inView ? <div id={COMMENTS_NODE_ID} /> : null}
        </div>
    )
}

I use next-themes to toggle DarkMode .我使用next-themes来切换DarkMode I also send a request to utterances iframe so it doesn't load script twice but it still loads it twice by unmounting & mounting the component.我还向 utterances iframe 发送了一个请求,因此它不会两次加载脚本,但它仍然通过卸载和安装组件来加载它两次。

DarkMode.tsx暗模式.tsx

import React from 'react'
import { useTheme } from 'next-themes'
import { MoonIcon, SunIcon } from '@heroicons/react/solid'

import { useHasMounted } from '@/hooks/index'
import { siteMetadata } from '@/_data/index'

export const DarkMode = () => {
    const { resolvedTheme, setTheme } = useTheme()
    const hasMounted = useHasMounted()

    const label = resolvedTheme === 'dark' ? 'Activate light mode' : 'Activate dark mode'

    if (!hasMounted) return null

    const toggleTheme = () => {
        const newTheme = resolvedTheme === 'light' ? 'dark' : 'light'
        setTheme(newTheme)

        // for utterances
        const frame = document.getElementsByClassName('utterances-frame')[0] as HTMLIFrameElement
        if (frame?.contentWindow) {
            const utterancesTheme =
                resolvedTheme === 'light'
                    ? siteMetadata.comment.utterancesConfig.darkTheme
                    : siteMetadata.comment.utterancesConfig.theme
            frame.contentWindow.postMessage({ type: 'set-theme', theme: utterancesTheme }, '*')
        }
    }

    return (
        <>
            <button
                className="focus:outline-none"
                type="button"
                title={label}
                aria-label={label}
                onClick={toggleTheme}
            >
                {resolvedTheme === 'light' ? (
                    <MoonIcon className="w-8 h-8" />
                ) : (
                    <SunIcon className="w-8 h-8" />
                )}
            </button>
        </>
    )
}

How do I make sure it only requests script once?我如何确保它只请求脚本一次? It now calls it everytime I toggle.现在每次我切换时它都会调用它。 It mounts and unmounts the component as I see nothing for a while when the script is loading.它安装和卸载组件,因为我在加载脚本时暂时看不到任何东西。

GitHub repo -> https://github.com/deadcoder0904/next-utterances-script-loads-twice/tree/master GitHub 存储库 -> https://github.com/deadcoder0904/next-utterances-script-loads-twice/tree/master

Stackblitz demo -> https://stackblitz.com/edit/github-6frqvs?file=pages%2Findex.tsx Stackblitz 演示 -> https://stackblitz.com/edit/github-6frqvs?file=pages%2Findex.tsx

Open in New Window to see the Dark Mode as Stackblitz currently doesn't support Tailwind Dark Mode.在新窗口中打开以查看暗模式,因为 Stackblitz 目前不支持 Tailwind 暗模式。 Check the Network Tab to see it sends request everytime even though you can also see the Comments component mounting & unmounting.检查网络选项卡以查看它每次发送请求,即使您还可以看到注释组件安装和卸载。

How do I only load script once?如何只加载一次脚本?

Try something like this:尝试这样的事情:

React.useEffect(() => {
    const scriptParentNode = document.getElementById(commentNodeId)
    const utterancesFrame = document.getElementsByClassName('utterances-frame')[0]
    if (!scriptParentNode || utterancesFrame) return

    // docs - https://utteranc.es/
    const script = document.createElement('script')
    script.src = 'https://utteranc.es/client.js'
    script.async = true
    script.setAttribute('repo', REPO_NAME)
    script.setAttribute('issue-term', 'pathname')
    script.setAttribute('label', 'comment :speech_balloon:')
    script.setAttribute('theme', utterancesTheme)
    script.setAttribute('crossorigin', 'anonymous')

    scriptParentNode.appendChild(script)
}, [REPO_NAME, commentNodeId, utterancesTheme])

So what basically is happen here.所以这里基本上发生了什么。 It is looks like when we append the utterances script after loading it replaces himself with:看起来当我们在加载后附加 utterances 脚本时,它自己替换为:

<div class="utterances" style="height: 267px;">
    <iframe class="utterances-frame" title="Comments" scrolling="no" src="https://utteranc.es/..." loading="lazy"></iframe>
</div>

To avoid unnecessary append we check if there are some <iframe class="utterances-frame" already in the page and only if not append the script.为了避免不必要的追加,我们检查页面中是否已经存在一些<iframe class="utterances-frame"并且仅当没有追加脚本时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Next.js 内联脚本只加载一次 - Next.js inline script only loads once 如何使用 React 钩子或其他方式(React/Next js)使组件(弹出模式)仅出现一次 - How to make a component (popup modal) appear only once using React hooks or otherwise (React/Next js) 为什么 css 不能在 React 中加载,而只能在 Next.js 中加载? - Why css can not load in React but only in Next.js? 更新数组 useState 反应钩子(Next.js) - updating array useState react hooks (Next.js) Next.js 未处理的运行时错误“无法加载脚本:/_next/static/chunks/...”仅在开发模式下触发 - Next.js Unhandled Runtime Error "Failed to load script: /_next/static/chunks/..." only triggered in dev mode 将 react redux 与 next.js 结合使用 - Using react redux with next.js 使用带有 next.js 的反应路由器 - using react router with next.js 我想使用 react 和 next.js 从 object 到我的数组 map,这是一个使用钩子的 function 组件 - I want to map my array from an object using react and next.js, it's a function component using hooks 如何在广泛使用 React hooks 的同时利用 Next.js 服务器端渲染? - How do I take advantage of Next.js server-side rendering while using React hooks extensively? 在Next JS中有条件地使用React钩子 - Using React hooks conditionally in Next JS
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM