Vue3学习第二课

文章介绍了VueRouter在Vue.js应用中的配置和使用,包括组件化的路由定义、动态路由、参数传递以及路由守卫。同时,文章提到了Vue3中使用Vite和TypeScript的路由配置方式。此外,文章还探讨了Pinia作为Vue的状态管理库,如何通过defineStore定义模块,使用state、getters、actions,并展示了如何在组件中导入和使用store。
摘要由CSDN通过智能技术生成

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)defineStore定义容器

参数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

  1. 直接修改state:
import { useStore } from '@/store'

const store = useStore()
const handleClickChangeOne = () => {
  store.count++
  store.message = 'hahah'
}

  1. $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'
  })
}

  1. 替换state:
    可以通过设置 store 的 $state 属性为一个新对象,来替换 store 的整个state。
const handleClickChangeFour = () => {
  store.$state = {
    count: count + 1,
    message: 'Abalam',
  }
}

  1. 重置state:
    可以通过 store 的 $reset() 方法重置 state 的值为初始值,比如修改了name、库存等,可一键重置,将值初始化为初始状态的值。
store.$reset()

  1. 订阅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类型的对象
在这里插入图片描述

  1. 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 }
)

在这里插入图片描述

3)Getters(计算数据)

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 不再被缓存,只是函数调用。

4)Actions(方法)

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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值