You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:16:06