文章目录
前言
一、Vue3合成API初体验
1、为什么选择成分API?
创建Vue组件使我们能够将接口的可重复部分及其功能提取到可重用的代码段中。就可维护性和灵活性而言,仅此一项就可以使我们的应用程序发展得很远。但是,我们的集体经验证明,仅凭这一项还不够,特别是当您的应用程序变得非常大时-考虑一下数百个组件。当处理如此大的应用程序时,共享和重用代码变得尤为重要。
假设在我们的应用程序中,我们有一个视图来显示某个用户的存储库列表。最重要的是,我们要应用搜索和过滤功能。我们处理此视图的组件可能如下所示:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories () {
// using this.user to fetch user repositories
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
该组件具有以下职责:
从假定的外部API获取该用户名的存储库,并在用户更改时刷新它
使用searchQuery字符串搜索存储库
使用filters对象过滤存储库
组织与组件的选择逻辑(data
,computed
,methods
,watch
)在大多数情况下工作。但是,当我们的组件变得更大时,逻辑关注点列表也会增加。这可能导致难以阅读和理解的组件,特别是对于那些最初没有编写它们的人。
展示一个大型组件的示例,其中其逻辑关注点按颜色分组。
这种碎片化使得难以理解和维护复杂的组件。期权的分离掩盖了潜在的逻辑问题。另外,当处理单个逻辑问题时,我们必须不断地“跳动”相关代码的选项块。
2、合成API基础
setup 组件选项
该setup选项应该是可以接受的功能props和context我们将谈论以后。此外,我们返回的所有内容都setup将暴露给组件的其余部分(计算属性,方法,生命周期挂钩等)以及组件的模板。
App.vue
<template>
<div>
<h1 @click="changeEvent">计数:{{count}}</h1>
<h1 @click="changeNum">计数:{{num}}</h1>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
data(){
console.log('data')
return {
count:0
}
},
methods:{
changeEvent(){
this.count++
}
},
setup(){
console.log('setup')
const num = ref(0)
function changeNum(){
num.value+=10
}
return {num,changeNum}
},
beforeCreate(){
console.log("beforeCreate:初始化数据之前")
},
created(){
console.log('created:数据初始化之后')
},
beforeMount(){
console.log('beforeMounte:挂载渲染之前')
},
mounted(){
console.log('mounted:挂载渲染之后')
},
}
</script>
代码结果:
二、Vue3合成API详解
1、反应变量与 ref
在Vue 3.0中,我们可以使用新ref函数使任何变量在任何地方具有反应性,如下所示:
import { ref } from 'vue'
const counter = ref(0)
ref
接受参数并将其返回包装在具有value
属性的对象中,然后可以使用该属性访问或更改反应变量的值:
在对象内部包装值似乎是不必要的,但是需要使行为在JavaScript中的不同数据类型之间保持统一。
2、对变化做出反应 watch
就像user我们使用该watch选项在组件内部的属性上设置观察程序一样,我们也可以使用watch从Vue导入的功能进行相同的操作。它接受3个参数:
- 一个无参考或getter函数,我们想看
- 回调
- 可选配置选项
快速浏览一下它的工作原理。
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
counter
例如counter.value = 5
,无论何时修改,监视都会触发并执行回调(第二个参数),在这种情况下,该回调将登录'The new counter value is: 5'
到我们的控制台。
3、独立computed属性
与ref
和相似watch
,还可以使用computed
从Vue导入的功能在Vue组件外部创建计算属性。让我们回到我们的反例:
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
在此,该computed
函数返回对类似getter的回调的输出(作为的第一个参数传递)的只读 响应引用computed
。为了进入价值新创建的计算的变量,我们需要使用.value
属性,就像用ref
。
<template>
<div>
<h1 @click="changeEvent">计数:{{count}}</h1>
<h1 @click="changeNum">计数:{{num}}</h1>
<!-- <h1>用户名:{{user.username}}</h1>
<h1>特点:{{user.type}}</h1> -->
<h1>用户名:{{username}}</h1>
<h1 @click="changeType">特点:{{type}}</h1>
<h1>特点翻转:{{reverseType}}</h1>
<!-- <User :username="username" :age="age" class="abc" />
<Student /> -->
</div>
</template>
<script>
import {ref,reactive,toRefs,computed,watchEffect,watch} from 'vue'
export default {
name: 'App',
data(){
console.log('data')
return {
count:0
}
},
methods:{
changeEvent(){
this.count++
}
},
setup(){
console.log('setup')
const num = ref(0)
function changeNum(){
num.value+=10
}
const user = reactive({
username:"xiaowu",
age:22,
type:"学习!!",
reverseType:computed(()=>{
return user.type.split('').reverse().join('');
})
})
function changeType(){
user.type = "学习一般!!"
}
//判断监听事件的对象 则用watchEffect
watchEffect(()=>{
console.log(user.type)
console.log('当userType改变时候,会触发执行此函数')
})
// 单独监听
watch(num,(newNum,prevNum)=>{
console.log(newNum,prevNum)
console.log('当num改变时候,会触发执行此函数')
})
// 多个监听
watch([num,user],(newNum,prevNum)=>{
console.log(newNum,prevNum)
console.log('当num改变时候,会触发执行此函数')
})
return {num,changeNum,...toRefs(user),changeType}
},
beforeCreate(){
console.log("beforeCreate:初始化数据之前")
},
created(){
console.log('created:数据初始化之后')
},
beforeMount(){
console.log('beforeMounte:挂载渲染之前')
},
mounted(){
console.log('mounted:挂载渲染之后')
},
}
</script>
结果:
三、setup中使用生命周期函数
1、生命周期挂钩注册 setup
为了使Composition API与Option API相比功能更完整,我们还需要一种在中注册生命周期挂钩的方法setup
。由于Vue导出了几个新功能,因此可以做到这一点。Composition API上的生命周期挂钩与Options API的名称相同,但前缀为on
:即mounted
看起来像onMounted
。
这些函数接受一个回调,该回调将在组件调用该挂钩时执行。
App.vue
<template>
<div>
<h1 @click="changeEvent">计数:{{count}}</h1>
<h1 @click="changeNum">计数:{{num}}</h1>
<User :username="username" :age="age" class="abc" />
</div>
</template>
<script>
import {ref,reactive,toRefs,computed} from 'vue'
import User from './components/User.vue'
export default {
name: 'App',
data(){
console.log('data')
return {
count:0
}
},
components:{
User
},
methods:{
changeEvent(){
this.count++
}
},
setup(){
console.log('setup')
const num = ref(0)
function changeNum(){
num.value+=10
}
//reactive表示多个属性的响应
const user = reactive({
username:"小吴",
age:22,
type:"学习!!",
reverseType:computed(()=>{
return user.type.split('').reverse().join('');
})
})
function changeType(){
user.type = "学习一般!!"
}
return {num,changeNum,...toRefs(user),changeType}
},
}
</script>
User.vue
<template>
<div>
<h1>username:{{username}}</h1>
<h1>age:{{age}}</h1>
<h1>description:{{description}}</h1>
</div>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onBeforeUnmount,onUnmounted} from 'vue'
export default {
setup(props,content){
console.log(props)
console.log(content)
const description = ref(props.username+"年龄是"+props.age)
onBeforeMount(()=>{
console.log('onBeforeMount')
})
onBeforeMount(()=>{
console.log('onBeforeMount')
})
return {description}
},
props:['username','age']
}
</script>
父组件中 <User :username="username" :age="age" class="abc" />
的class="abc"
也传入到子组件中:
四、通过Provide和inject将数据提供给子组件
1、场景背景
假设我们要重写以下代码,其中包含一个使用Composition APIMyMap
为MyMarker
组件提供用户位置的组件。
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {
inject: ['location', 'geolocation']
}
</script>
2、使用Provide
使用providein
时setup()
,我们首先从中显式导入该方法vue。这使我们可以使用自己的调用来定义每个属性provide
。
该provide
函数允许您通过两个参数定义属性:
1、属性名称(<String>
类型)
2、物业价值
使用我们的MyMap
组件,我们提供的值可以按以下方式重构:
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}
</script>
3、使用inject
使用inject
in时setup()
,还需要从中显式导入vue
。一旦这样做,我们就可以调用它来定义我们如何将其公开给我们的组件。
该inject
函数有两个参数:
1、要注入的属性的名称
2、默认值(可选)
使用我们的MyMarker
组件,我们可以使用以下代码对其进行重构:
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
案例:
App.vue
<template>
<div>
<Student />
</div>
</template>
<script>
import {reactive,provide} from 'vue'
import Student from './components/Student.vue'
export default {
name: 'App',
components:{
Student
},
methods:{
changeEvent(){
this.count++
}
},
setup(){
//reactive表示多个属性的响应
const student = reactive({
name:"小明",
classname:"一年一班"
})
provide('student',student)
},
}
</script>
Student.vue
<template>
<div>
<h1>学生</h1>
<h1>name:{{name}}</h1>
<h1>classname:{{classname}}</h1>
</div>
</template>
<script>
import {ref,inject} from 'vue'
export default {
setup(props,content){
const student = inject('student')
return {...student}
},
props:['username','age']
}
</script>
结果:
五、vue3路由的基本使用
1、介绍
Vue Router是Vue.js的官方路由器。它与Vue.js核心深度集成,使使用Vue.js轻松构建单页应用程序变得轻而易举。功能包括:
- 嵌套路线映射
- 动态路由
- 模块化,基于组件的路由器配置
- 路由参数,查询,通配符
- 查看由Vue.js过渡系统提供动力的过渡效果
- 细粒度的导航控制
- 与自动活动CSS类的链接
- HTML5历史记录模式或哈希模式
- 可自定义的滚动行为
- 网址的正确编码
2、安装
npm install vue-router@next
3、入门
使用Vue + Vue Router创建单页应用程序非常简单。借助Vue.js,我们已经在使用组件组成应用程序。将Vue Router添加到混合中时,我们要做的就是将组件映射到路由,并让Vue Router知道在哪里渲染它们。
3.1、router-link
请注意a,我们如何使用自定义组件而不是使用常规标签router-link来创建链接。这使Vue Router可以更改URL而无需重新加载页面,处理URL生成及其编码。稍后我们将看到如何从这些功能中受益。
3.2、router-view
router-view将显示与该网址相对应的组件。您可以将其放置在任何位置以使其适合您的布局
App.vue
<template>
<div>
<h1>Hello App!</h1>
<!-- use the router-link component for navigation. -->
<!-- specify the link by passing the `to` prop. -->
<!-- `<router-link>` will render an `<a>` tag with the correct `href` attribute -->
<p>
<router-link to="/">Go to Home</router-link>
<br>
<router-link to="/about">Go to About</router-link>
</p>
<!-- route outlet -->
<!-- component matched by the route will render here -->
<!-- <router-view name="ShopTop"></router-view> -->
<router-view></router-view>
<!-- <router-view name="ShopFooter"></router-view> -->
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
Home.vue
<template>
<div>
<h1>这是首页</h1>
<router-link to="/page">goPage</router-link>
</div>
</template>
About.vue
<template>
<h1>这是about</h1>
</template>
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index.js'
const app = createApp(App)
// 使用路由
app.use(router)
app.mount('#app')
index.js
import {createRouter,createWebHashHistory} from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
// 1. Define route components.
// These can be imported from other files
// const Home = { template: '<div>Home</div>' }
// const About = { template: '<div>About</div>' }
// 2. Define some routes
// Each route should map to a component.
// We'll talk about nested routes later.
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = createRouter({
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
history: createWebHashHistory(),
routes, // short for `routes: routes`
})
export default router;
结果:
点击 Go to About
六、动态路由和404NotFound
1、带参数的动态路线匹配
很多时候,我们需要将具有给定模式的路由映射到同一组件。例如,我们可能有一个User
应为所有用户呈现的组件,但具有不同的用户ID。在Vue Router中,我们可以在路径中使用动态段来实现这一目标,我们称其为param:
const User = {
template: '<div>User</div>',
}
// these are passed to `createRouter`
const routes = [
// dynamic segments start with a colon
{ path: '/users/:id', component: User },
]
现在,类似/users/johnny和的URL/users/jolyne都将映射到相同的路由。
甲PARAM是一个冒号表示:
。当路由匹配时,其参数值将像this.$route.params
在每个组件中一样公开。因此,我们可以通过将User
的模板更新为来呈现当前的用户ID :
const User = {
template: '<div>User {{ $route.params.id }}</div>',
}
您可以在同一路径中具有多个参数,它们将映射到上的相应字段$route.params
。例子:
模式 | 匹配路径 | $ route.params |
---|---|---|
/ users /:用户名 | / users / eduardo | { username: ‘eduardo’ } |
/ users /:用户名/ posts /:postId | / users / eduardo / posts / 123 | { username: ‘eduardo’, postId: ‘123’ } |
此外$route.params
,该$route
对象还公开其他有用的信息,例如$route.query
(如果URL中存在查询)$route.hash
等。
2、全部捕获/ 404未找到路线
常规参数只会匹配网址片段之间的字符,并用分隔/
。如果我们想匹配任何东西,我们可以使用自定义参数正则表达式,方法是在参数后面紧随括号内添加正则表达式:
const routes = [
// will match everything and put it under `$route.params.pathMatch`
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
// will match anything starting with `/user-` and put it under `$route.params.afterUser`
{ path: '/user-:afterUser(.*)', component: UserGeneric },
]
在这种特定情况下,我们在括号之间使用自定义正则表达式,并将pathMatch
参数标记为可选的可重复的。这是为了让我们可以根据需要直接导航到路线,方法是将拆分path
为一个数组:
this.$router.push({
name: 'NotFound',
params: { pathMatch: this.$route.path.split('/') },
})
案例:
App.vue
<template>
<div>
<h1>Hello App!</h1>
<router-view></router-view>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
News.vue
<template>
<div>
<h1>新闻页{{$route.params.id}}</h1>
</div>
</template>
<script>
export default {
mounted(){
console.log(this.$route)
}
}
</script>
NotFound.vue
<template>
<h1>404,找不到页面</h1>
</template>
index.js
import {createRouter,createWebHashHistory} from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
import News from '../components/News.vue'
import NotFound from '../components/NotFound.vue'
// 1. Define route components.
// These can be imported from other files
// const Home = { template: '<div>Home</div>' }
// const About = { template: '<div>About</div>' }
// 2. Define some routes
// Each route should map to a component.
// We'll talk about nested routes later.
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{
path:"/news/:id",
component:News
},
{
path:"/:path(.*)",
component:NotFound
},
]
// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = createRouter({
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
history: createWebHashHistory(),
routes, // short for `routes: routes`
})
export default router;
结果: