目录
SPA:single page web application
后端路由的映射方案
浏览器向服务器通过url申请页面,然后服务器通过url映射到具体的html页面,再将html页面传递给服务器,这就是后端路由的映射方式,url 对应了一个具体的 html页面
SPA:single page web application
之前的后端方案是一个 url 对应了一个具体的 html页面
而现在的许多网站的方式不是这样,拿网易云网站举例
在点击导航栏时,例如点击 我的音乐
页面的url会发生改变,但是导航栏是没有变化的,而只变化了下面的界面
这说明这种方式,url发生变化时不会渲染整个页面,而只更新一部分
即url与组件一一对应,这就是SPA模式
其实也就是前端路由
url的hash
vue-router
基础使用
1、创建路由
先下载插件 npm i vue-router
一般会建立一个js文件,用来创建路由并保持映射关系(其实就是保存url和组件的对应关系)
//引入路由函数
import { createRouter,createWebHashHistory} from 'vue-router'
//引入组件
import page from '../components/page.vue'
import nav from '../components/navitem.vue'
//创建路由
const router = createRouter({
//确定映射模式:hash模式
history:createWebHashHistory(),
//确定映射关系
routes:[
{path:'/page',component:page},
{path:'/nav',component:nav}
]
})
//导出路由
export default router
每个对应关系是可以有独一无二的name属性的,以便后续使用
{
name: "nav",
path: '/nav',
component: nav
},
2、让路由生效
在main.js中引入路由并运用
import { createApp } from 'vue'
import App from './App.vue'
//引用路由
import router from './router/index'
const app = createApp(App)
//运用路由
app.use(router)
app.mount('#app')
3、router-view占位
在APP.vue或者其他组件中使用 router-view占位,以告诉页面当我切换url时,组件渲染在什么地方
4、router-link进行路由的切换
其实就是类似于a元素,点击时就可以切换url,使用 to属性
<template>
<router-view></router-view>
<router-link to="/page" class="link">page</router-link>
<router-link to="/nav" class="link">nav</router-link>
</template>
router-link自带class:router-link-active
选中哪个router-link,哪个router-link就会加入这个class,且具有排他作用
当然也可以自己定义按钮函数:点击按钮触发jump函数,跳转到page
const route = useRouter()
let jump = ()=>{
route.push('/page')
}
路由默认url
在初始时可以设定默认的跳转url
const router = createRouter({
history:createWebHashHistory(),
routes:[
//默认
{path:'/',redirect:'/page'},
{path:'/page',component:page},
{path:'/nav',component:nav}
]
})
异步打包
【VUE3】保姆级基础讲解(三)非父子组件通讯,$refs,动态组件,keep-alive,Composition API_独憩的博客-CSDN博客
const page = ()=>import(/* webpackChunkName: 'page' */'../components/page.vue')
const nav = ()=>import(/* webpackChunkName: 'nav' */'../components/navitem.vue')
这样在打包的时候就会单独创造js文件
注释是固定写法,既魔法注释,会告知打包时的js文件名称
动态路由匹配
对于用户或者商品数据,往往的url是 商品\商品编号 组成,例如 good\111
那么我们希望,即使url变成这个样子,也要渲染good组件,只是穿进去的id是111
用到了动态路由匹配
routes:[
{path:'/',redirect:'/page'},
{path:'/page',component:page},
{path:'/nav',component:nav},
{path:'/good/:id',component:good}
]
那么就定义了 id 用来传导编号
在使用时直接
<router-link to="/good/111" class="link">good1</router-link>
<router-link to="/good/222" class="link">good2</router-link>
在url转换成 good\111或者good\222时也能匹配到 good 组件
在good.vue中,如果想使用这个id:
<template>
<h1>{{$route.params.id}}</h1>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params);
</script>
<style scoped>
</style>
在组件中 使用 $route.params.id
在js代码中,使用useRoute()获取路由
js代码中还可以使用 route.forward()或 route.back()跳转后一个和前一个界面
如果想在切换 id时能持续获取params,需要用到onBeforeRouteUpdate
<template>
<h1>{{$route.params.id}}</h1>
</template>
<script setup>
import { useRoute , onBeforeRouteUpdate} from 'vue-router'
const route = useRoute()
onBeforeRouteUpdate((to,from)=>{
console.log(to.params);
console.log(from.params);
})
console.log(route.params);
</script>
<style scoped>
</style>
notfound
当传入的url是没经过匹配时,这时可以设定显示一个 notfound 组件
{path:'/:pathMatch(.*)',component:notfound}
嵌套路由
<template>
<h1>page</h1>
<router-view></router-view>
</template>
一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:
/page/profile /page/posts
+------------------+ +-----------------+
| page | | page |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
也就是说在 组件中再次使用 router-view占位
page.vue:
<template>
<h1>page</h1>
<router-view></router-view>
</template>
那么在配置routes时,应该加入children属性
{path:'/page',
component:page,
children:[
{
path:'profile',
component:()=>import('../components/profile.vue')
}
]},
动态路由
添加路由
使用addRoute函数直接添加根路由
router.addRoute({
name: "nav",
path: '/nav',
component: nav
})
添加嵌套路由
要将嵌套路由添加到现有的路由中,可以将路由的 name 作为第一个参数传递给 router.addRoute()
,这将有效地添加路由,就像通过 children
添加的一样:
router.addRoute("page", {
path: 'profile',
component: () => import('../components/profile.vue')
}
)
通过这种方式给page路由添加了children
删除路由
有几个不同的方法来删除现有的路由:
- 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
router.addRoute({ path: '/about', name: 'about', component: About }) // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的 router.addRoute({ path: '/other', name: 'about', component: Other })
- 通过调用
router.addRoute()
返回的回调:
当路由没有名称时,这很有用。const removeRoute = router.addRoute(routeRecord) removeRoute() // 删除路由如果存在的话
- 通过使用
router.removeRoute()
按名称删除路由:
需要注意的是,如果你想使用这个功能,但又想避免名字的冲突,可以在路由中使用router.addRoute({ path: '/about', name: 'about', component: About }) // 删除路由 router.removeRoute('about')
Symbol
作为名字。
当路由被删除时,所有的别名和子路由也会被同时删除
路由导航守卫
在进行路由跳转时,可以进行回调函数,既 守卫
有一种使用场景是:在点击跳转时,先判断是否登陆,如果登陆了就跳转到对应的路由,如果没有登陆就跳转到登陆路由
全局前置守卫beforeEach
router.beforeEach((to, from) => {
if(to.path !== "/login"){
return "/login"
}
})
其他守卫:导航守卫 | Vue Router
Vuex状态管理
状态管理概念
在一个项目中存在着很多状态,其实这个状态就是我们一直使用的变量,例如网易云音乐这个项目,播放状态(暂停or播放),现在播放的歌曲名称,播放到哪一句....都是其状态,对于这些数据的管理我们就称之为是状态管理。
在前面我们是如何管理自己的状态呢?
在Vue开发中,我们使用组件化的开发方式;
- 而在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为state;
- 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View;
- 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions;
在之前的例子中,都是在组件中用变量直接控制的,但是这样存在弊端:
- 当项目组件十分庞大,状态很多,会使得组件十分臃肿,不好维护
- 虽然我们有props和emits等数据传递方法,但是当组件很多,且组件之间不是父子关系时,数据的传递会变得十分复杂
那么就需要创建一个状态库 store来管理这些状态:
- 第一幅图表示所有的组件都可以和store内部的状态进行交互
- 第二幅图表示,组件可以直接拿到数据state,但是如果想修改,必须走 actions---mutations---state流程,具体在下文阐述
vuex的基本元素为:state ,getters,mutation,actions,modules
vuex基础使用
安装
npm i vuex
创建store对象
与路由类似,一般会在src文件夹下创建store文件夹,下面创建一个index.js文件:
import {createStore} from 'vuex'
const store = createStore({
state:()=>{
return{
counter:100
}
},
})
export default store
这里创建了一个store实例,然后定义了内部的state,采用的是函数式编程
使用createStore创建store对象,默认内部的状态是响应式的
main.js调用
在main.js中:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import store from './store/index'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
组件使用store
然后在任何一个组件中就可以使用state的数据进行展示:
<h1>{{$store.state.counter}}</h1>
在组件的js中获取数据方法:
import {useStore} from 'vuex'
const store = useStore()
console.log(store.state);
state状态映射到组件
options api computed 属性方案
当state里面的状态很多时,如果每个状态都采用 $store.state.xxx 的形式调用,会十分繁琐
那么我们希望将state状态整体映射到组件中
一般我们会使用 computed 属性,其属于options api
对于state:
import {createStore} from 'vuex'
const store = createStore({
state:()=>{
return{
counter:100,
name:'kobe',
age:18
}
},
})
export default store
在组件中可以引入mapState函数放置在computed属性中,这个函数前加 ... 表示其会返回多个函数,通过数组的形式将state中的状态映射进来,在模板中就可以直接使用了
<template>
<h1>{{name}}</h1>
</template>
<script>
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['name','age','counte'])
}
}
</script>
setup方案
但是这个操作在setup里面写就很麻烦,一般不会这么用
const store = useStore()
const {name ,age,counter} =mapState(['name','age','counter'])
const Cname = computed(name.bind({$store:store}))
从mapState中返回的是函数形式,要想使用必须放在computed属性中
但是在上述的方案中,其实底层调用store的逻辑还是 this.$store.state.xxx
但是setup 中是不存在this的,所以要使用bind属性给其设置一个this指向
直接解构(推荐)
我们一般会使用setup编写逻辑,不希望还同时使用options api,所以上述方案都不太好
可以直接解构:
import {toRefs} from 'vue'
import {useStore} from 'vuex'
const store = useStore()
const{name,age,counter} = toRefs(store.state)
这样既映射成功,还保证了其响应式
getters
基础使用
类似于options api中的computed属性,当你想对state中的状态做一些操作时,可以使用getters
import {createStore} from 'vuex'
const store = createStore({
state:()=>{
return{
counter:100,
}
},
getters:{
doubleCounter(state){
return state.counter*2
}
}
})
export default store
使用方法和state类似
<h1>{{$store.getters.doubleCounter}}</h1>
getters函数同时调用getters
getters中的函数可以相互调用
import {createStore} from 'vuex'
const store = createStore({
state:()=>{
return{
counter:100,
name:'lee',
}
},
getters:{
doubleCounter(state){
return state.counter*2
},
//相互调用
message(state,getters){
return `${state.name} need ${getters.doubleCounter} yuan`
},
}
})
export default store
getters函数返回函数
getters中的函数可以返回函数,在调用时可以传参:
import {createStore} from 'vuex'
const store = createStore({
state:()=>{
return{
friends:[
{name:"111",age:12},
{name:"112",age:13},
{name:"113",age:14},
]
}
},
getters:{
findfriend(state){
return function(name){
return state.friends.find(item=>item.name == name)
}
}
}
})
export default store
findfriend函数返回了一个函数,形参是name,这个函数又通过传入的name找到对应的friend信息返回
调用时:
<h1>{{$store.getters.findfriend(112)}}</h1>
getter函数映射到组件
跟state方法的方案类似,也会有三个方案,也会有mapGetters函数,这里不再赘述,只写一下最推荐的直接解构方案
const{findfriend} = toRefs(store.getters)
Mutation
更改VUEX中状态的唯一方法是提交mutation(其实也能直接修改,但是vuex认为不规范)
基本使用
首先在store中定义mutation方法:
mutations:{
increment(state){
state.counter++
},
changename(state){
state.name = 'kobe'
}
}
在组件中使用,必须要使用.commit方法:
<template>
<button @click="changeName">跳转到page</button>
<h1>{{$store.state.name}}</h1>
</template>
<script>
export default {
methods: {
changeName() {
this.$store.commit('changename')
}
}
}
</script>
当然也可以传入参数:
mutations:{
changename(state,newname){
state.name = newname
}
}
<script>
export default {
methods: {
changeName() {
this.$store.commit('changename','lalla')
}
}
}
</script>
映射使用
mutations也可以映射使用
同样使用 mapMutations 函数
<template>
<button @click="changename('kobe')">跳转到page</button>
<h1>{{$store.state.name}}</h1>
</template>
<script>
import { useStore, mapMutations } from 'vuex'
export default {
methods: {
...mapMutations(['changename'])
}
}
</script>
重要原则
也就是说在mutations中不要进行异步操作,例如网络请求,如果要网络请求数据,需要用到actions