likes
comments
collection
share

VUE3浅析---pinia和proxy

作者站长头像
站长
· 阅读数 18

一、pinia:仓库

1、概念:

Pinia 是 Vue3 的存储库,代替 Vuex 成为VUE3的状态管理工具。相比于 Vuex 它有以下优势: - 不存在mutations,存储数据的方式更加简化。 - 在组件上可以直接做存储库中的数据的修改,并且都是响应式的。 - 贴合ts。

2、安装并使用

2.1、安装

yarn add pinia 或者 npm install pinia

2.2、使用:在src目录下定义stores/store.ts文件,使用如下代码初始化pinia仓库并在main.ts中启用pinia

pinia常用的模块有三个:

  • state:存储数据的仓库
  • action:用来定义处理state的方法操作,在组件中可以直接调用actions里面定义好的方法处理state中的值,但是注意,如果使用箭头函数那么this指向就会发成变化。
  • getters:相当于computed,可以用来监听state中值的变化的。
import { defineStore } from "pinia";

const useUserStore = defineStore({
    // 当前存储库的唯一键
    id: "user",

    // 存储库,他必须以一个函数的形式存在,在函数里面返回具体要存储的数据或者对象
    state: () => ({
        name: "Jerry",
        age: 20,
        userInfo: "",
    }),

    // 相当于计算属性 computed
    getters: {
        userInfo: (state) => "姓名:" + state.name + "--年龄:" + state.age,
    },

    // 可以处理异步或者同步方法逻辑
    actions: {},
});

const useGoodsStore = defineStore({
    // 当前存储库的唯一键
    id: "goods",

    // 存储库,他必须以一个函数的形式存在,在函数里面返回具体要存储的数据或者对象
    state: () => ({
        name: "衣服",
        price: 100,
        goodsInfo: "",
    }),

    // 相当于计算属性 computed
    getters: {
        goodsInfo: (state) => "名称:" + state.name + "--价格:" + state.price,
    },

    // 可以处理异步或者同步方法逻辑
    actions: {},
});

export { useGoodsStore, useUserStore };

// 在main.ts中启用pinia
import { createPinia } from 'pinia'
app.use(createPinia())

2.3、在组件中改变仓库中的数据

  • 直接赋值
  • 使用$patch使用对象改变值
  • 使用$patch使用函数改变值,使用函数的好处就是能增减逻辑判断
  • 直接使用goodsStore.$state直接改变值,但是要写全定义的所有的变量
  • 直接调用actions中定义的方法:goodsStore.setGoodName()
<script setup lang="ts">
import { useGoodsStore, useUserStore } from './stores/store'
import { ref } from 'vue'

// 定义两个v-mode的变量,用于接受输入值
const goodsName = ref<string>('')
const userName = ref<string>('')

// 调用store里面定义的两个钩子函数,初始化仓库
const userStore = useUserStore()
const goodsStore = useGoodsStore()

const changeGoodsInfo = () => {
  console.log('changeGoodsInfo')
  // goodsStore.name = goodsName.value // a、直接赋值
  // goodsStore.$patch({ // b、使用`$patch`使用对象改变值
  //   name: goodsName.value
  // })

  // goodsStore.$patch((store) => { // c、 使用`$patch`使用函数改变值,使用函数的好处就是能增减逻辑判断
  //   if (goodsName.value != '') {
  //     store.name = goodsName.value
  //   }
  // })

  // goodsStore.$state = { // d、直接使用`goodsStore.$state`直接改变值,但是要写全定义的所有的变量
  //   name: goodsName.value,
  //   price: 1.2,
  //   goodsInfo: '姓名:' + goodsName.value + '--年龄:' + 1.2
  // }

  goodsStore.setGoodName() // e、直接调用`actions`中定义的方法:`goodsStore.setGoodName()`
}

const changeUserInfo = () => {
  console.log('changed')
  userStore.name = userName.value
}
</script>

<template>
    <div>
        <p>用户信息:{{ userStore.userInfo }}</p>
        <p>商品信息:{{ goodsStore.goodsInfo }}</p>

        <br />
        <hr />
        姓名:<input type="text" v-model="userName" />
        <button @click="changeUserInfo">提交用户信息</button>
        <br />
        <hr />
        名称:<input type="text" v-model="goodsName" />
        <button @click="changeGoodsInfo">提交商品信息</button>
    </div>
