7. vue-router、Vuex、pinia

177 阅读7分钟

一、vue-router

1.路由发展

路由是网络工程的一个术语:

  • 路由器主要维护一个映射表
  • 映射表会决定数据的流向
  • 路由器给每台电脑分配IP地址,即消息 -> IP地址 -> Mac地址

后端路由的映射方案:

  • 一个url对应一个网页
  • url发送网络请求,服务器正则对url匹配,交给controller进行处理
  • 生成HTML或数据返回给前端

前端路由: 页面操作修改不同的url 显示 不同的组件,页面不进行刷新

  • 修改hash值
    hash模式的原理就是 onhashChange事件 image.png
  • HTML5的history
    history模式的原理就是 onpopstate函数 + pushState函数 + replaceState函数
    history模式需要后端进行对应的配置

向浏览器历史记录中新增一条数据(地址栏中的地址会改变),但是页面不会刷新,但是如果我们给定的地址是不存在的,当我们页面手动刷新的时候,当前地址会报404错误,所以browser router需要服务器的支持(一般给予的支持:即使请求地址不存在,也是把首页的内容返回给客户端,而不是404)

image.png

SPA VS MPA

# SPA VS MPA
- spa:single page application 单页面应用
  - iframe
  - AMD/CMD + 打包工具
  - 专业的路由管理模块vue-router / react-router-dom
- MPA:multi page application 多页面应用

>区别一:应用构成
  + 多页面:由多个完整页面构成
  + 单页面:一个外科页面和多个页面片段构成
>区别二:跳转方式
  + 多页面:页面之间的跳转是从一个页面到另一个页面
  + 单页面:一个页面片段删除或者隐藏,加载另一个页面片段并显示。片段键的模拟跳转,没有开壳页面
>区别三:跳转后公共资源是否重新加载
  + 多页面: http://xxx/page1.html和http://xxx/page2.html
  + 单页面: http://xxx/shell.html#page1 和http://xxx/shell.html#page2
>区别四:用户体验
  + 多页面:页面间切换加载慢,不流畅
  + 单页面:页面片段键切换快,用户体验好
>区别五:能否实现转场动画
  + 多页面: 否
  + 单页面:可以
>区别六:页面间传递数据
  + 多页面: 依赖URL、cookie、localstorage,实现麻烦
  + 单页面:页面传递数据容易(Vuex、Vue中的父子组件通讯props对象)

2.路由的基本使用

2.1 VUE2 ROUTER

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './pages/Home';
import Custom from './pages/Custom';
import System from './pages/System';

Vue.use(VueRouter);
export default new VueRouter({
  //=>设置路由模式 hash/history,如果是history,告诉服务端404页面需要处理
  mode:"hash",
  //=>配置一级路由
  routes:[
    {
      //=>请求的是一个/,我们让其重定向到/home
      path: '/',
      redirect: '/home'
      //component: Home
    },
    {
      //=>HASH路径
      path: './home',
      //=>渲染的组件
      component: Home
    },
    {
      path: './custom',
      component: Custom,
      //=>配置某一个一级路由下面的二级路由
      children:[{
        path: '/custom/list',
        component: CustomList
      },{
        path: 'handle', //=>可以简写,但是不能写斜杠了,否则认为是根目录下访问了
        component: CustomHandle
      }]
    },
    {
      path: './system',
      component: System
    }
  ]
});

image.png

<div id="app">
  <header class="headerBox">
    <el-menu active-text-color="xx" default-active="1">
      <el-menu-item :index="1">
        //实现路由的切换和跳转
        <router-link to='/home'>首页</router-link>
      </el-menu-item>
      <el-menu-item :index="2">
        <router-link to='/custom'>客户管理</router-link>
      </el-menu-item>
      <el-menu-item :index="3">
        <router-link to='/system'>系统设置</router-link>
      </el-menu-item>
    </el-menu>  
  </header>
  //展示一级路由中各组件的内容(呈现组件的视图容器)
  <router-view></router-view>
</div>

2.2 VUE3 ROUTER

image.png

2.3 router-link的属性

image.png

3.路由懒加载

image.png

image.png

4.动态路由

