【问题标题】:<script> tag only works on page refresh (Single Page Application)<script> 标签仅适用于页面刷新(单页应用程序)
【发布时间】:2022-02-13 23:06:50
【问题描述】:

我正在使用纯 Javascript + express-JS 构建一个单页应用程序。我有两个链接到两个锚链接的视图,以便我可以在视图之间切换。问题是,其中一个视图(默认视图)显示 d3 图表,并且仅在站点最初加载或页面刷新时才显示它。如果我只是单击它自己的锚链接,理论上它应该什么都不做并且仍然显示图表,但图表消失了,但与该视图对应的所有其他 html 元素都保留了。我确保在我的所有内容都被加载后,该文件的脚本标记被加载到基本 html 文件中,但问题仍然存在。有人能指出这里出了什么问题吗?

var tag = document.createElement("script");

function scriptLoad() {
    tag.src = "static/js/d3chart.js";
    tag.id = "d3chart"
    document.getElementsByTagName('head')[0].appendChild(tag);
};

document.addEventListener("DOMContentLoaded", () => {
    document.body.addEventListener("click", e => {
        if (e.target.matches("[data-link]")) {
            e.preventDefault();
            navigateTo(e.target.href);
        }
        scriptLoad();
    });
    router();
});

基本 html 文件中的 HTML 锚链接

<a href="/" data-link>View 1</a>
<a href="/view2" data-link>View 2</a>

完整代码

import view1 from "./views/view1.js"
import view2 from "./views/view2.js"

const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");

const getParams = match => {
    const values = match.result.slice(1);
    const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);

    return Object.fromEntries(keys.map((key, i) => {
        return [key, values[i]];
    }));
};

const navigateTo = url => {
    history.pushState(null, null, url);
    router();
};

const router = async () => {
    const routes = [
        { path: "/", view: view1 },
        { path: "/view2", view: view2 }
    ];

    // Test each route for potential match
    const potentialMatches = routes.map(route => {
        return {
            route: route,
            result: location.pathname.match(pathToRegex(route.path))
        };
    });

    let match = potentialMatches.find(potentialMatch => potentialMatch.result !== null);

    if (!match) {
        match = {
            route: routes[0],
            result: [location.pathname]
        };
    }

    const view = new match.route.view(getParams(match));

    document.querySelector("#app").innerHTML = await view.getHtml();

};

window.addEventListener("popstate", router);

var tag = document.createElement("script");

function scriptLoad () {
    tag.src = "static/js/d3chart.js";
    tag.id = "d3chart"
    document.getElementsByTagName('body')[0].appendChild(tag);
}

document.addEventListener("DOMContentLoaded", () => {
    document.body.addEventListener("click", e => {
        if (e.target.matches("[data-link]")) {
            e.preventDefault();
            navigateTo(e.target.href);
        }
        scriptLoad();
    });
    router();
});

view1.js 文件 - 开始时的默认视图(图表视图)

import AbstracView from "./AbstractView.js"

export default class extends AbstracView {

    constructor() {
        super();
        this.setTitle("D3 Chart - View1");
    }
   
    async getHtml() {
        return `
            <h1>D3 Chart</h1>
            <div class="chart-area"></div>
        `;
    }
}

【问题讨论】:

  • 您正在单击侦听器内部执行scriptLoad() 函数,它应该在外部完成,否则,每次单击时您都在重新加载同一个文件,这似乎是您的问题。
  • 我把它放在外面,它不起作用。我也在使用 History API,这是否会导致这种情况?此外,最初,当我在基本 html 文件中包含脚本标记时,图表甚至没有在启动或刷新时加载。一旦我确定在加载内容后插入了脚本标签,图表就会显示出来,但是当我点击它自己的锚链接时它就会消失
  • 我认为它与 History API 无关,但这可能取决于您在 navigateTo 函数中所做的事情。可以肯定的是,您将scriptLoad 移到了click 侦听器之外,但您将它保留在DOMContentLoaded 侦听器中,对吗?如果是这样,请向我们展示您的其余代码,例如模板和 navigateTo 函数的内容。
  • 我已经用其余的代码更新了我的问题。让我知道您是否需要查看其他内容。是的,我在DOMContentLoaded 侦听器内但在click 侦听器之外调用了scriptLoad(); 函数。我在router(); 函数调用的正上方调用它。
  • script 没有直接加载到 HTML 文件中是否有任何原因?

标签: javascript html express single-page-application


【解决方案1】:

