js之实现hashRouter也不难

学习获得成就感

学习中获得成就感非常重要的!将会决定选择的路线能否持之以恒的坚持下去👇🏻

suggestion by Ai

  1. 设定明确的目标:设定具体、可量化的学习目标,这样你在完成每个目标时都能够感到满足和有成就感。
  2. 分解大目标:如果你的学习目标很庞大,可以将其分解成更小、更具体的任务。完成每个小任务都是一个里程碑,让你感到进步和成就。
  3. 庆祝里程碑:每当达到一个重要的学习里程碑,不要忘记给自己一些小奖励或庆祝的方式,这会增强你的成就感和动力。
  4. 记录进步:保持一个学习日记或记录表,记录你的学习进展和成就。回顾过去的进步,会让你更有动力和自豪感。
  5. 寻求反馈:寻求他人的反馈和认可,他们的鼓励和肯定会增强你的成就感。可以向老师、同学、朋友或家人请教并分享你的学习成果。
  6. 持续学习:保持对学习的热情和好奇心,持续进步和学习新知识会带来更多的成就感。

💪💫 记住,学习的过程需要时间和努力,不要忘记欣赏自己的进步,享受学习的乐趣!加油!

为什么写这篇文章

  1. 用惯了封装好的库(时代发展不可避免),做技术需要探究下原理和实践过程。
  2. 记录下学习的内容
  3. 复盘 + 总结

作用

可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。

location.hash

🤔 location.hash 是一个包含在网页 URL 中的片段标识符,通常以 "#" 开头。它用于在网页中指示特定位置或锚点,让浏览器可以直接跳转到对应的片段或元素。

具体来说,当 URL 中包含了 "#" 字符后面的部分,浏览器就会将这部分信息解析为 location.hash。这使得网页作者可以通过设置特定的锚点,使用户在访问网页时直接跳转到指定的位置。这对于长页面或包含目录导航的文档特别有用。

表现形式:http://127.0.0.1:8080/index.html#index

涉及api

location.hash、onhashchange

  1. 设置location.hash
  2. onhashchange 监听hash的变化,做其他处理
javascript 复制代码
    location.hash = '/path';
    
    document.addEventListener('hashchange',function(e){
       // ...
   },false)

实现过程

父类

抽象因为history路由同样需要这些方法。父类,主要存储历史记录、路由表、当前路由、组件渲染

javascript 复制代码
class Parent {
  constructor(config) {
    this._routes = config.routes;
    this.routeHistory = [];
    this.currentUrl = '';
    this.currentIndex = -1;
  }
  // 路由变化触发
  getRoute = () => {
    const routes = this._routes;
    const route = routes.find((r) => r.path === this.currentUrl);
    this.renderComponent(route);
  };
  // 渲染路由组件
  renderComponent = (route) => {
    const component = route.component;
    if (typeof component === 'function') {
      console.log('renderComponent');
      component();
    }
  };
}
export default Parent;

Hash

子类实现hash初始化、hash监听、跳转方法

javascript 复制代码
import Parent from './Parent.js';

class Hash extends Parent {
  constructor(config) {
    super(config);
    this.init();
  }
  // 注册hashchange、初始化默认加载路由
  init(){}
  // 事件处理函数
  refresh(){}
  // 地址栏添加#/path,加载默认路由组件
  initLocationHash(){}
  // 改变location.hash =path;
  changeLocatinHash(path){}
  push(path){}
  go(index){}
  back(){}
  forward(){}
}
export default Hash;

具体实现

初始化

javascript 复制代码
class Hash extends Parent{
    init(){
       this.initLocationHash();
       window.addEventListener('hashchange', this.refresh.bind(this), false);
       window.addEventListener('load', this.refresh.bind(this), false);
    }
    // 路由变化触发函数
   refresh(e) {
      this.getRoute();
   }
   
   initLocationHash = () => {
       // 如果currentUrl为空表示初始状态
        if (!this.currentUrl) {
              // 找到index为true的路由并渲染
              let defaultItem = this._routes.find((r) => r.index);
              this.currentUrl = defaultItem.path;
              this.currentIndex = 0;
              this.routeHistory = [defaultItem];
              this.changeLocationHash(this.currentUrl);
              this.getRoute();
        }
  };
    
}

改变路由的方法

javascript 复制代码
class Hash extends Parent{
    go = (index = 1) => {
        if (index > 0) {
          this.currentIndex += index;
        } else {
          (this.currentIndex -= index) < 0 && (this.currentIndex = 0);
        }
        const route = this.routeHistory[this.currentIndex];
        console.log(route, 'route history');
        this.changeLocationHash(route?.path);
        return route;
      };
    push = (path) => {
        const route = this._routes.find((r) => r.path === path);
        this.routeHistory.push(route);
        this.currentIndex = this.routeHistory.length;
        this.changeLocationHash(route.path);
        return route;
    };
   back = () => {
        this.go(-1);
    };
   forward = () => {
       this.go();
   };
  }

消费

创建路由表并注册,导出history消费。

javascript 复制代码
    import HashRouter from './packages/Router/Hash.js';
    import AboutMe from './modelControl/AboutMe/index.js';
    import Article from './modelControl/article/index.js';
    const routes = [
      { index: true, path: '/index', index: true, component: AboutMe },
      { path: '/about', component: AboutMe },
      { path: '/article', component: Article },
    ];

    const history = new HashRouter({ routes });
    export default history;

模块中调用

javascript 复制代码
    querySelectorAll('[data-menu]').forEach((menu) => {
      menu.onclick = () => {
        history.push(path);
    };
});

主要是对a标签进行拦截阻止默认行为,进行路由处理。根据对比location.origin是否为外部链接判断,否则认为是内容路由跳转

typescript 复制代码
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
function LinkWithRef(
  {
    onClick,
    relative,
    reloadDocument,
    replace,
    state,
    target,
    to,
    preventScrollReset,
    ...rest
  },
  ref
) {
    // ...其他实现
        
    // 执行路由方法
    let internalOnClick = useLinkClickHandler(to, {
    replace,
    state,
    target,
    preventScrollReset,
    relative,
  });
  function handleClick(
    event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
  ) {
   //响应自定义click事件
    if (onClick) onClick(event);
    if (!event.defaultPrevented) {
    // 执行路由行为
      internalOnClick(event);
    }
  }

  return (
    // eslint-disable-next-line jsx-a11y/anchor-has-content
    <a
      {...rest}
      href={absoluteHref || href}
      onClick={isExternal || reloadDocument ? onClick : handleClick}
      ref={ref}
      target={target}
    />
  );
})

效果

结束

  1. 掌握hash路由实现原理
  2. 提高自己封装能力
  3. 探究现有封装库(react-router),学习源码思路
  4. 对自己来说还是很有意思的
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax