vuejs路由和组件系统

news2025/1/14 18:33:32

前端路由原理

  • createRouter

    * hash
      * window.addEventListener('hashChange')
      *	两种实现路由切换的模式:UI组件(router-link,router-view),Api(push()方法)
    * history 
      * HTML5新增的API ,可以通过不刷新页面前提下,修改页面路由-history.pushState() 
    
  • useRouter

  • router-link

  • router-view

手写实现一个路由跳转
/**
 * mini 版本 vue-router
 */

import { ref, provide, inject } from 'vue';
import RouterLink from './router-link.vue';
import RouterView from './router-view.vue'

// 保证全局唯一性,可以用 Symbol 创建
const ROUTER_KEY = '__router__'

// 
class Router {
  constructor(options) {
    // 记录访问历史
    this.history = options.history;
    // 初始化传入路由表
    this.routes = options.routes;
    // 当前激活的路由
    this.current = ref(this.history.url)

    // hashChange 事件触发把当前激活路由的值记录下来
    this.history.bindEvents(() => {
      this.current.value = window.location.hash.slice(1)
    })
    
  }

  push(to) {
    location.hash = '#' + to;
  
    window.history.pushState({}, '', to)
  }

  beforeEach(fn) {
    this.guards = fn;
  }

  // app 插件的注册调用函数
  // provie/inject,pinia 
  install(app) {
    app.provide(ROUTER_KEY, this)

    // 兼容 options API
    app.$router = this;

    // 注册全局组件
    app.component('router-link', RouterLink)
    app.component('router-view', RouterView)
  }
}

// 1. hash
// hashChange -> View 
function createWebHashHistory() {
  function bindEvents(fn) {
    window.addEventListener('hashchange', fn)
  }

  return {
    bindEvents,
    url: window.location.hash.slice(1) || '/'
  }
}

// TODO
function createWebHistory() {

}

// 组合式 API,获取当前 vue-router 的实例
// options API, this.$router 来获取vue-router 的实例
function useRouter() {
  return inject(ROUTER_KEY)
}

// 暴露一个创建对应类的实例的方法
function createRouter(options) {
  return new Router(options)
}

export { createRouter, useRouter, createWebHashHistory }



router-link

<template>
  <a :href="'#' + props.to">
    <slot />
  </a>
</template>

<script setup>

// a -> href
// router-link to
import { defineProps } from 'vue';

let props = defineProps({
  to: { type: String, required: true }
})

</script>

<style lang="css" scoped></style>

router-view

<template>
  <!-- 动态组件 -->
  <component :is="comp"></component>
</template>

<script setup>

import { computed } from 'vue'
import { useRouter } from './mini-router'

// 获取到了 Router 的实例
let router = useRouter();

console.log('router 实例', router)

const comp = computed(() => {
  // 根据注册的路由表和当前激活的 route 匹配
  const route = router.routes.find(route => {
    // 百分百等于,静态路由
    return route.path === router.current.value
  })

  // 路由守卫的激活
  const ret = route?.guards

  return route.component;
})

</script>

<style lang="scss" scoped></style>
特性原理解析
  • 路由匹配规则:静态路由、动态路由、正则匹配

    const router = new VueRouter({
       routes: [
         // 动态路径参数 以冒号开头
         { path: '/user/:id', component: User }
       ]
     })
    
  • 嵌套路由:

    const router = new VueRouter({
       routes: [
         {
           path: '/user/:id',
           component: User,
           children: [
             {
               // 当 /user/:id/profile 匹配成功,
               // UserProfile 会被渲染在 User 的 <router-view> 中
               path: 'profile',
               component: UserProfile
             },
             {
               // 当 /user/:id/posts 匹配成功
               // UserPosts 会被渲染在 User 的 <router-view> 中
               path: 'posts',
               component: UserPosts
             }
           ]
         }
       ]
     })
    
  • 路由守卫:
    在这里插入图片描述

  • 路由元信息:路由表中配置meta字段

  • 滚动行为记录:这个功能只在支持 history.pushState 的浏览器中可用。

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
  }
})
  • 路由懒加载和异步组件