image.png $route.params = $router.currentRoute.value.params
获取:当前地址下刷新页面,params中的信息也不会消失

5.NotFount页面

image.png

image.png

6.编程式路由跳转

//=>vue2通过this.$router跳转
this.$router
    go(n): 回退或者前进
    back()   =>go(-1)
    forward()=>go(1)
    push():跳转到指定的路由,实现路由切换
    => 编程式导航
//=>vue3通过useRouter返回值跳转
import { useRouter } from 'vue-router'
const router = useRouter()
router.go(n): 
      .back() =>go(-1) 
      .forward()=>go(1) 
      .push()
基于地址直接params问号传参和对象path方式跳转
vue3 router共同点:
1.传递的信息都会在地址栏中显示
2.都可以通过$route.query或 $route.currentRoute.value.query获取
区别:
1.params方式传参基于问号传参,对象方式只能基于query对象方式传参

<router-link to='/custom/list'></router-link>
<router-link to='/custom/list?lx=my'></router-link> 

<router-link :to="{path:'/custom/list'}"></router-link>
<router-link :to="{path:'/custom/list',query:{lx:'my'}}"></router-link>

7.命名路由

//=>命名路由(给每一个路由设置一个名字,后期可以基于名字实现跳转)
{
    path:'/custom',
    name:'custom',
    component:Custom,
    children:[{
        path:'/custom/list',
        name:'customList',
        component:CustomList
    },{
        path:'handle',
        name:'customHandle',
        component:CustomHandle
    }]
}
<router-link :to="{name:'customList'}"></router-link >
<router-link :to="{name:'customList',query:{lx:'my'}}"></router-link >
<router-link :to="{name:'customList',params:{lx:'my'}}"></router-link >
new Vue({
    router, //=>this.$router this.$route===$router.currentRoute
    store, //=>this.$store
    render:h=>h(app)
}).$mount('#app');

配置路由
components:{
    default:Home,
    view2:System
}
跳转
<router-view></router-view>
<router-view name="view2"></router-view>

8.动态管理路由-根据判断动态添加路由

image.png

image.png

8.1 删除路由的三种方法

  • 方式一:添加一个name相同的路由:

image.png

  • 方式二:通过removeRoute方法,传入路由的名称

image.png

  • 方式三:通过addRoute方法的返回值回调

image.png

8.2 路由的其他方法

  • router.hasRoute(): 检测路由是否存在
  • router.getRoutes(): 获取一个包含所有路由记录的数组

9.路由导航守卫

9.1 登录逻辑的导航守卫

image.png

/**
 * 全局前置钩子:不管路由匹配哪一个地址,渲染了哪一个组件,都会触发
 * 参数:
 *   + to:将要跳转的到的路由对象(query/params..)
 *   + from:从那个路由来,存储的是这个路由对象,即将要离开的路由
 * 返回值:
 *   + false:取消当前导航
 *   + 不返回或undefined:进行默认导航
 *   + 返回一个路由地址: 当前导航中断,进入到下一个导航(和$router.push一样)
 *     可以是一个string类型的路径
 *     可以是一个对象,对象中包含path、query、params等信息
 * 可选的第三个参数:next(不推荐使用)
 *   + 在vue2中我们是通过next函数来决定如何进行跳转的
 *   + 但是在vue3中我们是通过返回值来控制的,不在推荐使用next函数,这是因为开发中很容易调用多次next
 */

// 需求:进入到订单页面时,判断用户是否登录
// 一:没有登录,跳转到登录页面,进行登录的操作
// 二:已经登录,那么直接进入到订单页面
router.beforeEach((to, from) => {
  const token = localStorage.getItem("token")
  if (!token && to.path === "/order") {
    return '/login'
  }
});

9.2 导航守卫的整个解析

官方描述:

导航被触发
1.在失活的组件里调用 beforeRouteLeave 组件内守卫
2.调用全局的 beforeEach 路由前置守卫
    return '/login
3.在重用的组件里调用 beforeRouteUpdate 组件内守卫(2.2+)
    /user/123 /user/321
4.在路由配置里调用beforeEnter 路由独享守卫
    coutes:[path,component,beforeEnter(){}]
5.解析异步路由组件
    ()=>import("about.vue") -> js文件
