Vue基础 _ Day05
1. Vue路由
什么是路由?
一个路由 ( route ) 就是一组映射关系( key - value ),分为后端路由和前端路由。
多个路由 ( routes ) 需要经过路由器( router )的处理。
后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源,切换页面时每次都向后台服务器发出请求,服务器响应请求,会打开新页面或者刷新页面,它的 key 是URL地址,value 是一个函数( function ),用于处理客户端提交的请求。
后端路由的工作过程:服务器接收到一个请求时,根据请求路径,找到匹配的函数来处理请求,返回响应数据。
前端路由:对于 单页面应用程序 来说,主要通过URL中的hash(#号) 来实现不同页面之间的切换,每次跳转到不同的URL都是使用前端的锚点路由,不会重新请求和刷新页面。同时,hash有一个特点:HTTP请求中不会包含hash相关的内容,这种通过hash改变来切换页面的方式,称作前端路由(其中的一种,还有一种history模式,不带#号)
单页面应用程序 ( Single Page Web Application,SPA)
它的 key 是hash的值,value 是 component 组件。
前端路由的工作过程:当浏览器的路径改变时,对应的组件就会显示,不会跳转到新页面。
路由的基本使用
- 引入js文件,这个js需要放在vue.js后,自动安装(提供了一个VueRouter的构造方法)
- 使用 new VueRouter() 构造方法创建路由实例,构造方法可以接收一个参数,参数类型为对象类型
- 在参数对象中配置属性
routes:[]
,这个数组中的元素也是对象,对象里包含path属性和component属性
path属性是 路由的路径,即在哪个路径下显示哪个组件 ,component属性就是这个路径下要显示的组件(直接传组件的对象,不加引号)
- 创建的路由需要和vue实例关联一下,将路由实例挂载在vue实例上
- 在可操作区域中预留路由的显示位置,即指定在特定路径时,路由中的组件显示在哪个位置,需要使用标签:
<router-view></router-view>
下面是一个案例,但是需要在地址栏手动添加
/index路径
才会跳转至路由:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 1.引入Vue-router.js文件 -->
<script src="https://unpkg.com/vue-router@3.0.0/dist/vue-router.js"></script>
</head>
<body>
<div id='app'>
<!-- 5.预留显示位置 -->
<router-view></router-view>
</div>
</body>
<script>
// 直接创建component对象,这种方式不能在操作区域中写<index></index>标签
let index = {
template: "<h1>首页</h1>"
}
// 2.创建router对象
const router = new VueRouter({
// 3.配置路由路径、显示组件
routes: [
{
path: '/index',
component: index
}
]
})
const vm = new Vue({
// 4.将路由实例挂载在Vue实例上
router,
el: '#app',
data: {
},
methods: {
},
})
</script>
</html>
路由的跳转
路由的跳转需要使用:router-link
标签,标签中有一个 to
属性,它的属性值是路由的路径;
router-link
标签在页面中默认展示为<a>
标签,可以通过tag属性来设置自定义标签;
<div id='app'>
<router-link to="/index">去首页</router-link>
<!-- 5.预留显示位置 -->
<router-view></router-view>
</div>
router-link
标签效果如下,点击后即可在下方的预留位置显示index组件的内容:
设置
tag属性
的属性值为 div:
<div id='app'>
<router-link to="/index" tag="div">去首页</router-link>
<!-- 5.预留显示位置 -->
<router-view></router-view>
</div>
设置后便没有了<a>
标签的特性,但同时也会具有自定义标签的某种特性,并且仍然可以点击后跳转至对应路由。
路由的重定向
在路由实例中的routes[]
里创建路由时,添加redirect
属性就可以实现路由的重定向,比如将默认路径'/'
重定向为 '/index'
,那么在初始页面就会直接跳转到 /index 路径,并在预留显示位置处显示index组件:
const router = new VueRouter({
// 3.配置路由路径、显示组件
routes: [
{
// 初始页面路径重定向
path: '/',
redirect: '/index'
},
{
path: '/index',
component: index
},
]
})
选中路由高亮
Vue-router 提供了一个现成的类名.router-link-active
来设置选中路由的样式,我们只需要在 CSS 中自定义自己喜欢的样式即可,比如选中后变红,字体变大:
<style>
.router-link-active {
color: red;
font-size: 25px;
}
</style>
同时,Vue-router 也允许我们通过设置linkActiveClass
属性自定义此类名,它和 routes[]
属性平级,在创建路由实例时设置即可,比如:
<style>
.iChooseYou {
color: red;
font-size: 25px;
}
</style>
<script>
const router = new VueRouter({
// 自定义选中路由高亮类名
linkActiveClass: "iChooseYou",
// 3.配置路由路径、显示组件
routes: [
{
// 初始页面路径重定向
path: '/',
redirect: '/index'
},
{
path: '/index',
component: index
},
]
})
</script>
路由的嵌套
创建路由实例时,在路由对象中设置 children属性
即可实现路由的嵌套,它的属性值是一个数组,数组里的元素是路由对象,这个数组中的路由对象对应的组件在路径匹配显示时就会渲染在它父组件的<router-view>
中。
只要是路由对象都可以设置children属性,故可以层层嵌套!
下面的代码中嵌套的路由对象的path属性没有加'/'
,而是直接写的路径名称,意思是 使用相对路径
,相对于父组件的路径下的路径。
实际路径就是:'/index/login/personal'
下面是一个三级路由嵌套案例( 首页>登录页>个人中心页 ):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 1.引入Vue-router.js文件 -->
<script src="https://unpkg.com/vue-router@3.0.0/dist/vue-router.js"></script>
</head>
<style>
.iChooseYou {
color: red;
font-size: 25px;
}
</style>
<body>
<div id='app'>
<router-link to="/index" tag="button">去首页</router-link>
<!-- 5.预留显示位置 -->
<router-view></router-view>
</div>
<template id="index">
<div>
<h1>首页</h1>
<router-link to="/index/login" tag="button">去登录</router-link>
<router-view></router-view>
</div>
</template>
<template id="login">
<div>
<h1>我是登录页,我的上级路由是首页</h1>
<router-link to="/index/login/personal" tag="button">去个人中心页</router-link>
<router-view></router-view>
</div>
</template>
</body>
<script>
// 直接创建component对象,这种方式不能在操作区域中写<index></index>标签
let index = {
template: "#index"
}
let login = {
template: "#login"
}
let personal = {
template: "<h1>我是个人中心页,我的上级路由是登录页</h1>"
}
// 2.创建router对象
const router = new VueRouter({
// 自定义选中路由高亮类名
linkActiveClass: "iChooseYou",
// 3.配置路由路径、显示组件
routes: [
{
// 初始页面路径重定向
path: '/',
redirect: '/index'
},
{
// 首页是一级路由
path: '/index',
component: index,
children: [
{
// 登录页是二级路由
path: 'login',
component: login,
children: [
{
// 个人中心页是三级路由
path: "personal",
component: personal
}
]
}
]
},
]
})
const vm = new Vue({
// 4.将路由实例挂载在Vue实例上
router,
el: '#app',
data: {
},
methods: {
},
})
</script>
</html>
路由传参 - query传参
路由传参分为 query传参 和 params传参,query传参比较简单,直接在路径后写参数即可,参数的格式为经典的query形式:?key1=value1&key2=value2&...
<router-link to="/index/login/personal?id=1&name=张三" tag="button">去个人中心页</router-link>
接收参数需要靠$route
对象,$route
对象下有一个 query属性
,它的属性值也是一个对象,里面有我们传递过去的参数,比如上面传递的参数 id:1,name:‘张三’,取值只需要$route.query.参数名
:
<template id="personal">
<div>
<h1>我是个人中心页,我的上级路由是登录页</h1>
<h2>我拿到了上级传来的参数id:{{this.$route.query.id}}</h2>
<h2>我拿到了上级传来的参数name:{{$route.query.name}}</h2>
</div>
</template>
但是这种写法把参数写死了, 如果要动态传递上级路由的参数可以改成模板字符串的形式,这时候需要对
to
属性进行数据绑定,加一个冒号即可:
<template id="login">
<div>
<h1>我是登录页,我的上级路由是首页</h1>
<!-- 跳转路由query传参,to属性的模板字符串写法 -->
<router-link :to=`/index/login/personal?id=${id}&name=${name}` tag="button">去个人中心页</router-link>
<router-view></router-view>
</div>
</template>
<script>
let login = {
template: "#login",
data() {
return {
id: 233,
name: "芜湖"
}
}
}
</script>
然后这种写法在多个参数的情况下维护起来非常的麻烦,所以还有另一种 to属性的对象写法,query传参推荐写成这种形式:
<template id="login">
<div>
<h1>我是登录页,我的上级路由是首页</h1>
<!-- 跳转路由query传参,to属性的对象写法 -->
<!-- 属性名和属性值重复可以简写 -->
<router-link :to="{
path:'/index/login/personal',
query:{
id,
name
}
}" tag="button">去个人中心页</router-link>
<router-view></router-view>
</div>
</template>
<script>
let login = {
template: "#login",
data() {
return {
id: 233,
name: "芜湖"
}
}
}
</script>
注意,这种写法即使不传递动态参数,只写一个path属性 也是需要对to属性进行数据绑定的! 否则会直接按字符串进行解析,地址栏的路径会变成这种形式:
/index/{path:'/index/login/personal'}
命名路由
我们已经发现,在路由嵌套的情况下,跳转至子路由的路径的时,必须要带上它所有的上级路由的路径,这么写起来就非常麻烦。
我们可以通过给路由起名的方式来解决这个问题,简化路由的跳转。
操作方法非常简单,直接给路由对象添加 name属性
即可:
<script>
{
// 个人中心页是三级路由
name:"geren",
path: "personal",
component: personal,
}
</script>
设置完毕后,跳转至三级路由时就可以由原来的:
to = "/index/login/personal"
变为:
:to="{name:'geren'}"
而且也可以直接query传参:
:to="{ name:'geren', query:{ id:123 } }"
注意,这种写法必须要写成对象形式并进行数据绑定,否则路径会按字符串格式解析,无法正常跳转
路由传参 - params传参
params 传参比 query 传参麻烦了一丢丢,需要先在路由对象的path属性中声明参数占位符:path: "personal/:id/:name"
然后 to 属性中原来的query传参的形式要变为:to = "/index/login/personal/666/张三"
然后接收参数由原来的: $route.query.参数名
→ $route.params.参数名
参数的名称就是你在 path
路径中,自己写的占位符的名称,例如 :id
对应的参数名就是 id,对应的数据就是 666 ,取参时就这么写:$route.params.id。
当然,params传参也有模板字符串的形式和对象形式,模板字符串的形式:
<body>
<router-link :to=`/index/login/personal/${id}/${name}` tag="button">去个人中心页</router-link>
</body>
对象的形式:
<router-link :to="{
name:'geren',
params:{
id,
name
}
}" tag="button">去个人中心页</router-link>
注意:路由携带params参数时,如果使用to的对象写法,则必须使用name配置项!不能使用path配置项!!!
命名视图
官方解释:
有时候想同时(同级)展示多个视图,而不是嵌套展示,
例如创建一个布局,有 sidebar(侧导航) 和 main(主内容) 两个视图,
这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,
而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。
确保正确使用 components 配置 (带上 s):
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
路由函数式跳转
非常的简单,主要用到一个 push 方法
<button @click="toIndex">回到主页按钮</button>
<button @click="toMine">去个人中心按钮</button>
toIndex() {
this.$router.push({ path: '/index' })
},
toMine() {
this.$router.push({
name: "my",
params: { userid: "芜湖" }
})
},
2. 计算属性和监视属性
计算属性
组件中有一个 computed 配置项,它是一个对象,可以在里面设置计算属性的方法,来描述依赖响应式状态的复杂逻辑,官方文档的代码案例:
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
// 一个计算属性的 getter
publishedBooksMessage() {
// `this` 指向当前组件实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
我们在这里定义了一个计算属性 publishedBooksMessage。
更改此应用的 data 中 books 数组的值后,可以看到 publishedBooksMessage 也会随之改变。
计算属性默认仅能通过计算函数得出结果。当你尝试修改一个计算属性时,你会收到一个运行时警告。
只在某些特殊场景中你可能才需要用到 “可写” 的属性,你可以通过同时提供 getter 和 setter 来创建:
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
现在当你再运行 this.fullName = 'John Doe'
时,setter 会被调用而 this.firstName 和 this.lastName 会随之更新。
比如 HTML 中有一个input输入框,双向数据绑定了 fullName:
<input type="text" v-model="fullName">
这时输入框里的值发生变化,this.firstName 和 this.lastName 会随之改变。
set什么时候调用? 当fullName被修改时。
需要注意的是,如果在计算属性中的
fullName
是要回到Vue实例上的,那么data(){return}
中的数据就不能再有一个fullName了,即它们两者之间不能有重名的。
计算属性的简写
当你确定了属性只会被读取而不会被改变时可以采用简写的方式,比如上面的fullName
可以简写为:
fullName(){
return this.firstName + ' ' + this.lastName
},
监视属性
来自尚硅谷视频:
第一种写法:
第二种写法:
watch
选项期望接受一个对象,其中键是需要侦听的响应式组件实例属性 (例如,通过 data 或 computed 声明的属性)——值是相应的回调函数。该回调函数接受被侦听源的新值和旧值。
值也可以是一个方法名称的字符串 (通过 methods 声明),或包含额外选项的对象。当使用对象语法时,回调函数应被声明在 handler 中。
额外的选项包含: immediate、deep、flush、onTrack / onTrigger
immediate:在侦听器创建时立即触发回调。第一次调用时,旧值将为 undefined。
其他详情请移步官方文档
声明侦听器回调时避免使用箭头函数,因为它们将无法通过 this 访问组件实例。
深度监视
有嵌套结构时会出现 watch 侦听器监视不到的问题,比如我们想监视下面的 a 属性和 b 属性:
我们可能会写'numbers.a':{},'numbers.b':{}
(直接写numbers.a会报错),但是如果 numbers 里的属性非常多,我们又需要监视numbers里所有的属性的变化,这么写就非常不方便了,此时只需要在侦听器中配置deep属性为true即可。
监视属性的简写
如果你确定不需要用到 immediate 和深度监视 deep,那么就可以使用简写,用到其中的任何一个都不能使用简写!
简写的格式如下:
3. ref标签属性
获取到DOM元素后可以进行一系列DOM操作,获取到组件实例对象后可以进行读取里面的私有数据等操作,非常的简单
代码举例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id='app'>
<button @click="getRef">获取打了ref标识的DOM元素的文本</button>
<p ref="test">我是p标签,我打了个ref标识</p>
<son ref="sonModule"></son>
<button @click="changeRef">通过ref改变子组件son里的数据</button>
</div>
<template id="son">
<div>
<img src="./top.jpg" alt="" style="width: 50px;">
<p v-if="flag">{{msg}}</p>
<p v-else>芜湖</p>
</div>
</template>
</body>
<script>
const vm = new Vue({
el: '#app',
data: {
},
methods: {
getRef() {
console.log(this.$refs.test.innerText);
},
changeRef() {
this.$refs.sonModule.msg = "芜湖123123"
this.$refs.sonModule.flag = !this.$refs.sonModule.flag
}
},
components: {
"son": {
template: "#son",
data() {
return {
msg: "子组件sonModule",
flag: true
}
}
}
}
})
</script>
</html>
但要注意的是:如果具有ref属性的标签在v-for循环中遍历,则以 this.$refs.xxx
的形式不能获取到DOM元素,而是以数组的形式获取: this.$refs[xxx][索引]