const Foo = () => import('./Foo.vue')
把组件按组分块
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
  • 过渡动效
<transition>
  <router-view></router-view>
</transition>

<!-- 使用动态的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>
内置组件
  • 异步组件 defineAsyncComponent 、 <component :is=“xxx” / >
import { defineAsyncComponent } from 'vue'
const AsyncHelloWorld = defineAsyncComponent({
	//es6 import
	loader:()=>import('xxx.vue'),
	loadingComponent:LoadingComp,
	delay:100,
	timeout:300,
	errorComponent:xxx
})

异步组件的实现

// 异步组件的实现
// options = object {
//  loader: () => Promoise(void)
// }
// options 
function defineAsyncComponent(options) {
  if (typeof options === 'function') {
    options = {
      loader: options,
    };
  }

  const { loader } = options;

  let InnerComp = null;

  // 记录重试次数
  let retries = 0;
  // 封装 load 函数用来加载异步组件
  function load() {
    return (
      loader()
        // 捕获加载器的错误
        .catch((err) => {
          // 如果用户指定了 onError 回调,则将控制权交给用户
          if (options.onError) {
            // 返回一个新的 Promise 实例
            return new Promise((resolve, reject) => {
              // 重试方法
              const retry = () => {
                resolve(load());
                retries++;
              };
              // 失败
              const fail = () => reject(err);
              // 作为 onError 回调函数的参数,让用户来决定下一步怎么做
              options.onError(retry, fail, retries);
            });
          } else {
            throw error;
          }
        })
    );
  };
  // 创建一个 vue 组件
  return {
    name: 'AsyncComponentWrapper',
    setup() {
      // 标识异步组件是否加载成功
      const loaded = ref(false);
      const error = shallowRef(null);
      const loading = ref(false);
      // timeout 默认不超时
      const timeout = ref(false);

      let loadingTimer = null;
      if (options.delay) { // 100ms 
        loadingTimer = setTimeout(() => {
          loading.value = true;
        }, options.delay);
      } else {
        loading.value = true;
      }

      let timer = null
      // 用户配置参数 timeout
      if(options.timeout) {
        timer = setTimeout(() => {
          timeout.value = true;
        }, options.timeout);
      }

      onUmounted(() => clearTimeout(timer))

      // 调用 load 函数加载组件
      // import(), ES6
      load()
        .then((c) => {
          InnerComp = c;
          loaded.value = true;
        })
        .catch((err) => {
          error.value = err;
        })
        .finally(() => {
          loading.value = false;
          clearTimeout(loadingTimer);
        });
      
      // 占位内容...
      const Placeholer = { type: Text, children: '' }

      if(loaded.vlaue) {
        // 异步组价加载成功,渲染 InnerComp,否则渲染渲染出错组件
        return {
          type: InnerComp,
        }
      } else if(timeout.value && options.errorComponent) {
        // 超时,并且设置了 Error 组件
        return {
          type: options.errorComponent,
        }        
      } else if(error.value && options.errorComponent) {
        return {
          type: options.errorComponent,
        }
      }

      return Placeholer

    },
  };
}

// load 函数接收一个 onError 回调函数
function load(onError) {
  // 请求接口,得到 Promise 实例
  const p = fetch(100);
  // 捕获错误
  return p.catch((err) => {
    // 当错误发生时,返回一个新的 Promise 实例,并调用 onError 回调,
    // 同时将 retry 函数作为 onError 回调的参数
    return new Promise((resolve, reject) => {
      // retry 函数,用来执行重试的函数,执行该函数会重新调用 load 函数并发送请求
      const retry = () => resolve(load(onError));
      const fail = () => reject(err);
      onError(retry, fail);
    });
  });
}

function fetch(ms) {
  return new Promise((resolve, reject) => {
    // 请求会在  秒后失败
    setTimeout(() => {
      reject('err');
    }, ms);
  });
}

  • KeepAlive 组件
  • Teleport 组件
<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>
  • Transition 组件
    < Transition> 会在一个元素或组件进入和离开 DOM 时应用动画
