一、路由
1.1 服务端路由 与 客户端路由
- 服务端路由
服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时,浏览器会从服务端获得全新的 HTML,然后重新加载整个页面。 - 客户端路由
客户端路由和服务端路由类似,只不过它是在浏览器中运行,使用 JavaScript 处理逻辑,并利用一些基于 JS 的模板引擎或其它方式呈现页面。客户端路由通常用于单页面应用程序中,这时服务器端代码主要用于为客户端代码提供通过 ajax 技术请求的 API 接口数据。
在单页面应用中,客户端的 JavaScript 可以拦截页面的跳转请求,动态获取新的数据,然后在无需重新加载的情况下更新当前页面。这样通常可以带来更顺滑的用户体验,尤其是在更偏向“应用”的场景下(这类场景下用户通常会在很长的一段时间中做出多次交互)。
客户端路由可以通过 History API 或者 hashchange 事件 来管理应用当前应该渲染的视图。
1.2 实现一个简单的路由
如果你只需要一个简单的页面路由,而不想为此引入一整个路由库,你可以通过动态组件的方式,监听浏览器 hashchange 事件或使用 History API 来更新当前组件。
如下图,通过点击的方式,达到路由切换的目的:
主要代码如下:
<script setup>
import { ref, computed } from 'vue'
import A from './A.vue'
import B from './B.vue'
import NotFound from "./NotFound.vue"
const routes = {
'/': A,
'/b': B
}
const currentPath = ref(window.location.hash)
window.addEventListener('hashchange', () => {
currentPath.value = window.location.hash
})
const currentView = computed(() => {
return routes[currentPath.value.slice(1) || '/'] || NotFound
})
</script>
<template>
<div>
<div>
<a href="#/">A</a>
<a href="#/b" style="margin-left: 20px">B</a>
</div>
<br>
<Transition name="fade">
<component :is="currentView" style="margin-top:50px" tag="div"></component>
</Transition>
</div>
</template>
<style scoped lang="less">
.fade-enter-from {
opacity: 0;
}
.fade-enter-active {
transition: opacity 1s 0.5s ease;
}https://code.7-plan.com/web/operation-management
.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: opacity .5s ease;
}
</style>
1.3 官方路由库 - Vue Router
Vue Router - 为 Vue.js 提供富有表现力、可配置的、方便的路由
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:
- 嵌套路由映射
- 动态路由选择
- 模块化、基于组件的路由配置
- 路由参数、查询、通配符
- 展示由 Vue.js 的过渡系统提供的过渡效果
- 细致的导航控制
- 自动激活 CSS 类的链接
- HTML5 history 模式或 hash 模式
- 可定制的滚动行为
- URL 的正确编码
关于项目路由的详细配置和使用,详见官方文档
二、状态管理
2.1 在 Vue 中,每一个组件实例都是一个独立的单元,包含三个部分:
-
状态
驱动整个应用的数据源 -
视图
对状态的一种声明式映射 -
交互
状态根据用户在视图中的输入而做出响应变更的可能方式
当多个组件共享一个共同状态时,可以利用 Vue 提供的 API,在抽取的公共组件上做状态管理: -
props 属性传递(深层次组件嵌套,会造成 prop 逐级透传问题,会显得非常麻烦)
-
provide/inject 穿透(可以解决深层次数据传递问题)
-
event 自定义事件
-
ref 引用获取父/子实例
-
globalProperties 定义全局状态
-
eventBus 公交总线
2.2 通过 Vue 响应式 API 实现一个简单的状态管理
创建一个共享组件,并使用 reactive 创建一个响应式状态对象
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0
})
在需要这个状态的组件中分别引入并使用
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>From A: {{ store.count }}</template>
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>From B: {{ store.count }}</template>
在这两个组件中,尝试去修改 store 对象,看一下视图是否会自动更新
<template>
<button @click="store.count++">
From B: {{ store.count }}
</button>
</template>
刷新页面,发现点击操作后,两个引用的组件中,这个状态都同步更新了。
然而,我们学习JS高级或者设计模式的时候,再三强调不建议这么做!为什么,想一想,随意一个组件都可以轻易的去修改状态,这符合代码的健壮性和可维护性原则吗?
所以为了确保状态改变的逻辑像状态本身一样集中,建议在 store 上,通过方法的方式来表达修改状态的意图,提供对应的接口去进行响应的操作,而不是随意修改。
共享组件代码增加操作接口函数如下
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment() {
this.count++
}
})
其它组件中通过对应的接口,完成状态的处理操作
<template>
<button @click="store.increment()">
From B: {{ store.count }}
</button>
</template>
请注意这里点击的处理函数使用了 store.increment(),带上了圆括号作为内联表达式调用,因为它并不是组件的方法,并且必须要以正确的 this 上下文来调用。
2.3 通过上面不难看出,Vue 组件本身其实已经实现了对响应式状态的管理。那既然这样,为什么我们还要使用 Pinia 或 Vuex 这些状态管理器呢?
这是因为在大规模的生产应用中,还有很多其它事项要考虑:
- 更强的团队协作约定
- 与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
- 模块热更新 (HMR)
- 服务端渲染支持
下面来展开讲一讲状态管理的应用
Pinia - 符合直觉的 Vue.js 状态管理库。类型安全、可扩展性以及模块化设计。甚至让你忘记正在使用的是一个状态库。