沉淀一下对vue-cli的基本认识
图片是一个基本的,使用vue-cli开发的界面

这里,我基于这个界面讲解几个疑惑点
这里在做一个前提介绍,一个vue文件
在我看来就是一个div集合了css,和js的操作。
的一小块内容。
1.同样都是vue页面,如何做到有些vue文件是固定的 有些vue文件是可以变化的。
1.1固定页面的实现原理


放两张图。
原理讲如下:
非常好的问题!App.vue 是整个应用的根组件,是所有组件的"老祖宗"。让我用层级关系帮你理清楚:
完整的组件层级结构
vue
<!-- App.vue - 最顶层容器 -->
<template>
<div id="app">
<!-- 这里只有最基础的全局内容 -->
<router-view /> <!-- 关键:路由视图的"根出口" -->
</div>
</template>
实际的渲染层级:
浏览器挂载点 (#app)
↓
App.vue(根组件,永远存在)
↓
<router-view />(App.vue里的第一个路由出口)
↓
根据当前URL决定显示什么:
- 如果URL是 /login → 显示 Login.vue
- 如果URL是 / 或 /dashboard → 显示 Layout.vue
↓
Layout.vue(布局组件)
↓
Layout里的 <router-view />(第二个路由出口)
↓
显示具体的页面组件(Dashboard.vue、Users.vue等)
具体代码示例:
1. App.vue - 唯一的根组件
vue
<!-- App.vue -->
<template>
<div id="app">
<!-- 可以放一些全局的东西,比如加载进度条、全局提示等 -->
<GlobalLoading />
<GlobalToast />
<!-- 最关键:路由的根出口 -->
<router-view />
</div>
</template>
2. 路由配置体现层级
javascript
// router/index.js
const routes = [
{
path: '/login',
component: () => import('@/views/Login.vue')
// 登录页会直接替换 App.vue 里的 router-view
},
{
path: '/',
component: Layout, // Layout会替换 App.vue 里的 router-view
children: [
{
path: 'dashboard',
component: () => import('@/views/Dashboard.vue')
// Dashboard会替换 Layout 里的 router-view
}
]
}
]
渲染流程可视化:
URL: /login
App.vue 渲染:
┌─────────────────────────┐
│ App.vue │
│ ┌───────────────────┐ │
│ │ router-view │ │
│ │ ┌─────────────┐ │ │
│ │ │ Login.vue │ │ │
│ │ └─────────────┘ │ │
│ └───────────────────┘ │
└─────────────────────────┘
URL: /dashboard
App.vue 渲染:
┌─────────────────────────┐
│ App.vue │
│ ┌───────────────────┐ │
│ │ router-view │ │
│ │ ┌─────────────┐ │ │
│ │ │ Layout.vue │ │ │
│ │ │ ┌─────────┐ │ │ │
│ │ │ │router-view│ │ │ │
│ │ │ │Dashboard │ │ │ │
│ │ │ └─────────┘ │ │ │
│ │ └─────────────┘ │ │
│ └───────────────────┘ │
└─────────────────────────┘
App.vue 的核心作用:
-
唯一的根组件 -
main.js中挂载的就是它javascript// main.js new Vue({ render: h => h(App) // App作为根组件 }).$mount('#app') -
提供最外层的路由出口 - 决定显示登录页还是后台布局
-
放置全局性的东西:
- 全局样式(不用在每个组件重复写)
- 全局加载进度条
- 全局消息提示
- 权限控制守卫
对比理解:
- App.vue:整个房子的地基和框架,永远不变
- Layout.vue:客厅的布局(沙发、电视墙固定)
- views/xxx.vue:茶几上的摆件(可以随时更换)
- components/xxx.vue:固定的家具(沙发、电视)
2.siber里面的两个问题
1.为什么可以渲染路由信息
2.为什么点击可以实现路由效果
1. 这个组件是如何渲染出来的?
渲染流程(从根到叶)
vue
<!-- App.vue (根组件) -->
<template>
<div id="app">
<!-- 路由出口 -->
<router-view />
</div>
</template>
javascript
// router/index.js (路由配置)
const routes = [
{
path: '/',
component: Layout, // 访问根路径,渲染Layout组件
children: [
// ... 子路由
]
}
]
vue
<!-- Layout.vue (布局组件,包含侧边栏+内容区) -->
<template>
<div class="layout">
<!-- 关键点!SideMenu组件被硬编码在这里 -->
<SideMenu />
<div class="main-content">
<!-- 这里是动态内容的出口 -->
<router-view />
</div>
</div>
</template>
<script>
import SideMenu from './SideMenu.vue' // 导入你写的菜单组件
export default {
components: {
SideMenu // 注册组件
}
}
</script>
渲染过程:
main.js将App.vue挂载到#app容器App.vue中的<router-view/>根据 URL 决定显示什么- 当 URL 是
/或/dashboard时,路由匹配到Layout组件 Layout.vue渲染时,硬编码 了<SideMenu />标签SideMenu组件按照你的模板代码,渲染出完整的菜单 HTML
关键点: 你的 SideMenu 组件不是通过路由动态渲染的,而是直接在 Layout.vue 中硬编码引入 的,所以它会永远固定显示在左侧。
2. 为什么点击可以实现页面跳转?
核心机制:Vue Router 的编程式导航
你在代码中做了两件事,共同实现了跳转:
① 点击事件绑定
vue
<el-menu-item @click="handleMenuClick('/article/list')">
文章列表
</el-menu-item>
② 跳转逻辑
javascript
methods: {
handleMenuClick(path) {
this.$router.push(path) // 关键代码!
}
}
this.$router.push() 的工作流程
javascript
// 当你点击"文章列表"时:
handleMenuClick('/article/list')
↓
this.$router.push('/article/list')
↓
// Vue Router 内部做了这些事:
// 1. 改变浏览器 URL(不刷新页面,使用 history.pushState)
// 2. 查找路由配置,找到 path: '/article/list' 对应的组件
// 3. 触发重新渲染,将目标组件显示到 <router-view/> 中
// 4. 更新 $route 对象,触发你写的 watch 监听器
对比传统 Web 应用
传统 <a> 标签 |
你的 @click + $router.push |
|---|---|
<a href="/article/list"> |
@click="handleMenuClick('/article/list')" |
| 浏览器刷新页面 | 无刷新,单页应用体验 |
| 重新请求 HTML、CSS、JS | 只替换 <router-view/> 的内容 |
| 丢失所有 Vue 状态 | 保持状态(比如菜单的激活状态) |
为什么不用 <router-link>?
你可能会问:为什么不用更简洁的 <router-link>?
vue
<!-- 更 Vue 的方式 -->
<router-link to="/article/list">
<el-menu-item>文章列表</el-menu-item>
</router-link>
你当前用 @click + $router.push 的原因:
el-menu-item是 Element UI 组件,包裹<router-link>可能出现样式冲突- 需要额外处理
activeIndex(激活状态),用@click可以统一控制 - 更灵活的跳转逻辑(比如跳转前可以做权限判断)
javascript
handleMenuClick(path) {
// 可以在跳转前做各种事情
if (this.hasPermission(path)) {
this.$router.push(path)
} else {
this.$message.error('无权限访问')
}
}
可视化总结
你的 SideMenu 组件
│
├── 渲染方式:硬编码在 Layout.vue 中(不是路由决定的)
│ └── Layout.vue 渲染 → 加载 SideMenu → 生成 DOM
│
└── 跳转原理:编程式导航
├── 点击菜单 → 触发 @click
├── 调用 this.$router.push(path)
├── Vue Router 改变 URL 和视图
└── 右侧 <router-view/> 内容更新
一句话总结:
- 渲染 :
Layout.vue直接引用了你的组件,所以它永远在左侧 - 跳转 :
this.$router.push()告诉 Vue Router 切换页面,但菜单本身不动
3.views文件
为什么使用这个方法 this.$router.push('/article/list') ,可以实现页面切换
非常好的问题!这触及了 Vue Router 的核心原理。让我用图解+代码的方式彻底讲清楚 this.$router.push() 为什么能实现页面切换。
核心答案:因为 Vue Router 维护了一个响应式的当前组件
1. 先理解 $router 和 $route 是什么
javascript
// Vue Router 安装时,会在每个 Vue 实例上注入两个对象:
this.$router // 路由器实例,包含 push(), replace(), go() 等方法
this.$route // 当前路由信息,包含 path, params, query, matched 等
2. 关键原理:<router-view> 是一个响应式组件
vue
<!-- 这是 Vue Router 内置的 <router-view> 组件的简化版原理 -->
<template>
<component :is="currentComponent" />
</template>
<script>
export default {
computed: {
currentComponent() {
// 核心!这个计算属性依赖 this.$route
// 当 this.$route 变化时,Vue 会重新计算并重新渲染
const component = matchRoute(this.$route.path)
return component
}
}
}
</script>
3. push() 执行后发生了什么?(完整流程)
javascript
// 你调用这个方法
this.$router.push('/article/list')
// === Vue Router 内部执行流程 ===
// 第1步:更新浏览器 URL(不刷新页面)
window.history.pushState({}, '', '/article/list')
// 第2步:更新内部维护的响应式数据
this.currentRoute = reactive({ // 这是一个响应式对象
path: '/article/list',
params: {},
query: {},
matched: [...] // 匹配到的路由记录
})
// 第3步:触发响应式更新
// 因为 $route 是响应式的,而 <router-view> 依赖它
// 所以 Vue 会重新执行 <router-view> 的 render 函数
// 第4步:重新匹配组件
const newComponent = routeTable['/article/list'] // 找到 ArticleList 组件
// 第5步:重新渲染
// <router-view> 把旧的组件替换成新的组件
4. 可视化流程图
你点击菜单
↓
this.$router.push('/article/list')
↓
┌─────────────────────────────────────────┐
│ Vue Router 内部 │
│ ┌────────────────────────────────────┐ │
│ │ 1. 改变 URL (history.pushState) │ │
│ │ 2. 更新响应式数据 $route │ │
│ │ - path: '/article/list' │ │
│ │ - component: ArticleList │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
↓
Vue 检测到响应式数据变化
↓
重新执行 <router-view> 的 render
↓
把 ArticleList 组件渲染出来
↓
页面切换完成(无刷新!)
5. 用简单代码模拟核心原理
javascript
// 这是一个简化版的 Vue Router 实现
class SimpleRouter {
constructor(routes) {
this.routes = routes
// 核心!创建一个响应式的当前路由对象
this._currentRoute = Vue.observable({
path: window.location.pathname,
component: null
})
}
push(path) {
// 1. 改变浏览器 URL
window.history.pushState({}, '', path)
// 2. 找到对应的组件
const route = this.routes.find(r => r.path === path)
// 3. 更新响应式数据(这会触发视图更新!)
this._currentRoute.path = path
this._currentRoute.component = route.component
}
get route() {
return this._currentRoute
}
}
// 在你的组件中
this.$router = new SimpleRouter(routes)
// <router-view> 简化版
Vue.component('router-view', {
render(h) {
// 依赖 this.$router.route.component
// 当它变化时,Vue 自动重新渲染
return h(this.$router.route.component)
}
})
6. 验证:watch 监听 $route 变化
你之前写的代码中就有证据:
javascript
watch: {
'$route'(to) { // ← 这个监听器能工作,说明 $route 是响应式的
const path = to.path
if (path.startsWith('/article')) {
this.activeIndex = '2'
}
// ...
}
}
如果 $route 不是响应式的,这个 watch 永远不会触发!
7. 完整的数据流
┌─────────────────────────────────────────────────────────┐
│ 你的代码 │
│ this.$router.push('/article/list') │
└────────────────────┬────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Vue Router 内部 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ transitionTo('/article/list') │ │
│ │ ↓ │ │
│ │ 更新 _route 响应式对象 │ │
│ │ ↓ │ │
│ │ Object.assign(this._route, { │ │
│ │ path: '/article/list', │ │
│ │ matched: [Layout, ArticleList] │ │
│ │ }) │ │
│ └───────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Vue 响应式系统 │
│ 检测到 this._route 变化 │
│ ↓ │
│ 通知所有依赖 this._route 的组件重新渲染 │
└────────────────────┬────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ <router-view> 组件 │
│ 重新执行 render() 函数 │
│ ↓ │
│ 从 this.$route.matched 获取新组件 │
│ ↓ │
│ 渲染 ArticleList 组件 │
└─────────────────────────────────────────────────────────┘
总结一句话
this.$router.push() 能切换页面,是因为它更新了一个响应式的 $route 对象,而 <router-view> 组件依赖这个对象来决定渲染哪个组件,Vue 的响应式系统自动完成了视图更新。
这就是 Vue 的数据驱动视图 思想的最佳体现:你不需要手动操作 DOM,只需要改变数据($route),视图(<router-view>)会自动更新。