</template>

<style scoped></style>

2.4、结构store:结构之后,不具有哦响应式,要想保留响应式,就必须使用pinia提供的storeToRefs进行转化

import { storeToRefs } from 'pinia'

const goodsStore = useGoodsStore()
// let { name, price } = goodsStore
let { name, price } = storeToRefs(goodsStore)
const changeGoodsInfo = () => {
	console.log('changeGoodsInfo', name, price)
	price.value += 1
}

// storeToRefs的源码解析
function storeToRefs(store) {
	// 判断当前是Vue2的直接使用toRefs进行包裹处理
    if (isVue2) {
        return toRefs(store);
    }
    else {
	    // 拿到store的原始对象,实际上处理之后就是一个Object对象
        store = toRaw(store);
        // 最终返回的就是refs集合,下边如果发现store中的ref或者reactive对象之后,就会被放到refs集合中
        const refs = {};
        for (const key in store) {
            const value = store[key];
            if (isRef(value) || isReactive(value)) {
                // 将store中的ref或者reactive对象放到refs集合中
                refs[key] =
                    // ---
                    toRef(store, key);
            }
        }
        return refs;
    }
}

2.5、getters和actions:用于操作state中的属性

  // 相当于计算属性 computed:getters中的方法当state中的任何一个属性变化时,就会给监听到
  getters: {
    goodsInfo: state => { 
      console.log('goodsInfo', '=====')
      return '名称:' + state.name + '--价格:' + state.price
    },
    getGoodsInfo(): string {
      console.log('getGoodsInfo', '=====')
      return '名称:' + this.name + '--价格:' + this.getGoodsPrice + '元' // 这里使用箭头函数的话,this指向就会出现问题,无法使用this调用getGoodsPrice
    },
    getGoodsPrice: state => {
      console.log('getGoodsPrice', '=====')
      state.price *= 100
      return state.price
    }
  },

// 定义Goods类型
type Goods = {
    name: string;
    price: number;
};

// 模拟一个异步方法:3秒后返回数据
const setGoodsInfo = (): Promise<Goods> => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                name: "iPad Pro",
                price: 29.96,
            });
        }, 3000);
    });
};

// 可以处理异步或者同步方法逻辑
  actions: {
    setGoodsName() {
      this.name = 'Zara'
      this.setGoodsPrice() // 普通防范中调用普通方法
    },
    setGoodsPrice() {
      this.price = 200
    },

// 在actions中可以使用异步和同步方法,
    async getGoodsInfo1() {
      const result = await setGoodsInfo() // 同步调用setGoodsInfo获取数据,3秒后返回数据
      this.name = result.name
      this.price = result.price
      this.setGoodsName() // 异步方法中调用普通方法
    }
  },

3、实例API

3.1、$patch:可以直接操作state里面的值
  // goodsStore.$patch({ // b、使用`$patch`使用对象改变值
  //   name: goodsName.value
  // })

  // goodsStore.$patch((store) => { // c、 使用`$patch`使用函数改变值,使用函数的好处就是能增减逻辑判断
  //   if (goodsName.value != '') {
  //     store.name = goodsName.value
  //   }
  // })

  // goodsStore.$state = { // d、直接使用`goodsStore.$state`直接改变值,但是要写全定义的所有的变量
  //   name: goodsName.value,
  //   price: 1.2,
  //   goodsInfo: '姓名:' + goodsName.value + '--年龄:' + 1.2
  // }
3.2、$reset:将state中的值还原成最近一次改变的值
const changeGoodsInfo = () => {
    console.log("changeGoodsInfo", name, price);

    goodsStore.getGoodsInfo1();
    setTimeout(() => {
        goodsStore.$reset(); // 还原成最近一次改变的值
    }, 5000);
};