<Transition name="fade">
  ...
</Transition>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
使用场景
  • keep-alive,多标签页交互,多 tab 切换
  • teleport,全局弹窗,dom 结构脱离组件树渲染
  • transition,实现组件过渡动画
  • defineAsyncComponent,声明一个异步组件,实现性能优化和分 chunk 打包

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1691278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年电工杯A题论文首发+摘要分享+问题一代码分享

问题一论文代码链接&#xff1a;https://pan.baidu.com/s/1kDV0DgSK3E4dv8Y6x7LExA 提取码&#xff1a;sxjm --来自百度网盘超级会员V5的分享 园区微电网风光储协调优化配置 摘要&#xff1a;园区微电网由风光发电和主电网联合为负荷供电&#xff0c;为了尽量提高风光电量的…

机器人运动轨迹学习——GMM/GMR算法

机器人运动轨迹学习——GMM/GMR算法 前置知识 GMM的英文全称为&#xff1a;Gaussian mixture model&#xff0c;即高斯混合模型&#xff0c;也就是说&#xff0c;它是由多个高斯模型进行混合的结果&#xff1a;当然&#xff0c;这里的混合是带有权重概念的。 一维高斯分布 GMM中…

鸿蒙布局List简介

鸿蒙布局List简介 List--常见的布局容器List 创建方式创建方式一&#xff0c;通过Listitem创建方式二&#xff0c;通过ForEach和Listitem创建方式三&#xff0c;通过ListItemGroup List–常见的布局容器 List是在app开发中最常见的一种布局方式&#xff0c;例如通讯录、新闻列…

Vue3实现简单的瀑布流效果,可抽离成组件直接使用

先来看下效果图&#xff1a; 瀑布流中的内容可进行自定义&#xff0c;这里的示例图是通过不同背景颜色的展示进行区分&#xff0c;每个瀑布流中添加了自定义图片和文字描述。 实现方式&#xff1a; 1.建立子组件&#xff08;可单独抽离&#xff09;写出瀑布流的样式 文件名为…

远程桌面连接不上远程服务器,如何有效的解决远程桌面连接不上远程服务器的问题

在解决远程桌面连接不上远程服务器的问题时&#xff0c;我们需要采取一种专业且系统的方法来排查和修复可能存在的故障。以下是一些建议和步骤&#xff0c;帮助用户更有效地解决此类问题。 首先&#xff0c;用户需要确认远程服务器的状态和网络连接是否正常。 这包括检查服务器…

51汇编版--配套proteus仿真

代码配套的仿真&#xff0c;我自己试过是好使的&#xff0c;外部中断&#xff0c;计数功能要自己添加脉冲信号或按键才能有对应现象&#xff0c;自己搞一下子。 不限速下载链接 https://wwo.lanzoul.com/iQ46m1zm456j 密码:g92f 如果连接不正常&#xff0c;请尝试将lanzoul的…

Python游戏编程:一步步用Python打造经典贪吃蛇小游戏

贪吃蛇作为一款极其经典且广受欢迎的小游戏&#xff0c;是早期 Windows 电脑和功能手机&#xff08;特别是诺基亚手机&#xff09;流行度极高的小游戏&#xff0c;是当时功能手机时代最具代表性的游戏之一。游戏的基本规则和目标十分简单&#xff0c;但却极具吸引力&#xff0c…

【管理咨询宝藏111】安永某集团供应链流程设计及现状分析报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏111】安永某集团供应链流程设计及现状分析报告 【格式】PDF版本 【关键词】安永、供应链、流程优化 【核心观点】 - 缺乏客户分级&#xff0c;无…

力扣HOT100 - 75. 颜色分类

解题思路&#xff1a; 单指针&#xff0c;对数组进行两次遍历。 class Solution {public void sortColors(int[] nums) {int p 0;int n nums.length;for (int i 0; i < n; i) {if (nums[i] 0) {int tmp nums[i];nums[i] nums[p];nums[p] tmp;p;}}for (int i p; i …