6.在被激活的组件里调用beforeRouteEnter 组件内守卫
    about组件:beforeRouterEnter 没有this
    beforeRouueEnter(next){
        //没有this
        next((instance)=>{})
    }
7.调用全局的beforeResolve 全局解析守卫(导航被确认跳转之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用)(2.5+)
8.导航被确认
9.调用全局的afterEach钩子
10.触发DOM更新
    about template -> DOM
11.调用beforeRouteEnter 守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入
    有this
- 进入组件
  全局beforeEach -> 路由独有beforeEnter -> 组件独有beforeRouteEnter -> 全局beforeResolve ->全局afterEach -> beforeCreate —> data -> created -> beforeMount -> Mounted 
  
- 离开组件进入另一个组件
  组件独有beforeRouteLeave -> 全局beforeEach -> 路由独有beforeEnter -> 组件独有beforeRouteEnter -> 全局beforeResolve -> 全局afterEach ->beforeCreate —> data -> created -> beforeMount ->(在新组件进入渲染之前上一个组件销毁) beforedestory -> destoryed -> Mounted -> beforeUpdate ->Updataed

二、Vuex状态管理

1.注册使用Vuex

1.1 在vue2中注册使用Vuex

//=>在vue2中注册Vuex
import Vue from 'vue';
import App from './App.vue';
import store from './store/index';

Vue.config.productionTip = false;
new Vue({
  // 在每一个组件的实例中都会挂载一个$store的属性,所有的组件都可以基于this.$store获取store容器
  store,
  render: h => h(App),
}).$mount('#app');

//vue2中使用Vuex
import Vue from 'vue';
import Vuex from 'vuex';

//=>Vuex是vue中的一个插件
Vue.use(Vuex);
//=>创建store容器并且导出
const store = new Vuex.Store({
  state: {},
  //=>存储的方式等价于computed计算属性:监听当前容器中state中的计算属性
  getters: {},
  //=>mutations存储sync function,这些方法修改state的状态信息
  mutations: {},
  //=>actions存储async function:这些方法首先异步获取需要的数据,再基于commit触发mutations中的方法,从而改变state
  actions:{},
  modules:{}
});
export default store;

1.2 在vue3中注册使用Vuex

//=>在vue3中注册Vuex
import { createApp } from 'vue';
import vuex from './store'
import App from './App.vue'

const app = createApp(App);
app.use(vuex);
app.mount('#app');

//vue2中使用Vuex
import { createStore } from 'vuex'

