javascript - 使用 React 钩子在 Next.js 中只加载一次脚本?
问题描述
我想在我的博客中使用话语。它应该只在有人滚动到帖子底部时加载,所以我使用react-intersection-observer
它。我做了以下钩子。
使用话语.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])
}
话语.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>
)
}
我next-themes
用来切换DarkMode
。我还向 utterances iframe 发送了一个请求,因此它不会两次加载脚本,但它仍然通过卸载和安装组件来加载它两次。
DarkMode.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>
</>
)
}
如何确保它只请求一次脚本?现在每次我切换时都会调用它。它安装和卸载组件,因为在加载脚本时我有一段时间什么都看不到。
GitHub 回购 -> https://github.com/deadcoder0904/next-utterances-script-loads-twice/tree/master
Stackblitz 演示 -> https://stackblitz.com/edit/github-6frqvs?file=pages%2Findex.tsx
在新窗口中打开以查看暗模式,因为 Stackblitz 目前不支持 Tailwind 暗模式。检查网络选项卡以查看它每次都发送请求,即使您还可以看到评论组件安装和卸载。
我如何只加载一次脚本?
解决方案
尝试这样的事情:
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])
所以这里基本上发生了什么。看起来当我们在加载后附加 utterances 脚本时,它将自己替换为:
<div class="utterances" style="height: 267px;">
<iframe class="utterances-frame" title="Comments" scrolling="no" src="https://utteranc.es/..." loading="lazy"></iframe>
</div>
为了避免不必要的附加,我们检查页面中是否已经有一些<iframe class="utterances-frame"
,并且只有在没有附加脚本的情况下。
推荐阅读
- javascript - 我收到一个错误:对象作为 React 子项无效(找到:[object Promise])
- python - Python:如何检查列表是否包含数值并返回布尔值?
- react-native - 如何自动化 Expo web index.html 样式
- flutter - 当参数不是 String Flutter 时,Uri 声明失败
- php - 使标头静音并访问日志输出,但仍显示来自 php 内置服务器的 error_log()/syslog() 输出
- javascript - 如何在innerHTML中使用onClick函数
- angular - 从多个容器中获取价值 - Mat Table
- c++ - 如何从世界空间渲染到相机空间?
- javascript - 如何在 html 音频标签中自动播放 saavan 链接?
- azure-data-factory - 如何在 Azure 数据工厂映射数据流中将 UST 转换为 EST?