visual studio 2022 ssh 主机密钥算法失败问题解决

 Solution - aengusjiang 问题&#xff1a; I follow the document, then check sshd_config, uncomment“HostKey /etc/ssh/ssh_host_ecdsa_key” maybe need add the key algorithms: #HostKeyAlgorithms ssh-ed25519[Redacted][Redacted]rsa-sha2-256,rsa-sha2-512 Ho…

对于高速信号完整性,一块聊聊啊(10)

本文包含的主要内容有: 过孔设计概述:从前面的各种基础知识到过孔设计,逐步对信号完整性有了初步了解,在过孔设计这里稍微做一个概述,也是个人的一些理解,算是一个小结。 过孔设计的必要性。 过孔结构的基础知识 实例:过孔设计仿真HFSS实例 过孔设计概述 通过前面…

如何运用多媒体,打造企业实力展示厅?

企业文化、产品是其长期发展的根本所在&#xff0c;为此越来越多的企业开始选择运用多媒体互动&#xff0c;来打造企业多媒体展厅的方式&#xff0c;对企业文化、品牌形象、产品进行推广宣传&#xff0c;并在多媒体互动装置的支持下&#xff0c;能让客户能够快速且全面的了解企…

【全开源】多场馆场地预定小程序源码(ThinkPHP+FastAdmin+UniApp)

场馆场地预定小程序源码一款基于ThinkPHPFastAdminUniApp开发的多场馆场地预定小程序&#xff0c;提供运动场馆运营解决方案&#xff0c;适用于体育馆、羽毛球馆、兵乒球馆、篮球馆、网球馆等场馆&#xff08;高级版&#xff09;

自由应用大本营?开源免费的Android应用商店:F-Droid Client

F-Droid Client&#xff1a;拥抱开源&#xff0c;守护隐私&#xff0c;让自由软件成为您生活的一部分- 精选真开源&#xff0c;释放新价值。 概览 F-Droid Client是一个开源的Android应用商店&#xff0c;它在GitHub上免费提供。这个项目致力于收集和展示各类自由及开源软件&a…

spring cloud config server源码学习(一)

文章目录 1. 注解EnableConfigServer2. ConfigServerAutoConfiguration2.1 ConditionalOnBean和ConditionalOnProperty2.2 Import注解2.2.1. EnvironmentRepositoryConfiguration.class2.2.2. CompositeConfiguration.class2.2.3. ResourceRepositoryConfiguration.class2.2.4.…

最新可用GPT-4o模型4大方法免费使用

探索GPT-4o&#xff1a;一次界限突破的AI体验&#xff01; 在AI世界的最前沿&#xff0c;GPT-4o的亮相已经掀起了一场技术革命的风暴&#xff01;它的问世不仅在行业内部引起了剧烈的震动&#xff0c;更是激起了一波波全球性的讨论和热烈追捧。 既然你在这里&#xff0c;我知…

Prometheus监控平台配置--监控mysql

上一篇中讲述了怎么安装Prometheus&#xff0c;然后对服务器集群资源信息进行监控并通过grafana展示监控信息&#xff0c;在这一篇中我们只讲和mysql相关的监控&#xff0c;关于prometheus的监控原理以及安装可以看下上一篇。 1.上传 通过rz命令将安装包上传到任意目录&#xf…

JVM1.8分代的理论基础和简单测试

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

Redhat7.4部署MySQL-5.7.17搭建双主互为主从

一、准备工作 需要先准备已经搭建好的两台数据库&#xff0c;并且保证服务器之间网络是通的&#xff0c;3306端口可以相互访问。 二、修改两台数据库my.cnf 配置文件&#xff0c;将下列内容添加进去&#xff0c;放在 [mysqld] 下 我们暂定两台服务器为A服务和B服务&#xff…

分享一个用AI降本的思路,不懂代码也能上手

如何用AI解决实际的业务问题&#xff1f; 生财圈友我来利用ChatGPT做算法建模&#xff0c;每年为公司省下6万元。 今天他将分享通过ChatGPT进行数据分析的思路&#xff0c;从最开始定义问题到最终数据论证。 上手的实操过程门槛并不高&#xff0c;但可以实现把官方电商平台的…