vue3学习-4(终结)
5. 内置组件
TransitionGroup | Vue.js给列表元素用的
KeepAlive | Vue.js多个组件间动态切换时缓存被移除的组件实例
Teleport | Vue.js可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去,做遮盖层可以用
Suspense | Vue.js在组件树中协调对异步依赖的处理
6. 应用规模化
单文件组件
<script setup>
import { ref } from 'vue'
const greeting = ref('Hello World!')
</script>
<template>
<p class="greeting">{{ greeting }}</p>
</template>
<style>
.greeting {
color: red;
font-weight: bold;
}
</style>
如何看待关注点分离?
一些有着传统 Web 开发背景的用户可能会因为单文件组件将不同的关注点集合在一处而有所顾虑,觉得 HTML/CSS/JS 应当是分离开的!
要回答这个问题,我们必须对这一点达成共识:前端开发的关注点不是完全基于文件类型分离的。前端工程化的最终目的都是为了能够更好地维护代码。关注点分离不应该是教条式地将其视为文件类型的区别和分离,仅仅这样并不够帮我们在日益复杂的前端应用的背景下提高开发效率。
在现代的 UI 开发中,我们发现与其将代码库划分为三个巨大的层,相互交织在一起,不如将它们划分为松散耦合的组件,再按需组合起来。在一个组件中,其模板、逻辑和样式本就是有内在联系的、是耦合的,将它们放在一起,实际上使组件更有内聚性和可维护性。
工具链
在线测试
npm create vue@latest
开发工具插件
Vue CLI迁移到vite 了
Vue - Official 取代了我们之前为 Vue 2 提供的官方 VS Code 扩展 Vetur。如果你之前已经安装了 Vetur,请确保在 Vue 3 的项目中禁用它。
浏览器开发者插件
在线演练场
路由
从头开始实现一个简单的路由
<script setup>
import { ref, computed } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import NotFound from './NotFound.vue'
const routes = {
'/': Home,
'/about': About
}
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>
<a href="#/">Home</a> |
<a href="#/about">About</a> |
<a href="#/non-existent-path">Broken Link</a>
<component :is="currentView" />
</template>
状态管理
理论上来说,每一个 Vue 组件实例都已经在“管理”它自己的响应式状态了。我们以一个简单的计数器组件为例:
<script setup>
import { ref } from 'vue'
// 状态
const count = ref(0)
// 动作
function increment() {
count.value++
}
</script>
<!-- 视图 -->
<template>{{ count }}</template>
它是一个独立的单元,由以下几个部分组成:
- 状态:驱动整个应用的数据源;
- 视图:对状态的一种声明式映射;
- 交互:状态根据用户在视图中的输入而作出相应变更的可能方式。
下面是“单向数据流”这一概念的简单图示:
然而,当我们有多个组件共享一个共同的状态时,就没有这么简单了:
- 多个视图可能都依赖于同一份状态。
- 来自不同视图的交互也可能需要更改同一份状态。
对于情景 1,一个可行的办法是将共享状态“提升”到共同的祖先组件上去,再通过 props 传递下来。然而在深层次的组件树结构中这么做的话,很快就会使得代码变得繁琐冗长。这会导致另一个问题:Prop 逐级透传问题。
对于情景 2,我们经常发现自己会直接通过模板引用获取父/子实例,或者通过触发的事件尝试改变和同步多个状态的副本。但这些模式的健壮性都不甚理想,很容易就会导致代码难以维护。
一个更简单直接的解决方案是抽取出组件间的共享状态,放在一个全局单例中来管理。这样我们的组件树就变成了一个大的“视图”,而任何位置上的组件都可以访问其中的状态或触发动作。
用响应式 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>
Pinia
https://cn.vuejs.org/guide/scaling-up/state-management.html#pinia
虽然我们的手动状态管理解决方案在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑:
- 更强的团队协作约定
- 与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
- 模块热更新 (HMR)
- 服务端渲染支持
Pinia整合了vuex 5的很多想法,而且vuex不更新了,以后只会更新pinia。
Vuex
测试
- 单元测试:检查给定函数、类或组合式函数的输入是否产生预期的输出或副作用。
- 组件测试:检查你的组件是否正常挂载和渲染、是否可以与之互动,以及表现是否符合预期。这些测试比单元测试导入了更多的代码,更复杂,需要更多时间来执行。
- 端到端测试:检查跨越多个页面的功能,并对生产构建的 Vue 应用进行实际的网络请求。这些测试通常涉及到建立一个数据库或其他后端。
请记住,测试这个组件做了什么,而不是测试它是怎么做到的。
- 推荐的做法
- 对于 视图 的测试:根据输入 prop 和插槽断言渲染输出是否正确。
- 对于 交互 的测试:断言渲染的更新是否正确或触发的事件是否正确地响应了用户输入事件。
用例指南
添加 Vitest 到项目中
在一个基于 Vite 的 Vue 项目中,运行如下命令:
> npm install -D vitest happy-dom @testing-library/vue
接着,更新你的 Vite 配置,添加上 test
选项:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
// ...
test: {
// 启用类似 jest 的全局测试 API
globals: true,
// 使用 happy-dom 模拟 DOM
// 这需要你安装 happy-dom 作为对等依赖(peer dependency)
environment: 'happy-dom'
}
})
如果使用 TypeScript,请将 vitest/globals
添加到 tsconfig.json
的 types
字段当中。
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
会自动搜索test目录下的*.test.js
结尾的文件
// MyComponent.test.js
import { render } from '@testing-library/vue'
import MyComponent from './MyComponent.vue'
test('it should work', () => {
const { getByText } = render(MyComponent, {
props: {
/* ... */
}
})
// 断言输出
getByText('...')
})
最后,在 package.json
之中添加测试命令,然后运行它:
{
// ...
"scripts": {
"test": "vitest"
}
}
npm test
测试组合式函数
// counter.js
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return {
count,
increment
}
}
// counter.test.js
import { useCounter } from './counter.js'
test('useCounter', () => {
const { count, increment } = useCounter()
expect(count.value).toBe(0)
increment()
expect(count.value).toBe(1)
})
依赖
- 生命周期钩子
- 供给/注入
// test-utils.js
import { createApp } from 'vue'
export function withSetup(composable) {
let result
const app = createApp({
setup() {
result = composable()
// 忽略模板警告
return () => {}
}
})
app.mount(document.createElement('div'))
// 返回结果与应用实例
// 用来测试供给和组件卸载
return [result, app]
}
import { withSetup } from './test-utils'
import { useFoo } from './foo'
test('useFoo', () => {
const [result, app] = withSetup(() => useFoo(123))
// 为注入的测试模拟一方供给
app.provide(...)
// 执行断言
expect(result.foo.value).toBe(1)
// 如果需要的话可以这样触发
app.unmount()
})
服务端渲染 (SSR)
7. TypeScript
8. 进阶主题(必看,核心原理)
Introduction · Get Started with Nuxt
尽管 Vue 主要是为构建 Web 应用而设计的,但它绝不仅仅局限于浏览器。你还可以:
- 配合 Electron 构建桌面应用
- 配合 Ionic Vue 构建移动端应用
- 使用 Quasar 或 Tauri 用同一套代码同时开发桌面端和移动端应用
- 使用 TresJS 构建 3D WebGL 体验
- 使用 Vue 的自定义渲染 API 来构建自定义渲染器,比如针对终端命令行的!
组合式 API 常见问答 | Vue.js为啥大型项目用组合式api
9. 项目
网上项目太多了,找了这几个后台项目,我公司目前用的是vue-element-admin基于vue2的
#vue3
https://github.com/vbenjs/vue-vben-admin
https://github.com/pure-admin/vue-pure-admin
#vue2
https://panjiachen.github.io/vue-element-admin-site