likes
comments
collection
share

让Pinia写起来更简洁

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

让Pinia写起来更简洁


前言

其实Pinia已经很简洁优雅,但是我们在组织代码结构的时候还可以再少些代码。

本文简洁之处在于通过require.context()自动化导入各个store模块,最后放在一个store对象中供其他vue文件使用。

注意 本文以Vue2为例,但与Vue3写法上无明显差别

1. 为什么用pinia

  1. 模块化且按需导入,避免所有的store都被导入 让Pinia写起来更简洁
  2. Pinia 相对于 Vuex 提供了更简单的 API,更少的规范,以及 Composition-API 风格的 API。
  3. Pinia 没有 Mutations,Actions 支持同步和异步,没有模块的嵌套结构。

2. 安装pinia

  • 在Vue2中使用

npm i pinia @vue/composition-api(安装pinia和组合api) 想了解细节可参考官方安装文档

  • 在Vue3中使用

npm i pinia

3. 引入pinia

项目目录

  ├── src  
	  ├── api
	  ├── assets
	  ├── components
	  ├── router
	  ├── store
		  ├── modules
		      ├── user.js
		      ├── login.js
                 ├── temp
                     ├── test.js
		  ├── index.js
	  ├── views
	  ├── App.vue
	  └── main.js

3.1 具体代码

在store文件夹下的index文件中引入并导出,然后挂载到vue上

/*./src/store/index.js*/

//在index文件中引入pinia,最后导出
import Vue from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)

const pinia = createPinia()
// 导入modules文件夹下的所有js文件,包含子文件夹下的js文件
const modulesFiles = require.context('./modules', true, /\.js$/)
//modulesFiles.keys() 得到文件路径 ['./login.js', './temp/test.js', './user.js']
const store = modulesFiles.keys().reduce((pre, cur) => {

  const moduleName = cur.replace(/^\.\/(.*)\.\w+$/, "$1");
  pre[moduleName] = modulesFiles(cur).default;
  return pre;
}, {});

console.log(store) //打印结果看 #3.2关于代码中的require.context() 的图片

export {storeToRefs} from 'pinia'
export default pinia
export {store}

---------------------------------------------------------------------------------------
/*main.js*/

import pinia from "./store";

new Vue({
  router,
  pinia,
  render: h => h(App)
}).$mount("#app");

3.2 关于代码中的require.context()

//语法: 
require.context(directory,useSubdirectories,regExp)

*   `directory`:表示检索的目录
*   `useSubdirectories`:表示是否检索子文件夹
*   `regExp`:匹配文件的正则表达式,一般是文件名
  1. const modulesFiles = require.context('./modules', true, /\.js$/)

作用:返回 modules目录下的所有后缀为.js模块的引用,包含子目录

  1. modulesFiles.keys() 得到文件路径 ['./login.js', './temp/test.js', './user.js']

  2. 关于代码中用的正则表达式

/^\.\/(.*)\.\w+$/ 表示以 ./开头,以 .\w+结尾。 (.*)是匹配到的文件名.

\w 是指大小写字母数字或下划线。

replace方法中的$1是指正则表达式中括号内的字符,即第一个子匹配

所以 ./login.js 会替换成 login

  1. 通过reduce方法导出一个对象
const store = modulesFiles.keys().reduce((pre, cur) => {
  const moduleName = cur.replace(/^\.\/(.*)\.\w+$/, "$1");
  pre[moduleName] = modulesFiles(cur).default;
  return pre;
}, {});

console.log('store',store);

对象的属性名是modules目录下的js文件名,值是js文件中默认导出的 useStore 方法

例如 login.js 中 export default loginStore,到时候在组件中使用 loginStore 时,可以直接使用store.login() 获取loginStore实例

这是console.log(store)的结果 让Pinia写起来更简洁

对于temp子文件夹中的js文件,例如 test.js 。要通过store['temp/test']()获取实例对象

3.3 踩坑提示

让Pinia写起来更简洁 vue2中如果出现类似报错,请在vue.config.js中配置webpack

module.exports = {
  configureWebpack: config => {
    config.module.rules.push({
      include: /node_modules/,
      test: /\.mjs$/,
      type: 'javascript/auto'
    })
  }
}

4. 使用pinia

4.1 定义一个store

/*user.js*/
------------------------------------------------------------------
import { defineStore } from 'pinia'

 //'user' 是应用程序中 store 的唯一 id,Pinia 使用它将 store 连接到 devtools。
const userStore = defineStore('user', {
  state: () => { //state:状态库、必须是函数,否则会报错。
    return {
      store_counter: 0,
      store_nameList: ['Eduardo'],
      store_obj:{age:14,gender:'男'}
    }
  },
  actions: { //actions: 相当于vue组件中的 methods .可以通过this访问state数据,支持异步,async、await关键字
    updateState({ data, type }) {
      this[type] = { ...this[type], ...data };
    }
  }
})

//导出userStore
export default userStore

4.2 关于state的操作

  1. 引用state
//store 就是sotre文件夹中导出的index
import { store, storeToRefs } from "@/store";

export default {
  setup() {
    const userStore = store.userStore();
    return {
      userStore,
      ...storeToRefs(userStore),  //从 Store 中提取所有属性同时保持其响应式
    };
  },
  methods: {},
  mounted() {
      console.log(this.store_counter)
  }
}

修改state

//方式一:直接赋值修改
this.store_counter = 0
//方式二:使用 $patch 批量赋值修改
this.userStore.$patch({
  store_counter: this.store_counter + 1,
  store_nameList: [...this.store_nameList,'张三']
})
//方式三:$patch的参数改成函数,可以部分修改数组中的元素,而非新建一个数组(对象也是一样)
this.userStore.$patch((state) => {
    state.store_counter ++
    state.store_nameList.push('张三')
})

//方式三: 触发actions中的方法

this.userStore.updateState({data:{gender:'女'},type:'store_obj'})
//这个方法的意思是更新变量,相当于this.store_obj = {this.store_obj,...{gender:'女'}}
//推荐更新对象里的属性时用这个方法

4.3 关于getters的操作

定义getters

/*user.js*/
------------------------------------------------------------------
import { defineStore } from 'pinia'

const userStore = defineStore('user', {
  state: () => {
    return {
       store_nameList: ['Eduardo'],
    }
  },
  getters: { //getters: 计算属性,用于需要二次加工的属性,与vuex几乎无区别
    store_logNameList(state) {
        console.log(state.store_nameList)
    }
  }
})

//导出userStore
export default userStore

组件中使用getters (和使用state一样,直接this.getters名)

//store 就是sotre文件夹中导出的index
import { store, storeToRefs } from "@/store";

export default {
  setup() {
    const userStore = store.userStore();
    return {
      ...storeToRefs(userStore),  //从 Store 中提取所有属性同时保持其响应式
    };
  },
  methods: {},
  mounted() {
      console.log(this.store_logNameList)
  }
}

5. 使用插件

假设需要在stroe中使用router,就需要使用到pinia的插件机制扩展store的api

定义插件

import { createPinia } from 'pinia'
import { App} from "vue";

import { router } from '@/routers'

const pinia = createPinia()
pinia.use(({ store }) => {
    store.$router = router
  })
app.use(pinia)

使用插件

import { defineStore } from 'pinia'
import { RouteLocationNormalized } from 'vue-router'
export const useApplicationStore = defineStore('application', {
  state: () => (
    ...
  ),
  actions:{
    toHome(){
      this.$router.push('/home')
    }
  }
})

5. 注意

推荐加上store_前缀,让同事一眼认出变量是来自于pinia。方便别人读懂你的代码