<script> 标签只对页面刷新有效(单页应用)

<script> tag only works on page refresh (Single Page Application)

我正在使用纯 Javascript + express-JS 构建单页应用程序。我有两个视图 linked 到两个锚点 link,这样我就可以在视图之间切换。问题是,其中一个视图(默认视图)显示了一个 d3 图表,它只在网站最初加载或页面刷新时显示它。如果我只是单击它自己的锚点 link,理论上它应该什么也不做,仍然显示图表,但图表消失了,但与该视图对应的所有其他 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 锚点 links 在基础 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>
        `;
    }
}

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

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

根据您的评论,文件 d3chart.js 将 运行 创建图表的代码并将其应用于 div 名称 class [=16] =].让您的代码正常工作的快速而肮脏的方法是执行以下操作:

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>
        `;
    }
}

好的,所以 Reddit 上的一个人帮助我解决了这个问题,基本上我要做的就是在 view1.js 文件的 class 中创建一个方法并放置我的 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.