router实现原理

目录

  • about.html
  • detail.html
  • home.html
  • index.html
  • router.js
index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
</head>

<body>
    <div class="product-item">测试的产品</div>
    <div class="flex">
        <ul class="menu-x">
            <c-link to="/" class="c-link">首页</c-link>
            <c-link to="/about" class="c-link">关于</c-link>
        </ul>
    </div>
    <div>
        <c-router>
            <c-route path="/" component="home" default></c-route>
            <c-route path="/detail/:id" component="detail"></c-route>
            <c-route path="/about" component="about"></c-route>
        </c-router>
    </div>
</body>
<script src="./router.js"></script>

</html>
router.js
const oriPushState = history.pushState;

// 重写pushState
history.pushState = function (state, title, url) {
    // 触发原事件
    oriPushState.apply(history, [state, title, url]);
    // 自定义事件
    var event = new CustomEvent("c-popstate", {
        detail: {
            state,
            title,
            url
        }
    });
    window.dispatchEvent(event);
}

// <c-link to="/" class="c-link">首页</c-link>
class CustomLink extends HTMLElement {
    connectedCallback() {
        this.addEventListener("click", ev => {
            ev.preventDefault();
            const to = this.getAttribute("to");

            // 更新浏览历史记录
            history.pushState("", "", to);
        })
    }
}
window.customElements.define("c-link", CustomLink);

// 优先于c-router注册
// <c-toute path="/" component="home" default></c-toute>
class CustomRoute extends HTMLElement {
    #data = null;
    getData() {
        return {
            default: this.hasAttribute("default"),
            path: this.getAttribute("path"),
            component: this.getAttribute("component")
        }
    }
}
window.customElements.define("c-route", CustomRoute);

// 容器组件
class CustomComponent extends HTMLElement {
    async connectedCallback() {
    
        // 获取组件的path,即html的路径
        const strPath = this.getAttribute("path");
        // 加载html
        const cInfos = await loadComponent(strPath);
        const shadow = this.attachShadow({ mode: "closed" });
        // 添加html对应的内容
        this.#addElement(shadow, cInfos);
    }
    #addElement(shadow, info) {
        // 添加模板内容
        if (info.template) {
            shadow.appendChild(info.template.content.cloneNode(true));
        }
        // 添加脚本
        if (info.script) {
            // 防止全局污染,并获得根节点
            var fun = new Function(`${info.script.textContent}`);
            // 绑定脚本的this为当前的影子根节点
            fun.bind(shadow)();
        }
        // 添加样式
        if (info.style) {
            shadow.appendChild(info.style);
        }
    }
}
window.customElements.define("c-component", CustomComponent);

// <c-router></c-router>
class CustomRouter extends HTMLElement {
    #routes
    connectedCallback() {
        const routeNodes = this.querySelectorAll("c-route");


        // 获取子节点的路由信息
        this.#routes = Array.from(routeNodes).map(node => node.getData());
        // 查找默认的路由
        const defaultRoute = this.#routes.find(r => r.default) || this.#routes[0];
        // 渲染对应的路由
        this.#onRenderRoute(defaultRoute);
        // 监听路由变化
        this.#listenerHistory();
    }

    // 渲染路由对应的内容
    #onRenderRoute(route) {
        var el = document.createElement("c-component");
        el.setAttribute("path", `/${route.component}.html`);
        el.id = "_route_";
        this.append(el);
    }

    // 卸载路由清理工作
    #onUploadRoute(route) {
        this.removeChild(this.querySelector("#_route_"));
    }

    // 监听路由变化
    #listenerHistory() {
        // 导航的路由切换
        window.addEventListener("popstate", ev => {
       
            const url = location.pathname.endsWith(".html") ? "/" : location.pathname;
            const route = this.#getRoute(this.#routes, url);
            console.log(route,'route')
            this.#onUploadRoute();
            this.#onRenderRoute(route);
        });
        // pushStat或replaceSate
        window.addEventListener("c-popstate", ev => {

            const detail = ev.detail;

            console.log(detail,'detai66l')

         
   
            const route = this.#getRoute(this.#routes, detail.url);
            this.#onUploadRoute();
            this.#onRenderRoute(route);
        })
    }

    // 路由查找
    #getRoute(routes, url) {
        console.log(routes,'routes')
        return routes.find(function (r) {
            const path = r.path;
            const strPaths = path.split('/');
            const strUrlPaths = url.split("/");

            let match = true;
            for (let i = 0; i < strPaths.length; i++) {
                if (strPaths[i].startsWith(":")) {
                    continue;
                }
                match = strPaths[i] === strUrlPaths[i];
                if (!match) {
                    break;
                }
            }
             console.log(match,'matchmatch')
            return match;
        })
    }
}
window.customElements.define("c-router", CustomRouter);

// 动态加载组件并解析
async function loadComponent(path, name) {
    this.caches = this.caches || {};
    // 缓存存在,直接返回
    if (!!this.caches[path]) {
        return this.caches[path];
    }
    const res = await fetch(path).then(res => res.text());
    // 利用DOMParser校验
    const parser = new DOMParser();
    const doc = parser.parseFromString(res, "text/html");
    // 解析模板,脚本,样式
    const template = doc.querySelector("template");
    const script = doc.querySelector("script");
    const style = doc.querySelector("style");
    // 缓存内容
    this.caches[path] = {
        template,
        script,
        style
    }
    return this.caches[path];
}
home.html

<template>
    <div>商品清单</div>
    <div id="product-list">
        <div>
            <a data-id="10" class="product-item c-link">香蕉</a>
        </div>
        <div>
            <a data-id="11" class="product-item c-link">苹果</a>
        </div>
        <div>
            <a data-id="12" class="product-item c-link">葡萄</a>
        </div>
    </div>
</template>

<script>
    let container = this.querySelector("#product-list");
    // 触发历史更新
    // 事件代理
    container.addEventListener("click", function (ev) {
        console.log("item clicked");
        if (ev.target.classList.contains("product-item")) {
            const id = +ev.target.dataset.id;
            history.pushState({
                    id
            }, "", `/detail/${id}`)
        }
    })
</script>

<style>
    .product-item {
        cursor: pointer;
        color: blue;
    }
</style>
detail.html
<template>
    <div>商品详情</div>
    <div id="detail">
        商品ID:<span id="product-id" class="product-id"></span>
    </div>
</template>

<script>
    this.querySelector("#product-id").textContent=history.state.id;
</script>

<style>
    .product-id{
        color:red;
    }
</style>
about.html

<template>
    About Me!
</template>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值