好的,Reddit 上的一个人帮助我解决了这个问题,基本上我要做的就是在 view1.js 文件的类中创建一个方法,并将我的 d3 代码放在该方法中,就像这样

import AbstracView from "./AbstractView.js"

export default class extends AbstracView {

    constructor() {
        super();
        this.setTitle("D3 Chart - View1");
    }

    loadChart() {
        // My D3 code here
    }
   
    async getHtml() {
        return `
            <h1>D3 Chart</h1>
            <div class="chart-area"></div>
        `;
    }
}

后来,我只需要调用它,就像在我的路由器文件中一样

const view = new match.route.view(getParams(match));

document.querySelector("#app").innerHTML = await view.getHtml();

view.loadChart?.(); // runs the method after the HTML corresponding to the view has been added to the base HTML.

【讨论】:

    【解决方案2】:

    您必须将您的函数内联或在单击“a”html 标记时添加新的事件侦听器。

    <a onclick="scriptLoad()" href="/" data-link>View 1</a>
    <a onclick="scriptLoad()" href="/view2" data-link>View 2</a>

    【讨论】:

    • 想解释一下你的答案吗?这对我来说并没有真正遵循。
    • 我试过了,还是不行。我确实有一个事件侦听器,如果我在 IF 语句中调用scriptLoad();,您在我的代码中看到,图表不会在开始时加载,但是当我单击锚标记时它会加载,但只有一次。如果我再做一次,它不会。要让它再次工作,我需要刷新页面,单击锚标记,然后图表再次显示。
    【解决方案3】:

    根据您的 cmets,文件 d3chart.js 将运行代码以创建图表并将其应用于类名为 chart-area 的 div。让您的代码工作的快速而肮脏的方法是执行以下操作:

    document.addEventListener("DOMContentLoaded", () => {
        document.body.addEventListener("click", e => {
            if (e.target.matches("[data-link]")) {
                e.preventDefault();
                navigateTo(e.target.href);
            }
            // scriptLoad(); <- remove this
        });
        router();
    });
    

    view1.js

    import AbstracView from "./AbstractView.js"
    
    export default class extends AbstracView {
    
        constructor() {
            super();
            this.setTitle("D3 Chart - View1");
        }
       
        async getHtml() {
            return `
                <h1>D3 Chart</h1>
                <div class="chart-area"></div>
                <script id="d3chart" src="static/js/d3chart.js"></script>
            `;
        }
    }
    

    这将解决您最初的问题,但会创建一个新问题,正如您可能已经发现的那样,每次加载该视图时它都会发出一个 HTTP 请求!这可能是您想要的,但如果不是,您需要稍微更改代码的结构。文件d3chart.js 的代码需要公开loadChart 函数(或任何您想调用的函数),然后您将加载该脚本一次并在需要更新视图时调用loadChart 函数。

    d3chart.js

    function loadChart() {
      // Your code to create the chart and apply it to the an element.
    }
    

    模板

    <script id="d3chart" src="static/js/d3chart.js"></script>
    
    <a href="/" data-link>View 1</a>
    <a href="/view2" data-link>View 2</a>
    
    <div id="app"></div>
    

    view1.js

    import AbstracView from "./AbstractView.js"
    
    export default class extends AbstracView {
    
        constructor() {
            super();
            this.setTitle("D3 Chart - View1");
        }
       
        async getHtml() {
            return `
                <h1>D3 Chart</h1>
                <div class="chart-area"></div>
                <script>loadChart();</script>
            `;
        }
    }
    

    【讨论】:

    • 图表在您提到的两种解决方案中都完全消失了。在您的第二个解决方案中,您要求我将 loadChart(); 放在 getHtml()chart-area div 下,我检查了控制台并且 d3 脚本没有加载,所以我将 constructor() 移动到底部,在下面getHtml(); 并在其中调用 loadChart();。脚本已加载,但图表未出现。不过,我会继续尝试,用你的解决方案,在这里和那里做一些改变,看看我是否能纠正这个问题。
    • 您是否还更改了脚本和模板代码以匹配我的解决方案中的代码?不知道大家有没有看到,但是我在模板中添加了脚本的加载。
    • 是的,我注意到并添加了它。
    • 好吧,那我没办法了。我会仔细看看家。如果您在此期间发现,请告诉我们:)
    • 好的,非常感谢您抽出宝贵的时间。如果我弄明白了,我会更新问题并告诉你。
    猜你喜欢
    • 2021-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-21
    • 2011-03-15
    相关资源
    最近更新 更多