十二、Vue全家桶 – Pinia状态管理 及 网络请求库axios库

2022/8/31 Vuepiniaaxios

pinia中文文档 (opens new window)

axios官方文档 (opens new window)

# 1、Pinia和Vuex的对比

# 1.1.什么是Pinia呢?

  • Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近piña(西班牙语中的菠萝)的词;
    • Pinia开始于大概2019年,最初是作为一个实验为Vue重新设计状态管理,让它用起来像组合式API(Composition API)。
    • 从那时到现在,最初的设计原则依然是相同的,并且目前同时兼容Vue2、Vue3,也并不要求你使用Composition API;
    • Pinia本质上依然是一个状态管理的库,用于跨组件、页面进行状态共享(这点和Vuex、Redux一样);

1.png

# 1.2.Pinia和Vuex的区别

  • 那么我们不是已经有Vuex了吗?为什么还要用Pinia呢?
    • Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法;
    • 最终,团队意识到Pinia已经实现了Vuex5中大部分内容,所以最终决定用Pinia来替代Vuex;
    • 与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的仪式,提供了 Composition-API 风格的 API;
    • 最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持;
  • 和Vuex相比,Pinia有很多的优势:
    • 比如mutations 不再存在:
      • 他们经常被认为是 非常 冗长;
      • 他们最初带来了 devtools 集成,但这不再是问题;
    • 更友好的TypeScript支持,Vuex之前对TS的支持很不友好;
    • 不再有modules的嵌套结构:
      • 你可以灵活使用每一个store,它们是通过扁平化的方式来相互使用的
      • 也不再有命名空间的概念,不需要记住它们的复杂关系;

# 2、如何使用Pinia?

  • 使用Pinia之前,我们需要先对其进行安装:
yarn add pinia
# or with npm
npm install pinia
1
2
3
  • 创建一个pinia并且将其传递给应用程序:
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

createApp(App).use(createPinia()).mount('#app')
1
2
3
4
5

# 3、pinia的Store实例

# 3.1.认识Store

  • 什么是Store?

  • 一个 Store (如 Pinia)是一个实体,它会持有为绑定到你组件树的状态和业务逻辑,也就是保存了全局的状态;

  • 它有点像始终存在,并且每个人都可以读取和写入的组件;

  • 你可以在你的应用程序中定义任意数量Store管理你的状态

  • Store有三个核心概念:

    • state、getters、actions;
    • 等同于组件的data、computed、methods;
    • 一旦 store 被实例化,你就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性;

# 3.2.定义一个Store

  • 定义一个Store:
    • 我们需要知道 Store 是使用 defineStore() 定义的
    • 并且它需要一个唯一名称作为第一个参数传递
  • .../src/store/main.js文件
import { definePinia } from 'pinia'
export const useMain = definePinia('main',{
  // id:'main',//如果definePinia没有传入第一个参数name,而是直接传入一个对象,那么我们可以在这里设置id;是等价的
  state(){
    return {
      counter:0
    }
  }
})
1
2
3
4
5
6
7
8
9
  • 这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。

    • 第一个参数是应用程序中 store 的唯一 id
  • 返回的函数统一使用useX作为命名方案,这是约定的规范

# 3.3.使用定义的Store

  • Store在它被使用之前是不会创建的,我们可以通过调用use函数来使用Store:
    • 以下代码案例,借用3.2 中.../src/store/main.js文件
<template>
  <div class='home'>
    <h2>Counter: {{mainStore.counter}}</h2>  
  </div>
</template>
<script setup>
  import { useMain } from '@/store/main'
  
  const mainStore = useMain()
</script>
1
2
3
4
5
6
7
8
9
10
  • 注意Store获取到后不能被解构,那么会失去响应式:
    • 为了从 Store 中提取属性同时保持其响应式,您需要使用storeToRefs()。
<template>
  <div class='home'>
    <h2>Counter: {{mainStore.counter}}</h2>  
    <h2>Counter: {{counter}}</h2>  
    <h2>Counter2: {{counter2}}</h2>  
    <h2>Counter3: {{counter3}}</h2>  
    <button @click="mainStore.counter++">counter++</button>
  </div>
