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. 对自己来说还是很有意思的
相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui