React Router中外联JS代码仅首次生效问题求助
解决方案:React Router切换后文字轮换效果失效的问题
我来帮你解决这个问题——你的文字轮换效果只在About页刷新时生效,路由切换回来就失效,核心原因很简单:你的script.js里的初始化逻辑绑定在window.onload事件上,而这个事件只会在整个页面首次加载完成时触发一次。React Router是单页应用,切换路由并不会重新加载页面,只是组件的挂载/卸载,所以切回About页时window.onload不会再次执行,自然就没初始化文字轮换。
下面给你两种可靠的解决方案,从修改现有逻辑到组件化重构都有:
方案一:适配现有脚本,绑定组件生命周期
这种方式不需要大改你现有的script.js,只需要把它改成可调用的函数,然后在About组件的挂载钩子中执行,卸载时清理定时器防止内存泄漏。
步骤1:修改script.js,导出初始化函数并添加清理逻辑
export function initTxtRotate() { var TxtRotate = function(el, toRotate, period) { this.toRotate = toRotate; this.el = el; this.loopNum = 0; this.period = parseInt(period, 10) || 2000; this.txt = ''; this.isDeleting = false; this.timer = null; // 保存定时器引用,用于后续清理 this.tick(); }; TxtRotate.prototype.tick = function() { var i = this.loopNum % this.toRotate.length; var fullTxt = this.toRotate[i]; if (this.isDeleting) { this.txt = fullTxt.substring(0, this.txt.length - 1); } else { this.txt = fullTxt.substring(0, this.txt.length + 1); } this.el.innerHTML = '<span class="wrap">'+this.txt+'</span>'; var that = this; var delta = 300 - Math.random() * 100; if (this.isDeleting) { delta /= 2; } if (!this.isDeleting && this.txt === fullTxt) { delta = this.period; this.isDeleting = true; } else if (this.isDeleting && this.txt === '') { this.isDeleting = false; this.loopNum++; delta = 500; } this.timer = setTimeout(function() { that.tick(); }, delta); }; // 添加清理方法,用于组件卸载时清除定时器 TxtRotate.prototype.clear = function() { if (this.timer) { clearTimeout(this.timer); } }; var elements = document.getElementsByClassName('txt-rotate'); var instances = []; for (var i=0; i<elements.length; i++) { var toRotate = elements[i].getAttribute('data-rotate'); var period = elements[i].getAttribute('data-period'); if (toRotate) { var instance = new TxtRotate(elements[i], JSON.parse(toRotate), period); instances.push(instance); } } // 返回实例集合,方便组件卸载时清理 return instances; }
步骤2:修改About.js,在生命周期中调用初始化和清理
import React from 'react'; import { initTxtRotate } from "../js/script"; export default class About extends React.Component { // 保存文字轮换实例,用于卸载时清理 txtRotateInstances = []; componentDidMount() { document.title = 'About MA2WEB - Full-Stack Web Developer'; // 组件挂载时初始化文字轮换 this.txtRotateInstances = initTxtRotate(); } componentWillUnmount() { // 组件卸载时清理所有定时器,避免内存泄漏 this.txtRotateInstances.forEach(instance => instance.clear()); } render() { return ( <section className="main-section about animate"> <article className="about-contents-ma2web"> <h1 className="typing-ma2web">I have experience in <span className="txt-rotate" data-period="2000" data-rotate='[ "JavaScript", "Reactjs", "Nodejs", "GSAP", "PHP", "MySQL", "Wordpress", "SEO" ]'> </span></h1> <p> I can develop both client and server side languages. </p> </article> </section> ); } }
方案二:封装成React组件(更推荐)
这种方式完全遵循React的组件化思想,抛弃直接操作DOM的方式,用React状态管理实现文字轮换,更可靠也更易维护。
步骤1:创建TxtRotateComponent.js组件
import React, { useEffect, useRef, useState } from 'react'; const TxtRotateComponent = ({ words, period = 2000 }) => { const [currentText, setCurrentText] = useState(''); const [isDeleting, setIsDeleting] = useState(false); const [loopNum, setLoopNum] = useState(0); const timerRef = useRef(null); useEffect(() => { const tick = () => { const currentWord = words[loopNum % words.length]; let updatedText = currentText; let delta = 300 - Math.random() * 100; if (isDeleting) { updatedText = currentWord.substring(0, currentText.length - 1); delta /= 2; } else { updatedText = currentWord.substring(0, currentText.length + 1); } setCurrentText(updatedText); // 切换状态的逻辑 if (!isDeleting && updatedText === currentWord) { delta = period; setIsDeleting(true); } else if (isDeleting && updatedText === '') { setIsDeleting(false); setLoopNum(prev => prev + 1); delta = 500; } timerRef.current = setTimeout(tick, delta); }; // 首次触发 timerRef.current = setTimeout(tick, 500); // 组件卸载时清理定时器 return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, [currentText, isDeleting, loopNum, words, period]); return <span className="txt-rotate"><span className="wrap">{currentText}</span></span>; }; export default TxtRotateComponent;
步骤2:在About.js中使用这个组件
import React from 'react'; import TxtRotateComponent from "./TxtRotateComponent"; export default class About extends React.Component { componentDidMount() { document.title = 'About MA2WEB - Full-Stack Web Developer'; } render() { const skills = ["JavaScript", "Reactjs", "Nodejs", "GSAP", "PHP", "MySQL", "Wordpress", "SEO"]; return ( <section className="main-section about animate"> <article className="about-contents-ma2web"> <h1 className="typing-ma2web">I have experience in <TxtRotateComponent words={skills} period={2000} /></h1> <p> I can develop both client and server side languages. </p> </article> </section> ); } }
为什么之前的方法没用?
- 直接在
App.js引入script.js:只会在App首次加载时执行一次window.onload,后续路由切换不会触发。 - 单纯在
componentDidMount里调用函数但没处理清理:要么没正确绑定初始化时机,要么没清理定时器导致内存泄漏,而且直接操作DOM在React中不是最佳实践。
两种方案都能解决你的问题,方案二更符合React的开发模式,推荐使用。
内容的提问来源于stack exchange,提问作者Amin Azimi
相关产品推荐
相关产品推荐