</template>
<script setup>
  import { useMain } from '@/store/main'
  import { storeToRefs } from 'pinia';
  import { toRefs } from 'vue'
  
  const mainStore = useMain()
  
  const { counter } = mainStore //数据不是响应式的
  const { counter:counter2 } = toRefs(mainStore) //数据是响应式的 - 用vue3提供的toRefs方法
  const { counter:counter3 } = storeToRefs(mainStore) //数据是响应式的 - 用pinia提供的storeToRefs方法
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 4、Store实例的State

# 4.1.认识和定义State

  • state 是 store 的核心部分,因为store是用来帮助我们管理状态的。
    • 在 Pinia 中,状态被定义为返回初始状态的函数;
import { definePinia } from 'pinia'
export const useMain = definePinia({
  id:'main'
  state(){
    return {
      counter:0,
      name:'kobe',
      age:45
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11

# 4.2.操作State(一)

  • 读取和写入 state:
    • 默认情况下,您可以通过 store 实例访问状态来直接读取和写入状态;
  • 重置 State:
    • 你可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值;

2.png

# 4.3.操作State(二)

  • 改变State:
    • 除了直接用 store.count++ 修改 state,你还可以调用 $patch 方法;
    • 它允许您使用部分“state”对象同时应用多个更改;
  • 合并State:
    • 您可以通过将其 $state 属性设置新对象来跟 Store 的整个状态进行合并:

3.png

# 5、Store实例的Getters

# 5.1.认识和定义Getters

  • Getters相当于Store的计算属性:
    • 它们可以用 defineStore() 中的 getters 属性定义;
    • getters中可以定义接收一个state作为参数的函数;
//.../src/store/main.js
import { defineStore } from 'pinia'
export const useMain =  defineStore('main',{
  state(){
    return {
      counter:0,
      firstName:'kobe',
      lastName:'bryant'
    }
  },
  getters:{//getters中可以定义接收一个state作为参数的函数;
    
    doubleCounter:state => state.counter * 2,
    doublePlusOne(state) {
      return state.counter * 2 + 1
      //return this.doubleCounter + 1 //我们可以通过this来访问到当前store实例的所有其他属性;
    }
    fullName(state) {
      return state.firstName + ' ' + state.lastName
    }
    
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 5.2.访问Getters(一)

  • 访问当前store的Getters:
    • 以下代码案例使用5.1 的useMain的数据
<template>

</template>
<script setup>
  import { useMain } from '@/store/main'
  const mainStore = useMain()
  console.log(mainStore.doubleCounter)
  console.log(mainStore.fullName)
</script>
1
2
3
4
5
6
7
8
9
  • Getters中访问自己的其他Getters
    • 我们可以通过this来访问到当前store实例的所有其他属性;
//.../src/store/main.js
import { defineStore } from 'pinia'
export const useMain =  defineStore('main',{
  state(){
    return {
      counter:0,
      firstName:'kobe',
      lastName:'bryant'
    }
  },
  getters:{
    doubleCounter:state => state.counter * 2,
    doublePlusOne(state) {
      //return state.counter * 2 + 1 + state.lastName
      return this.doubleCounter + 1 + this.lastName //我们可以通过this来访问到当前store实例的所有其他属性;
    }    
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 访问其他storeGetters
    • 如:.../src/store/user.js 中 有 state状态为:nickname:'曼巴精神'
//.../src/store/main.js
import { defineStore } from 'pinia'
import { useUser } from './user'
export const useMain =  defineStore('main',{
  state(){
    return {
      counter:0,
      firstName:'kobe',
      lastName:'bryant'
    }
  },
  getters:{
    fullName(state) {
      return state.firstName + ' ' + state.lastName
    }
    message(state) {//访问其他store的Getters
      const userStore = useUser()
      return this.fullName + ':' + userStore.nickname
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 5.3.访问Getters(二)

  • Getters也可以返回一个函数,这样就可以接受参数:

4.png

# 6、Store实例的Actions

# 6.1.认识和定义Actions

  • Actions 相当于组件中的 methods。
    • 可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑;
  • 和getters一样,在action中可以通过this访问整个store实例的所有操作;
//.../src/store/counter.js
import { defineStore } from "pinia";

export const useCounter = defineStore({
  id:'counter',
  state() {
    return {
      counter:11
    }
  },
  getters:{
    nineCounter(state) {
      return this.counter * 9
    }
  },
  actions: {
    increment() {
      this.counter++
    },
    randomCounter() {
      this.counter = Math.floor(Math.random() *100)
      console.log(this.nineCounter)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 使用useCounter -->
<template>
  <div class="category">
    <h2>我是Category页面</h2>
    <h2>counter:{{counterStore.counter}}</h2>
    <h2>counter*9(getters处理):{{counterStore.nineCounter}}</h2>
    <button @click="counterStore.counter++">counter+1</button>
    <button @click="randomCounterFn">生成[0,100)随机整数</button>
  </div>
</template>

<script setup>
  import { useCounter } from '@/store/counter';
  
  const counterStore = useCounter()
  function randomCounterFn() {
      counterStore.randomCounter()
  }
</script>

<style scoped>

</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 6.2.Actions执行异步操作

  • 并且Actions中是支持异步操作的,并且我们可以编写异步函数,在函数中使用await;

5f1045a0d3d1ef924.png

  • 具体pinia 中辅助函数mapState,mapGetters,mapActions看pinia官方文档学习;具体用法跟之前学习vuex中辅助函数的用法差别不大

# 7、认识axios

  • 为什么选择axios? 作者推荐和功能特点

6.png

  • 功能特点:

    • 在浏览器中发送 XMLHttpRequests 请求
    • 在 node.js 中发送 http请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求和响应数据
    • 等等
  • 补充: axios名称的由来? 个人理解

    ➢ 没有具体的翻译.

    ➢ axios: ajax i/o system.

# 8、axios请求方式

  • 支持多种请求方式:
    1. axios(config)
    2. axios.request(config)
    3. axios.get(url[, config])
    4. axios.delete(url[, config])
    5. axios.head(url[, config])
    6. axios.post(url[, data[, config]])
    7. axios.put(url[, data[, config]])
    8. axios.patch(url[, data[, config]])
  • 有时候, 我们可能需求同时发送两个请求
    • 使用axios.all, 可以放入多个请求的数组.【axios.all方法内部的原理其实就是Promise的类方法all即:Promise.all()】
    • axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2

# 9、常见的配置选项config

  • 请求地址

    • url: '/user',
  • 请求类型

    • method: 'get',
  • 请根路径

    • baseURL: 'http://www.mt.com/api',
  • 请求前的数据处理

    • transformRequest:[function(data){}],
  • 请求后的数据处理

    • transformResponse: [function(data){}],
  • 自定义的请求头

    • headers:{'x-Requested-With':'XMLHttpRequest'},
  • URL查询对象

    • params:{ id: 12 },
  • 查询对象序列化函数

    • paramsSerializer: function(params){ }
  • request body

    • data: { key: 'aa'},
  • 超时设置

    • timeout: 1000,

# 10、axios的创建实例:axios.create

  • 为什么要创建axios的实例呢?
    • 当我们从axios模块中导入对象时, 使用的实例是默认的实例;
    • 当给该实例设置一些默认配置时, 这些配置就被固定下来了.
    • 但是后续开发中, 某些配置可能会不太一样;
    • 比如某些请求需要使用特定的baseURL或者timeout等.
    • 这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.
import axios from 'axios'
const instance = axios.create({//创建一个axios实例  跟直接用axios等价
  baseURL:"http://123.207.22.22:1888"
})

instance.post("/param/postjson",{
  name:'kobe',
  age:46
}).then(res => {
  console.log("res:",res)
})
1
2
3
4
5
6
7
8
9
10
11

# 11、请求和响应拦截器

  • axios的也可以设置拦截器:拦截每次请求和响应
    • axios.interceptors.request.use(请求成功拦截, 请求失败拦截)
    • axios.interceptors.response.use(响应成功拦截, 响应失败拦截)
import axios from 'axios'
axios.interceptors.request.use(config => {
  console.log('请求成功拦截')
  return config
},err => {
  console.log("请求失败拦截")
  return err
})

axios.interceptors.response.use(res => {
  console.log('响应成功拦截')
  return res.data
},err => {
  console.log("响应失败拦截")
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 12、axios请求库封装(简洁版)

789ad725996d44a52.png

最后更新时间: 2022/09/27, 23:57:30
彩虹
周杰伦