export default createStore({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

2.vuex的执行流程

image.png

3.Vuex的基本使用

image.png

4.Vuex的state

4.1 state的基本使用

一个Vuex应用的核心就是store(仓库):
- store本质上是一个容器,它包含着你的应用中大部分的状态(state)

Vuex和单纯的全局对象有什么区别?
1. Vuex的状态存储是响应式的
    - 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新
    - 这样得到的state不会响应式的
    - 解构之后也不是响应式的,可以toRefs
2. 不能直接改变store中的状态
    - 要通过Vuex中的mutations改变state 

image.png

image.png

4.2 store的状态映射到组件中

# 1.单一状态树SSOT, Single Source of Truth
- 用一个对象包含了全部的应用层级的状态
- 这就意味着,每个应用将仅仅包含一个store实例
    - 单状态树和模块化并不冲突,后面会有module的概念

# 2.单一状态树的优势
- 如果状态保存到多个store对象中,管理会困难,所以Vuex采用单一状态树管理应用层级的全部状态
- 最直接的找到某个状态的片段
- 方便管理和维护

vue2中使用state状态

image.png

Vue3 setup中使用state状态

  • 一步一步操作

image.png

  • 封装hooks函数

image.png image.png

  • 解构state并toRefs

image.png

5. getters

5.1 基本使用

image.png

5.2 getters的映射

<template>
  <div class="home">
    <button @click="changeName">修改name</button>
    <h2>doubleCounter:{{ doubleCounter }}
    </h2>
    <h2>friendsTotalAge:{{ totalAge }}</h2>
    <h2>message:{{ message }}</h2>

    <!-- 根据ID获取某一个朋友 的信息 -->
    <h2>id-111的朋友信息:{{ getFriendById(111) }}</h2>
    <h2>id-112的朋友信息:{{ getFriendById(112) }}</h2>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  computed: {
    // 对象
    // ...mapGetters({
    //   doubleCounter: "doubleCounter",
    //   totalAge: "totalAge"
    // }),
    // 数组
    ...mapGetters(["doubleCounter", "totalAge"]),
    ...mapGetters(["getFriendById"])
  }
}
</script>

<script setup>
import { computed, toRefs } from 'vue'
import { mapGetters, useStore } from 'vuex'

const store = useStore()

// const { message: messageFn } = mapGetters(["message"])
// const message = computed(messageFn.bind({ $store: store }))

const { message } = toRefs(store.getters)
function changeName() {
  store.state.name = "Hezi"
}
</script>

6.mutations

6.1 mutations的基本使用

  • 更改Vuex中的store中的状态的唯一方法:提交mutation

在组件中使用

<template>
  <div class="home">
    <button @click="changeName">修改name</button>
    <button @click="incrementLevel">递增level</button>
    <button @click="changeInfo">修改info</button>
    <h2>Store Name:{{ $store.state.name }}</h2>
    <h2>Store Level:{{ $store.state.level }}</h2>
  </div>
</template>

<script setup>
import { useStore } from 'vuex';
import { CHANGE_INFO } from '../../store/mutation_types'

const store = useStore()

function changeName() {
  // store.state.name =  "xxx"
  store.commit("changeName", "小王")
}

function incrementLevel() {
  store.commit("incrementLevel")
}

function changeInfo() {
  store.commit(CHANGE_INFO, {
    name: "王二",
    level: 99
  })
}
</script>

定义常量

export const CHANGE_INFO = "CHANGE_INFO"

在Vuex中定义方法

import { createStore } from 'vuex'
import { CHANGE_INFO } from './mutation_types'

const store = createStore({
  state: () => ({
    counter: 100,
    name: 'wx',
    level: 100,
    avatarURL: "http://xxxx",
    friends: [
      { id: 111, name: "wx", age: 20 },
      { id: 112, name: 'we', age: 30 },
      { id: 113, name: "wr", age: 25 }
    ]
  }),
  getters: {
    // 1.基本使用
    doubleCounter(state) {
      return state.counter * 2
    },
    totalAge(state) {
      return state.friends.reduce((preValue, item) => {
        return preValue + item.age
      }, 0)
    },
    // 2.在getters属性中,获取其他的getters
    message(state, getters) {
      return `name:${state.name}; level:${state.level}; friendTotalAge:${getters.totalAge}`
    },
    // 3.getters支持返回一个函数
    getFriendById(state) {
      return function (id) {
        const friend = state.friends.find(item => item.id === id)
        return friend
      }
    }
  },
  mutations: {
    increment(state) {
      state.counter++
    },
    incrementLevel(state) {
      state.level++
    },
    changeName(state, payload) {
      state.name = payload
    },
    [CHANGE_INFO](state, payload) {
      state.level = payload.level
      state.name = payload.name
    }
  },
  actions: {
  },
  modules: {
  }
})

export default store

6.2 mutations的映射

vue2

<template>
  <div class="home">
    <button @click="changeName('小王')">修改name</button>
    <button @click="incrementLevel">递增level</button>
    <button @click="changeInfo({ name: '大王', level: 99 })">修改info</button>
    <h2>Store Name:{{ $store.state.name }}</h2>
    <h2>Store Level:{{ $store.state.level }}</h2>
  </div>
</template>

<script>
import { mapMutations } from 'vuex';
import { CHANGE_INFO } from '../../store/mutation_types'

export default {
  methods: {
    ...mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
  }
}
</script>

vue3

<template>
  <div class="home">
    <button @click="changeName('小王')">修改name</button>
    <button @click="incrementLevel">递增level</button>
    <button @click="changeInfo({ name: '大王', level: 99 })">修改info</button>
    <h2>Store Name:{{ $store.state.name }}</h2>
    <h2>Store Level:{{ $store.state.level }}</h2>
  </div>
</template>

<script setup>
import { useStore, mapMutations } from 'vuex';
import { CHANGE_INFO } from '../../store/mutation_types'

const store = useStore()

// 手动的映射和绑定
/**
 * 原则:mutation 必须是同步函数
 *  + devtools会记录mutation的日记
 *  + 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照
 *  + 如果执行异步操作,就无法追踪到数据的变化
 */

const mutations = mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
const newMutations = {}

Object.keys(mutations).forEach(key => {
  newMutations[key] = mutations[key].bind({ $store: store })
})
const { changeName, incrementLevel, changeInfo } = newMutations

</script>

7.actions

7.1 基本使用

image.png

image.png

7.2 actions的映射

vue2

<template>
  <div class="home">
    <h2>当前计数:{{ $store.state.counter }}</h2>
    <h2>name:{{ $store.state.name }}</h2>
    <button @click="incrementAction">发起action修改counter</button>
    <button @click="changeNameAction('Hezi')">发起action修改name</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex';
export default {
  methods: {
    ...mapActions(["incrementAction", "changeNameAction"])
  }
}
</script>

vue3

<template>
  <div class="home">
    <h2>当前计数:{{ $store.state.counter }}</h2>
    <h2>name:{{ $store.state.name }}</h2>
    <button @click="increment">发起action修改counter</button>
    <button @click="changeName">发起action修改name</button>
  </div>
</template>

<!-- <script>
import { mapActions } from 'vuex';
export default {
  methods: {
    ...mapActions(["incrementAction", "changeNameAction"])
  }
}
</script> -->

<script setup>
import { toRefs } from 'vue';
import { mapActions, useStore } from 'vuex';

const store = useStore()

/* 
1. 在setup中使用辅助函数
const actions = mapActions(["incrementAction", "changeNameAction"])
const newActions = {}
Object.keys(actions).forEach(key =>{
  newActions[key] = actions[key].bind({ $store: store})
})

const { incrementAction, changeNameAction } = newActions
*/
// 2.使用默认的用法
function increment() {
  store.dispatch("incrementAction")
}
function changeName() {
  store.dispatch("changeNameAction", "Hezi")
}

</script>

7.3 发送网络请求

image.png image.png home页面

<template>
  <div class="home">
    <h2>Home Page</h2>
    <ul>
      <template v-for="item in $store.state.banners" :key="item.acm">
        <li>{{ item.title }}</li>
      </template>
    </ul>
  </div>
</template>

<script setup>
import { useStore } from 'vuex';

const store = useStore()
// 告诉Vuex发起网络请求
// 要使得dispatch能返回promise,则在actions中异步请求的时候要返回promise,调用resolve返回数据,然后在组件中then的时候就知道代码调用了
store.dispatch("fetchHomeMultidataAction").then(res => {
  console.log("home中的then被回调:", res);
})

</script>

8.module模块

8.1 基本使用

image.png vuex

image.png

home模块


export default {
  state: () => ({
    // 服务器数据
    banners: [],
    recommends: []
  }),
  mutations: {
    changeBanners(state, banners) {
      state.banners = banners
    },
    changeRecommends(state, recommends) {
      state.recommends = recommends
    }
  },
  actions: {
    fetchHomeMultidataAction(context) {
      // fetch("http://123.207.32.32:8000/home/multidata").then(res => {
      //   return res.json()
      // }).then(data => {
      //   console.log(data);
      // })

      return new Promise(async (resolve, reject) => {
        const res = await fetch("http://123.207.32.32:8000/home/multidata")
        const data = await res.json()

        // 修改state的数据
        context.commit("changeBanners", data.data.banner.list)
        context.commit("changeRecommends", data.data.recommend.list)
        resolve("sss")
      })
    }
  }
}

home页面

<template>
  <div class="home">
    <h2>Home Page</h2>
    <ul>
      <!-- 获取数据:需要从模块中获取  -->
      <template v-for="item in $store.state.home.banners" :key="item.acm">
        <li>{{ item.title }}</li>
      </template>
    </ul>
  </div>
</template>

<script setup>
import { useStore } from 'vuex';

const store = useStore()
store.dispatch("fetchHomeMultidataAction").then(res => {
  console.log("home中的then被回调:", res);
})

</script>

8.2 模块默认和命名空间

模块默认

vuex image.png count模块

const counter = {
  state: ()=> ({
    count: 99
  }),
  getters: {
    doubleCount(state, getters, rootState) {
      return state.count + rootState.rootCounter
    }
  },
  mutations: {
    incrementCount(state) {
      state.count++
    }
  },
  actions: {
    incrementCountAction({state, commit, rootState}) {
      commit("incrementCount")
    }
  }
}

export default counter

home页面

<template>
  <div class="home">
    <h2>Home Page</h2>
    <!-- 
      1.使用state时,是需要state.moduleName.xxx
      2.使用getters时,是直接getters.xxx
      3.mutations、actions和getters一样,默认已经合并在Vuex里面了
     -->
    <h2>Count模块的count:{{ $store.state.count.count }}</h2>
    <h2>Count模块的doubleCount:{{ $store.getters.doubleCount}}</h2>

    <button @click="incrementCount">count模块+1</button>
  </div>
</template>

<script setup>
import { useStore } from 'vuex';

const store = useStore()
// 派发事件和提交mutation时,默认也是不需要跟模块名称
function incrementCount(){
  store.dispatch("incrementCountAction")
}

</script>

命名空间

image.png

vuex image.png count模块

const counter = {
  namespaced: true,
  state: ()=> ({
    count: 99
  }),
  getters: {
    doubleCount(state, getters, rootState) {
      return state.count + rootState.rootCounter
    }
  },
  mutations: {
    incrementCount(state) {
      state.count++
    }
  },
  actions: {
    incrementCountAction({state, commit, rootState}) {
      commit("incrementCount")
    }
  }
}

export default counter

home页面

<template>
  <div class="home">
    <h2>Home Page</h2>
    <!-- 
      加上命名空间:$store.getters["模块名/xxx"]
     -->
    <h2>Count模块的count:{{ $store.state.count.count }}</h2>
    <h2>Count模块的doubleCount:{{ $store.getters["count/doubleCount"] }}</h2>

    <button @click="incrementCount">count模块+1</button>
  </div>
</template>

<script setup>
import { useStore } from 'vuex';

const store = useStore()
// 派发事件和提交mutation时,需要跟模块名称
function incrementCount() {
  store.dispatch("count/incrementCountAction")
}

</script>

8.3 module修改或派发根组件

image.png

三、pinia

1.pinia和Vuex的区别

1.mutations不再存在
2.更友好的typeScript支持,
3.不再有module是的嵌套结构
  可以灵活使用每一个store,他们是通过扁平化的方式相互使用的
4.没有命名空间的概念

2.pinia的基本使用

  • 创建store image.png
  • 什么是store image.png
  • 定义一个store image.png
  • 使用store image.png

3.操作state

home页面

<template>
  <div class="home">
    <h2>home page</h2>
    <h2>name:{{ name }}</h2>
    <h2>age:{{ age }}</h2>
    <h2>level:{{ level }}</h2>
    <button @click="changeState">修改state</button>
    <button @click="resetState">重置state</button>
  </div>
</template>

<script setup>
import { storeToRefs } from 'pinia';
import useUser from '@/stores/user'

const userStore = useUser()

const { name, age, level } = storeToRefs(userStore)

// 修改state
function changeState() {
  // 1.一个个的修改状态
  // userStore.name = "Hezi"
  // userStore.age = 20
  // userStore.level = 200

  // 2.一次性修改多个状态
  // userStore.$patch({
  //   name: "Hezi",
  //   age: 30
  // })

  // 3.替换state为新的对象:这样只是改掉内存中的属性值,不是改变内存地址
  const oldState = userStore.$state
  userStore.$state = {
    name: "jom",
    level: 300
  }
  // console.log(oldState === userStore.$state);//true
}

// 重置state
function resetState() {
  userStore.$reset()
}

</script>

<style scoped>

</style>

user store

import { defineStore } from "pinia";

const useUser = defineStore("user", {
  state: ()=> ({
    name: "wx",
    age: 18,
    level: 100
  })
})

export default useUser

4.操作getters

image.png

home组件

<template>
  <div class="home">
    <h2>home page</h2>
    <h2>doubleCount:{{ counterStore.doubleCount }}</h2>
    <h2>doubleCountAddOne:{{ counterStore.doubleCountAddOne }}</h2>
    <h2>friend-111:{{ counterStore.getFriendById(111) }}</h2>
    <h2>showMessage:{{ counterStore.showMessage }}</h2>
  </div>
</template>


<script setup>
import { storeToRefs } from 'pinia';
import useCounter from '@/stores/counter';

const counterStore = useCounter()

</script>

<style scoped>

</style>

userStore 演示getters

// 定义关于counter的store
import { defineStore } from "pinia";

import useUser from "./user";

const useCounter = defineStore("counter", {
  state: () => ({
    count: 99,
    friends: [
      { id: 111, name: 'wx' },
      { id: 112, name: 'wy' },
      { id: 113, name: 'wz' },
    ]
  }),
  getters: {
    // 1.基本使用
    doubleCount(state) {
      return state.count * 2
    },
    // 2.一个getter引入另外一个getter
    doubleCountAddOne(state, getters) {
      return this.doubleCount + 1
    },
    // 3.getters支持返回一个函数
    getFriendById(state) {
      return function (id) {
        return state.friends.find(item => item.id === id)
      }
    },
    // 4.getters中用到别的store的数据
    showMessage(state) {
      // 1.获取user信息
      const useStore = useUser()
      // 2.获取自己的信息
      // 3.拼接信息
      return `name:${useStore.name}-count:${state.count}`
    }
  }
})

export default useCounter

5.操作actions

image.png home

<template>
  <div class="home">
    <h2>home page</h2>
    <h2>count:{{ counterStore.count }}</h2>
    <button @click="changeState">修改state</button>

    <!-- 展示数据 -->
    <h2>轮播的数据</h2>
    <ul>
      <template v-for="item in homeStore.banners">
        <li>{{ item.title }}</li>
      </template>
    </ul>
  </div>
</template>


<script setup>
import useCounter from '@/stores/counter';
import useHome from '@/stores/home'

const counterStore = useCounter()

function changeState() {
  // counterStore.increment()
  counterStore.incrementNum(10)
}

const homeStore = useHome()
homeStore.fetchHomeMultidata().then(res => {
  console.log("fetchHomeMultidata的action已经完成了:", res);
})
</script>

<style scoped>

</style>

home.js

import { defineStore } from "pinia";

const useHome = defineStore("home", {
  state: () => ({
    banners: [],
    recommends: []
  }),
  actions: {
    /* 
    async fetchHomeMultidata(){
      const res = await fetch("http://123.207.32.32:8000/home/multidata")
      const data = await res.json()
      
      this.banners = data.data.banner.list
      this.recommends = data.data.recommend.list
      return 'aaa' // 相当于promise.resolve(aaa)
    }
    */
    fetchHomeMultidata() {
      return new Promise(async (resolve, reject) => {
        const res = await fetch("http://123.207.32.32:8000/home/multidata")
        const data = await res.json()

        this.banners = data.data.banner.list
        this.recommends = data.data.recommend.list
        resolve('aaa')
      })
    }
  }
})

export default useHome

counter.js

// 定义关于counter的store
import { defineStore } from "pinia";

import useUser from "./user";

const useCounter = defineStore("counter", {
  state: () => ({
    count: 99,
    friends: [
      { id: 111, name: 'wx' },
      { id: 112, name: 'wy' },
      { id: 113, name: 'wz' },
    ]
  }),
  getters: {
    // 1.基本使用
    doubleCount(state) {
      return state.count * 2
    },
    // 2.一个getter引入另外一个getter
    doubleCountAddOne(state, getters) {
      return this.doubleCount + 1
    },
    // 3.getters支持返回一个函数
    getFriendById(state) {
      return function (id) {
        return state.friends.find(item => item.id === id)
      }
    },
    // 4.getters中用到别的store的数据
    showMessage(state) {
      // 1.获取user信息
      const useStore = useUser()
      // 2.获取自己的信息
      // 3.拼接信息
      return `name:${useStore.name}-count:${state.count}`
    }
  },
  actions: {
    increment(){
      this.count++
    },
    incrementNum(num){
      this.count += num
    }
  }
})

export default useCounter