Vue Router
是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:
- 嵌套路由映射
- 动态路由选择
- 模块化、基于组件的路由配置
- 路由参数、查询、通配符
- 展示由 Vue.js 的过渡系统提供的过渡效果
- 细致的导航控制
- 自动激活 CSS 类的链接
- HTML5 history 模式或 hash 模式
- 可定制的滚动行为
- URL 的正确编码
请查看 Vue Router 的文档
vue-router传参的四种方式、
http://t.csdn.cn/TAtvW
安装
npm install vue-router@4
方式一:vue3+js
在router/index.js里
//这里放的是路由规则
import {createRouter,createWebHashHistory,createWebHistory} from 'vue-router'
//1.定义路由组件
import PageOrderManage from '@/pages/secondary_page/PageOrderManage';
import PageUserManage from '@/pages/secondary_page/PageUserManage';
import PageRiderManage from '@/pages/secondary_page/PageRiderManage';
import PageStoreManage from '@/pages/secondary_page/PageStoreManage';
// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [
{ path: '/order', component: PageOrderManage },
{ path: '/user', component: PageUserManage },
{ path: '/rider', component: PageRiderManage },
{ path: '/store', component: PageStoreManage },
//传数传递第一种
{ path: '/query', component:()=>import('@/pages/secondary_page/PageQuery'),uid:'', version:'1.0.2',s:''},
//传数传递第二种
{ path: '/restful/:uid/:type',component:()=>import('@/pages/secondary_page/PageRestful') },
//如果路由没有匹配到,显示404
{ path: '/:pageMatch(.*)*', component:()=>import('@/pages/PageNotFound')},
]
// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
//createWebHistory() 不带#号
//createWebHashHistory() 带有#号
history: createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
//前置路由守卫
//to 到哪里去
//from 从哪里来
//next 下一步(放行)
router.beforeEach((to, from, next) => {
console.log('前置路由守卫执行');
console.log('from',from)
console.log("to",to)
//假装拦截
if(to.params.type == 0){
next("/404")
}
next();
})
export default router
homePage.vue文件中
<template>
<LeftNavigation class="head-navi" :pageRouter="pageRouter" :navList="navList"></LeftNavigation>
<HeadNavigation class="left-navi" :logout="logout" :userInfo="userInfo"></HeadNavigation>
<div class="se-page">
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<el-dialog v-model="centerDialogVisible" title="Warning" width="30%" center>
<span>
确定退出登录吗?
</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="centerDialogVisible = false">取消</el-button>
<el-button type="primary" @click="logoutApi">
确定
</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import LeftNavigation from '@/components/LeftNavigation.vue';
import HeadNavigation from '@/components/HeadNavigation.vue';
import {getUserInfo} from '@/api/home'
import {userLogout} from '@/api/home'
import { ElMessage } from 'element-plus'
//提升框导入
import { ref } from 'vue'
const centerDialogVisible = ref(false)
export default {
name:'PageHome',
methods:{
//路由跳转
pageRouter(url,query={},restful={}){
if(query.length > 0){
//字符串转对象
var queryObj = JSON.parse(query);
//带参路由跳转
this.$router.push({
path:url,
query:queryObj
})
}else if(restful.length > 0){
//字符串转对象
var restfulObj = JSON.parse(restful);
//带参路由跳转
this.$router.push(`${url}/${restfulObj.uid}/${restfulObj.type}`)
}else{
//普通路由跳转
this.$router.push(url)
}
},
logout(){//退出登录
this.centerDialogVisible = ref(true)
},
logoutApi(){//用户确定退出登录
//发送请求
userLogout().then(
response=>{
if(response.code == 200){
localStorage.removeItem('token')
this.$store.commit('logout')
ElMessage.error(response.msg)
}
}
)
}
},
components:{
LeftNavigation,
HeadNavigation,
},
data(){
return{
centerDialogVisible : ref(false),
userInfo:{},
navList:[
{name:'用户管理',icon:'icon-icon-geren',url:'/user'},
{name:'订单管理',icon:'icon-icon-dingdan',url:'/order'},
{name:'骑手管理',icon:'icon-icon-waimai',url:'/rider'},
{name:'商家管理',icon:'icon-shangjia',url:'/store'},
{name:'404页面',icon:'',url:''},
{name:'参数传递一',icon:'',url:'/query',query:`{"uid":1001,"s":"张三"}`},
{name:'参数传递二',icon:'',url:'/query',query:`{"uid":1002,"s":"李四"}`},
{name:'restful风格一',icon:'',url:'/restful',restful:`{"uid":1003,"type":"0"}`},
{name:'restful风格二',icon:'',url:'/restful',restful:`{"uid":1004,"type":"1"}`},
]
}
},
mounted(){
getUserInfo().then(
response=>{
this.userInfo=response.data;
console.log('成功',response)
}
)
}
}
</script>
<style scoped>
.left-navi,.head-navi{
float: left;
}
.se-page{
width: calc(100% - 220px);
height: calc(100vh - 51px);
float: left;
}
</style>
组件LeftNavigation.vue 中
<template>
<div class="navi">
<div class="system-name">
咕咕外卖
</div>
<ul class="navi-item">
<li v-for="(item,index) in navList" @click="changePage(item.url,$event)" :key="index" :data-query="item.query" :data-restful="item.restful">
<i :class="item.icon != ''? 'iconfont '+item.icon : 'iconfont icon-icon-geren'"></i>
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name:"LeftNavigation",
props:['pageRouter','navList'],
methods:{
changePage(url,e){
url= url==""?"/404":url;
this.pageRouter(url);
//获取点击标签上的自定义属性的值
const query = e.currentTarget.dataset.query;
const restful = e.currentTarget.dataset.restful;
//没定义query,就不传
if(query != undefined){
this.pageRouter(url,query)
}else if(restful != undefined){
this.pageRouter(url,{},restful)
}else{
this.pageRouter(url)
}
}
}
}
</script>
<style scoped>
.navi{
width: 220px;
height: calc(100vh);
background-color: #20222A;
color: rgba(255, 255, 255, .8);
}
.system-name{
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .15);
}
.navi-item{
list-style: none;
width: 100%;
}
.navi-item>li{
width: 90%;
height: 56px;
line-height: 56px;
padding: 0 5%;
cursor: pointer;
}
.navi-item>li:hover{
color: #fff;
}
</style>
方式二: vue3+vite+ts
在router/index.ts里
import { createRouter, createWebHistory } from 'vue-router'
import PageUserManager from '@/views/secondary-page/PageUserManage.vue';
import PageOrderManager from '@/views/secondary-page/PageOrderManage.vue';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'userManager',
component: PageUserManager
},
{
path: '/order',
name: 'order',
component: PageOrderManager
},
{
path: '/store',
name: 'store',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('@/views/secondary-page/PageStoreManage.vue')
},
{
path: '/rider',
name: 'rider',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('@/views/secondary-page/PageRiderManage.vue')
},
{
path: '/:pageMatch(.*)*',
component: () => import('@/views/error-page/404.vue'),
// meta: { hidden: true },
},
//传数传递第一种
{ path: '/query',
name:"query",
component:()=>import('@/views/secondary-page/PageQuery.vue'),
props: route => ({ uid: route.query.uid , s: route.query.s})
},
//传数传递第二种
{ path: '/restful/:uid/:type',
name:"restful",
component:()=>import('@/views/secondary-page/PageRestful.vue') },
]
})
export default router
homePage.vue
<script setup lang="ts">
import LeftNavigation from '@/components/LeftNavigation.vue';
import {RouterView} from 'vue-router'
//提升框导入
import { ref } from 'vue'
const navList = ref([
{name:'用户管理',icon:'icon-icon-geren',url:'/'},
{name:'订单管理',icon:'icon-icon-dingdan',url:'/order'},
{name:'骑手管理',icon:'icon-icon-waimai',url:'/rider'},
{name:'商家管理',icon:'icon-shangjia',url:'/store'},
{name:'404页面',icon:'',url:'/da'},
{name:'参数传递一',icon:'',url:'/query',query:{uid:1001,s:"张三"}},
{name:'参数传递二',icon:'',url:'/query',query:{uid:1002,s:"李四"}},
{name:'restful风格一',icon:'',url:'restful',restful:{uid:1003,type:"0"}},
{name:'restful风格二',icon:'',url:'restful',restful:{uid:1004,type:"1"}},
])
</script>
<template>
<LeftNavigation class="head-navi" :navList="navList"></LeftNavigation>
<div class="se-page">
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<RouterView/>
</div>
</template>
<style scoped>
.left-navi,.head-navi{
float: left;
}
.se-page{
width: calc(100% - 220px);
height: calc(100vh);
float: left;
}
</style>
LeftNavigation.vue
<script lang="ts">
import { defineComponent } from 'vue'
import { RouterLink } from 'vue-router'
export default defineComponent({
// 启用了类型推导
props: {
navList:Object
},
mounted() {
}
})
</script>
<template>
<div class="navi">
<div class="system-name">
咕咕外卖
</div>
<ul class="navi-item">
<li v-for="(item,index) in navList" :key="index">
<RouterLink :to="{path:item.url,query:{uid: 1002 ,s: '张山1'}}" v-if="item.url == '/query' ">
<i :class="item.icon != ''? 'iconfont '+item.icon : 'iconfont icon-icon-geren'"></i>
{{ item.name }}
</RouterLink>
<RouterLink :to="{name:item.url,params:{uid: 1001,type: '2'}}" v-else-if="item.url == 'restful' ">
<i :class="item.icon != ''? 'iconfont '+item.icon : 'iconfont icon-icon-geren'"></i>
{{ item.name }}
</RouterLink>
<RouterLink :to="item.url" v-else>
<i :class="item.icon != ''? 'iconfont '+item.icon : 'iconfont icon-icon-geren'"></i>
{{ item.name }}
</RouterLink>
</li>
</ul>
</div>
</template>
<style scoped>
.navi{
width: 220px;
height: calc(100vh);
background-color: #20222A;
color: rgba(255, 255, 255, .8);
}
.system-name{
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .15);
}
.navi-item{
list-style: none;
width: 100%;
}
.navi-item>li{
width: 90%;
height: 56px;
line-height: 56px;
padding: 0 5%;
text-align: center;
cursor: pointer;
}
.navi-item>li:hover{
color: #fff;
}
</style>
Pinia:
Pinia 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。
相比于 Vuex,Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导。
Pinia从使用角度和之前的Vuex一样。
Store是保存状态与业务逻辑的实体,有三个核心概念。
◆ state:存储全局状态
◆ getters:类似于组件的computed,根据已有state封装派生数据,也具有缓存的特性
◆ actions:类似于组件的methods,用来封装业务逻辑,支持同步和异步
安装
yarn add pinia
# 或者使用 npm
npm install pinia
在 src/main.ts 文件,使用Vue.use()方法将pinia作为插件使用:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
createApp(App).use(createPinia()).mount('#app')
2、使用
参数1:是对仓库的命名,名称必须具备唯一性;
参数2:配置的选项对象,即state、getters、actions,其中state的写法必须是函数,为了避免在服务端交叉请求导致的状态数据污染,而且必须是箭头函数,为了更好的TS类型推导。
创建store文件:index.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
export const isLoginStore = defineStore('isLogin', () =>{
const isLogin = ref(false)
function login(){
isLogin.value = true
}
function logout(){
isLogin.value = false
}
return { isLogin, login, logout }
})
1、使用 store
通过 import 导入 javascript 模块的方式引入,引入后,直接使用变量接收即可。
<script setup lang="ts">
import {useCounterStore ,isLoginStore} from '../store/index';
// setup内不用导出,定义变量即可使用
const store = useCounterStore ();
const loginStore = isLoginStore()
</script>
对比Vuex
从以上的Pinia定义store和使用store,可以看出,Pinia不同于Vuex:
Vuex:
Vuex的store需要一个主入口
通过modules属性,拆分成不同的模块
自动挂载在Vue实例上,通过this.$store去调用或者mapGetters等方法
Pinia:
Pinia的store不需要主入口
直接创建不同的store文件即可实现多模块
在使用时,直接通过javascript的模块导入,即可使用,可方便看到从哪个文件导入
2)State(数据)
state存储store的数据部分,Pinia的state是一个返回不同data对象的函数,完整类型推断建议使用箭头函数。
非常类似于我们组件定义中的data项。
import { defineStore } from 'pinia'
export default defineStore('myGlobalState', {
// other options
state: () => {
return {
count: 1,
message: 'Hello world',
phone: 13811111199
}
}
});
1、使用state
以javascript中的模块导出的方式导出store数据,state中的数据均可通过变量.state数据名获取:
直接获取:
<script setup lang="ts">
import useCommonStore from '../store/index';
const store = useCommonStore();
</script>
<template>
<div>
{{ store.count}}
</div>
</template>
解构获取:
store是一个reactive响应式对象,直接解构会使其失去响应式,类似setup中的props,为了既可解构又可保持其响应式,可使用storeToRefs,它将为每个reactive属性创建refs
2、修改state
- 直接修改state:
import { useStore } from '@/store'
const store = useStore()
const handleClickChangeOne = () => {
store.count++
store.message = 'hahah'
}
- $patch以对象形式修改:
const handleClickChangeTwo = () => {
store.$patch({
count: store.count + 1,
message: 'Abalam',
})
}
缺点: 如果只需修改state数据中的某一项,仍然需要将整个对象传给store。
3. $patch接收函数:
接收一个函数做为参数,函数参数为state对象,可直接针对要修改的属性进行修改。
const handleClickChangeThree = () => {
store.$patch((state) => {
state.count = store.count + 1
state.message = 'Abalam'
})
}
- 替换state:
可以通过设置 store 的 $state 属性为一个新对象,来替换 store 的整个state。
const handleClickChangeFour = () => {
store.$state = {
count: count + 1,
message: 'Abalam',
}
}
- 重置state:
可以通过 store 的 $reset() 方法重置 state 的值为初始值,比如修改了name、库存等,可一键重置,将值初始化为初始状态的值。
store.$reset()
- 订阅state
通过store的$subscribe()
方法监听state及其变化,类似于Vuex的subscribe方法。与常规watch()相比,使用$subscribe()的优点是,在patch之后,ubscribe
只会触发一次。
// 监听数据变化
store.$subscribe((mutation, state) => {
console.log(mutation, state)
})
当我们触发页面上更改 store 的操作时,则会触发 subscribe 监听,监听函数有两个参数 mutation 和 state。
mutation:包含3个参数
type:操作类型,‘direct’ | ‘patch object’ | ‘patch function’
storeId:操作的store id
events:操作的事件详情,包括针对的数据、新值、旧值等
state:Proxy类型的对象
- state订阅与组件分离
默认情况下,状态订阅绑定到添加它们的组件(如果store是在组件的setup()中)。也就是说,当卸载组件时,它们将自动删除。如果要在卸载组件后保留它们,可将{detached:true}作为第二个参数传递,以从当前组件分离state订阅。
store.$subscribe(function () {
console.log('')
}, { detached: true })
在pinia实例上监听整个state
watch(
store,
(state) => {
// 每当它发生变化时,将整个状态持久化到本地存储
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
Getters 完全等同于 store 中 state 的 computed values。可以使用defineStore() 中的 getters 属性定义它们。
接收state作为第一个参数,推荐使用箭头函数。
定义getters
getters: {
phoneHidden (state) {
console.log('Getter被调用')
return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
使用getters
<p>getter:{{store.phoneHidden}}</p>
访问其他getters
与计算属性一样,可以组合多个 getters,通过this.去访问其他getters。
other-sotre.ts
import { defineStore } from "pinia";
export const useOtherStore = defineStore('main', {
state: () => ({
data: [1, 2, 3]
})
})
index.ts
getters: {
phoneHidden (state) {
console.log('Getter被调用')
return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
},
otherGetter (state) {
const otherStore = useOtherStore()
return otherStore.data
},
},
给getters传递参数
getters只是一个计算属性,因此不可能向其传递任何参数。但是,可以从getters返回一个函数来接受任何参数。
export default defineStore('common', {
getListById: state => {
return (id: number) => state.list.find((item) => item.id === id);
}
});
<script setup lang="ts">
import useCommonStore from '../store/common';
const common = useCommonStore();
const { getListById } = storeToRefs(useCommonStore());
</script>
<template>
<div> {{ getListById(2) }} </div>
</template>
注意:使用这种方式的时候,getters 不再被缓存,只是函数调用。
Actions 相当于组件中的方法,可以用 defineStore() 中的 actions 属性定义,非常适合定义业务逻辑。
定义actions
export const useStore = defineStore('common', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
使用actions
同步的方式:
<script setup lang="ts">
import useCommonStore from '../store/common';
const common = useCommonStore();
</script>
<template>
<button @click="common.increment()">触发actions</button>
</template>
异步的方式:
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// let the form component display the error
return error
}
},
},
})
- 访问其他store的actions
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
订阅actions
使用store.$onAction()订阅actions,传递给它的回调函数在action之前执行,after在actions resolves之后执行,onError在actions抛出异常和错误的时候执行。
const unsubscribe = someStore.$onAction(
({
name, // name of the action
store, // store instance, same as `someStore`
args, // array of parameters passed to the action
after, // hook after the action returns or resolves
onError, // hook if the action throws or rejects
}) => {
// a shared variable for this specific action call
const startTime = Date.now()
// this will trigger before an action on `store` is executed
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// this will trigger if the action succeeds and after it has fully run.
// it waits for any returned promised
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// this will trigger if the action throws or returns a promise that rejects
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// manually remove the listener
unsubscribe()
$onAction 一般是在组件的 setup 建立,它会随着组件的 unmounted 而自动取消。如果你不想让它取消订阅,可以将第二个参数设置为 true:
someStore.$onAction(callback, true)