3.3、$subscribe:当state中的值任意一个发生变化的时候,就会触发该函数
/**
 * 当state中的值任意一个发生变化的时候,就会触发该函数
 * 
 * args: 里面会记录新旧值
 * state:就是当前操作的state的实例
 * options: 是一个对象,比如detached,这是一个boolean参数,当这个参数为true时,表明即使当前组件销毁后,也继续监控state里面值的变化,可选
 */
 goodsStore.$subscribe((args, state) => {
  console.log('args', args)
  console.log('state', state)
},{
  detached: true
})
3.4、$onAction:当调用actions里面的函数的时候,就会触发该函数
/**
 * 当调用actions里面的函数的时候,就会触发改函数
 *
 * args:接收参数,里面封装了多个api:
 *      args.after:当$onAction里面的逻辑执行完成之后才会执行args.after函数逻辑,所以args.after放置的位置于执行顺序无关
 *      args.onError:当调用actions里面的函数发生错误时,args.onError函数也会执行
 *      args.args:接收调用actions里面的函数传递的参数,是一个数组
 *      args.name:执行的actions里面的函数的名称
 * detached: 这是一个boolean参数,当这个参数为true时,表明即使当前组件销毁时,也继续监控actions里面的函数调用,可选
 */
goodsStore.$onAction((args) => {
    args.after(() => console.log("args.after", "===="));
    console.log("args", args);
}, true);

4、pinia持久化

当我们刷新页面或者跳转到下一个组件的时候,组件中展示的pinia中的值会被丢失,即还原成默认值,所以要实现将pinia中更改过的值持久化,这里采用localstore。

import { PiniaPluginContext } from "pinia";
import { toRaw } from "vue";

/**
 * 存储localStorage
 *
 * @param key 给定的key
 * @param value 给定的值
 */
const setLocalStorage = (key: string, value: string) => {
    try {
        localStorage.setItem(key, value);
    } catch (err) {
        console.log(err);
    }
};

/**
 * 获取localStorage
 *
 * @param key 给定的key
 * @returns 返回对应的key的值
 */
const getLocalFromStorage = (key: string) => {
    try {
        return localStorage.getItem(key)
            ? JSON.parse(localStorage.getItem(key) as string)
            : {};
    } catch (err) {
        console.log(err);
    }
    return undefined;
};

// 定义一个数据类型
type Options = {
    plugin?: string;
    key?: string;
};

/**
 * pinia插件定义,函数里面包裹函数,主要是为了实现用户自定义参数,外层函数,主要在交给store注册的时候,
 * 可以由用户自定参数,而真正被store调用执行的参数是return出去的函数,该函数才会接收store返回的context,里面有state的所有信息
 *
 * @param optons 用户自定的参数
 * @returns 从localStorage存储的state中值
 */
const PiniaPersistencePluggin = (optons: Options) => {
    return (context: PiniaPluginContext) => {
        const key = optons.key ?? "__PINIA__";
        const plugin = optons.plugin ?? "localStorage";
        const { store } = context;
        const data = getLocalFromStorage(key + `${store.$id}`);
        console.log("data===get", data);
        store.$subscribe(() => {
            if (plugin && plugin == "localStorage") {
                setLocalStorage(
                    key + `${store.$id}`,
                    JSON.stringify(toRaw(store.$state))
                );
            }
        });

        return { ...data };
    };
};

// 向外暴漏插件
export { PiniaPersistencePluggin };

// mian.ts中注册使用插件
import { PiniaPersistencePluggin } from './stores/piniaplugin'

const store = createPinia()
store.use(PiniaPersistencePluggin({ plugin: 'localStorage', key: '__PINIA__' })) // 在注册使用插件的时候,可以指定state的数据存储在localStorage获取cookie获取其他,还至指定默认的key

二、proxy:代理

// 在vite.config.ts中加入如下代码,可解决dev过程时的跨域问题,实际生产环境下,用的是ngigx
  server: {
    port: 8082,
    /**
     * 这里设置的是是否启用网络地址,如果为false,启动后就不会出现ip的访问地址:
     *   ➜  Local:   http://localhost:8082/
     *   ➜  Network: http://192.168.217.240:8082/ // 如果该参数为false,就不会出现改地址
     */
    host: true,
    /**
     * 为开发服务器配置CORS。设置为true以允许来自任何源的所有方法,或者使用一个对象单独配置。
     */
    cors: true,
    // 是否使用https进行访问
    https: true,
    // 跨域设置
    proxy: {
      '/api': { // 设置前端在访问后台地址的时候,要以/api开始
        target: 'http://localhost:8989', // 访问的目标地址
        changeOrigin: true, // 允许将源地址更改为目标地址
        rewrite: (path: string) => path.replace(/^\/api/, '') // 将前端请求时候加的/api替换为'',应为/api是前端为了解决跨域问题而加的,真正的后台地址是上是没有这个的
      },
    }
  },