【VUE3】如何使用VUEX的基本套路和文件架构

10505次阅读 635人点赞 作者: WuBin 发布时间: 2021-12-17 16:03:29
扫码到手机查看

文件目录

之前介绍过《如何在项目中快速的搭建并使用VUEX?》那个是在vue2环境下。今天来说一下如何在vue3中搭建vuex文件目录和使用。

src
 |-- store
     |-- actions.js  对多个mutation的提交操作进行封装
     |-- mutation.js 定义修改的操作相当于set
     |-- getters.js  获取vuex-state中的值
     |-- state.js    定义基本的属性
     |-- index.js    集合所有文件
 |-- main.js
 |-- components
     |-- demo.vue

需要先安装vuex,参考https://vuex.vuejs.org/zh/installation.html

再来就是了解一下vuex的核心概念:https://vuex.vuejs.org/zh/guide/state.html#单一状态树

可以吧vuex理解成为一个前端的数据库,用于存放临时的数据:服务器->发送数据->vuex接收到后->按照state中的设定进行保存,并定义一系列mutation进行提交、修改state中的字段,以及定义getters获取state中字段的值,最后还定义了actions封装多个mustation操作,只用一个操作就可以修改多个mutation。

文件定义以及使用

以我目前正进行的一个项目为例子进行说明。

state.js

首先定义各种状态,并给各种状态赋予一个默认的值。

// 定义全局的基础数据 定义播放器的基本状态

import { PLAY_MODE } from '@/assets/js/constant'

const state = {
  // 原始播放列表 歌曲列表
  sequenceList: [],
  // 真正的播放列表,因为有可能歌曲是顺序 逆序 倒序
  playlist: [],
  // 是否正在播放
  playing: false,
  // 播放模式 定义到常量文件中
  playMode: PLAY_MODE.sequence,
  // 当前播放的歌曲
  currentIndex: 0,
  // 播放器的状态 全屏还是收缩
  fullScreen: false
}

export default state

其中PLAY_MODE仅仅是常量,代表播放状态:

export const PLAY_MODE = {
  // 顺序播放
  sequence: 0,
  // 循环播放
  loop: 1,
  // 随机播放
  random: 2
}

getters.js

接下来定义状态state各个字段的获取方法,以一个为例。

// 定义一些获取数据的方法
// state中有currentIndex了 那么这里就可以获取当前播放的歌曲
// getters函数第一个参数就可以拿到state 通过当前播放列表拿到当前歌曲
export const currentSong = (state) => {
  // 当取值不存在时,返回一个空对象 避免程序报错
  return state.playlist[state.currentIndex] || {}
}

getters函数中第一个参数,就可以拿到state对象。固定用法。

mutations.js

有了get,怎么能没有post。这里定义一些修改state的操作方法。

// 提交数据 对state数据做修改

const mutations = {
  // 修改播放状态 第一个参数是state, 第二个参数是要修改为的值
  setPlayingState(state, playing) {
    state.playing = playing
  },
  // 设置顺序(原始)播放列表
  setSequenceList(state, list) {
    state.sequenceList = list
  },
  // 设置播放列表 当有随机播放情况的时候,playlist和sequencelist就会不同
  setPlaylist(state, list) {
    state.playlist = list
  },
  // 设置播放模式
  setPlayMode(state, mode) {
    state.playMode = mode
  },
  // 设置当前播放索引
  setCurrentIndex(state, index) {
    state.currentIndex = index
  },
  // 设置播放器全屏或者折叠模式
  setFullScreen(state, fullScreen) {
    state.fullScreen = fullScreen
  }
}

export default mutations

同样的,mutations方法中的第一个参数固定是state对象。第二个参数就是要赋予的值。

mutations方法中的方法要与state中的字段属性一一对应。

 actions.js

正常情况下,我们一个操作,可能会同时修改多个state中的属性,那么就意味着一个操作执行多个mutation中的方法。所以定义一个action将多个方法封装成一个函数。

// 定义动作 对一个操作 可以操作多个mutations的一个封装

import { PLAY_MODE } from '@/assets/js/constant'
import { shuffle } from '@/assets/js/util'

// 定义选择播放的动作 action第一个参数是一个对象可以直接结构 第二个参数是他的参数
// { list, index } 传入一个当前的列表 和 点击的索引
// commit是提交的意思 提交mutation中定义的函数
export function selectPlay({ commit, state }, { list, index }) {
  // 点击的时候顺序播放
  commit('setPlayMode', PLAY_MODE.sequence)
  commit('setSequenceList', list)
  // 当点击播放的时候,那么播放状态一定是true
  commit('setPlayingState', true)
  // 播放的时候会全屏播放
  commit('setFullScreen', true)
  // 当不随机播放的时候,播放列表和顺序播放列表相同
  commit('setPlaylist', list)
  // 设置播放索引
  commit('setCurrentIndex', index)
}

// 实现随机播放 这里的参数就不需要一个对象 直接一个参数即可
// 洗牌算法https://rosettacode.org/wiki/Knuth_shuffle
export function randomPlay ({ commit }, list) {
  // 点击的时候顺序播放
  commit('setPlayMode', PLAY_MODE.random)
  commit('setSequenceList', list)
  // 当点击播放的时候,那么播放状态一定是true
  commit('setPlayingState', true)
  // 播放的时候会全屏播放
  commit('setFullScreen', true)
  // 当不随机播放的时候,播放列表和顺序播放列表相同
  commit('setPlaylist', shuffle(list))
  // 设置播放索引,洗牌后播放第一首歌
  commit('setCurrentIndex', 0)
}

这里请忽略shuffle乱序方法,有兴趣的可以点击查看

这里说一下action中的参数。

  • 第一个参数{ commit, state } 中的commit()是提交方法。commit('setCurrentIndex',0)就是自动执行mutations.js中的setCurrentIndex(state, index)方法,commit方法的第二个参数就是setCurrentIndex()中要传入的参数index:setCurrentIndex(state, 0)
  • 第一个参数中的state就是状态state对象,但是切记,所有的state状态的修改,必须要通过mutation中的方法来执行。
  • 第二个参数{ list, index } 就是传入要修改值。这里如果传入多个值,最好使用一个对象传入。如果仅仅一个值那么就无需使用对象。如:randomPlay({ commit }, list)
  • 如果要在actions中不通过{ commit, state }参数,直接访问state的话,那么就使用this.state.xxx属性去访问【在vuex的index,js中,this指代的是store,因此如果要在actions和mutations中使用state中的数据,需要写成this.state.xxx,而不是this.xxx】

index.js

所有文件引用,导出,对vuex的配置。

// createLogger 开发环境下可以查看状态
import { createStore, createLogger } from 'vuex'
import state from './state'
import mutations from './mutations'
// 引入文件中所有的方法
import * as getters from './getters'
import * as actions from './actions'

const debug = process.env.NODE_ENV !== 'production'

export default createStore({
  state: state,
  mutations: mutations,
  // 可以同下方这样写 也可以与上面一样写
  getters,
  actions,
  // vuex提供了严格模式,
  // 帮忙检测state修改是否是在提交mutation的时候,如果不是,就会报错,
  // 开启时候会深度watch,所以线上模式就关闭这个模式
  strict: debug,
  // 插件,如果线上模式就不用插件 线下模式使用插件
  plugins: debug ? [createLogger()] : []
})

main.js

下面就需要在main.js中注册了。

import App from './App.vue'
import router from './router'
// 引入vuex的配置和设定
import store from './store'

// 引入全局样式文件,注意不能加双引号
import '@/assets/scss/index.scss'

createApp(App)
    .use(store)
    .use(router)
    .mount('#app')

在组件中使用

使用actions.js中的方法

在demo.vue中:

<div @click="selectItme({song, index})" v-for="(song, index) in songs">...</div> 

<script type="text/ecmascript-6">
// 使用vuex中的actions方法,将其在methods中注册
import { mapActions } from 'vuex'

export default {
    name: 'demo',
    data() {
        return {
            songs: [...]
        }
    },
    methods: {
        // 对传入的对象进行解构赋值
        selectItme({ song, index }) {
          // 传入播放列表和点击的索引
          this.selectPlay({
            list: this.songs,
            index
          })
        },
        // 参照上面的actions中的方法查看对比
        random() {
          this.randomPlay(this.songs)
        },

        // 注册后,就可以在methods中直接调用这个方法了 this.xxx()
        ...mapActions([
          'selectPlay',
          'randomPlay'
        ])
    }
}
</script>

使用getters中的方法

<h1 class="title">{{currentSong.name}}</h1>

import { mapGetters } from 'vuex';

computed: {
    ...mapGetters([
        'currentSong'
    ])
},

从vuex中获取mapGetters方法,然后在computed中进行注册,引入需要的方法,就可以直接在模板中进行渲染了。

调用mutation中的方法

你可以在组件中使用this.$store.commit('xxx')提交 mutation,或者使用mapMutations辅助函数将组件中的 methods 映射为store.commit调用(需要在根节点注入store)。

import { mapMutations } from 'vuex'

methods: {
    ...mapMutations([
        // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
        'increment', 
        
        // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
        'incrementBy' 
    ]),
    ...mapMutations({
        add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
}

vuex官网介绍Mutation

调用state中的数据

在计算属性中, 可以通过mapState直接获取state中的数据:

import { mapState } from 'vuex'
computed: {
   xxx() {
     return this.playlist.length
   },
  
   ...mapState([
      'playlist'
    ])
},

在普通的JS文件中使用

直接引入使用即可,如下:  更详细的使用《vue3项目中在普通js文件里获取vuex里的值和方法》

import store from './store.js';

console.log(store.state.data);
store.commit('xxx');

// 其中 store.commit 中的xxx是 mutation.js中的方法 比如 与action.js中相同
store.commit(setCurrentIndex', index);

获取getters中的值

store.getters["getters文件中export的属性"]
或者使用
store.getters.xxx
store.getters.currentSong 或者 store.getters['currentSong']

获取state中的值

store.state.namestore.state.TestData 

使用mutations(定义在mutations.js中的方法):

store.commit('set_TestData', data)

使用actions(定义在action.js中的方法):

store.dispatch(‘method', data)

store.dispatch('TestDatafun', data)

当在js文件中使用vuex,取出的对象是proxy时

在使用vuex的时候发现获取state的stringnumber类型的变量时,是可以直接通过store.state.value 取到值的,但是在取对象的时候,控制台打印出来的是Proxy 对象,Proxy对象里边的[[Target]]才是真实的对象。

//第一种获取target值的方式,通过vue中的响应式对象可使用toRaw()方法获取原始对象
import { toRaw } from '@vue/reactivity'
var list = toRaw(store.state.sequenceList)
//第二种获取target值的方式,通过json序列化之后可获取值
JSON.parse(JSON.stringify(store.getters.sequenceList))
更新:最近遇到一个在使用toRaw时遇到的小问题,使用这个方法会导致数据更新但视图不更新,从vue里面取出来的虽然是proxy对象,但是也可以直接进行数组的操作,如splice,数组拓展等

在组合API中使用vuex

在组合API中使用vuex,就需要在setup钩子函数中进行手动调用。

比如如下模板结构(setup中使用组合API方法,点击查看):

<template>
  <div class="player">
    <div
      class="normal-player"
      v-show="fullScreen"
    >
      <!--  使用v-if避免currentSong没有值的时候被渲染 -->
      <!-- 使用template避免产生多余的dom层 -->
      <template v-if="currentSong">
        <div class="background">
          <img :src="currentSong.pic">
        </div>
        <div class="top">
          <div
            class="back"
            @click="goBack"
          >
            <i class="icon-back"></i>
          </div>
          <h1 class="title">{{currentSong.name}}</h1>
        </div>
      </template>
    </div>
    <audio
      ref="audioRef"
    ></audio>
  </div>
</template>

下面是JS部分

// vuex提供useStore专门从vuex中拿数据的方法
import { useStore } from 'vuex'
// 如果需要在组合api中使用计算监听 那么就要单独引入
import { computed, watch, ref } from 'vue'

export default {
  // 对于复杂的组件 将其中的各个逻辑进行拆分成不同文件
  setup() {
    // 获取的时候要先定义一个ref对象
    const audioRef = ref(null)

    // 这个store可以理解为createStore中返回的实例,其中含有state、getters等属性
    const store = useStore()
    // setup中的响应式需要自己去处理,所以要使用computed()这个API
    // 让state.fullScreen变成一个响应式的
    // 记得必须要返回转化的计算属性
    const fullScreen = computed(() => store.state.fullScreen)
    const currentSong = computed(() => store.getters.currentSong)

    // 监听currentSong的属性变化,如果变化了就重新给audio赋值
    watch(currentSong, (newSong) => {
      if (!newSong.id || !newSong.url) {
        return
      }
      // 通过audioRef.value获取到audio的DOM元素
      const audioEl = audioRef.value
      audioEl.src = newSong.url
      audioEl.play()
    })

    // 通过store.commit方法,提交一个修改操作
    function goBack() {
      store.commit('setFullScreen', false)
    }
    
    // 只有返回出去 才能被模板获取到
    return {
      audioRef,
      fullScreen,
      currentSong,
      // 返回方法,模板中可以使用goback()
      goBack
    }
  }
}

上面的结构有点乱,我们来简单梳理下:

获取vuex中的实例(所有一切的前提)

// vuex提供useStore专门从vuex中拿数据的方法
import { useStore } from 'vuex'

setup(){
  // 这个store可以理解为createStore中返回的实例,其中含有state、getters等属性
  const store = useStore()
}

获取vuex中的state和getters

// setup中的响应式需要自己去处理,所以要使用computed()这个API
// 让state.fullScreen变成一个响应式的
// 记得必须要返回转化的计算属性
import { computed } from 'vue'
// 直接返回 state中字段的属性
const fullScreen = computed(() => store.state.fullScreen)
// 执行vuex中的getters方法
const currentSong = computed(() => store.getters.currentSong)

// 只有返回出去 才能被模板获取到
return {
   fullScreen,
   currentSong
}

执行mutations

setup() {
    // 通过store.commit方法,提交一个修改操作
    function goBack() {
      store.commit('setFullScreen', false)
    }
    // 只有返回出去 才能被模板获取到
    return {
      // 返回方法,模板中可以使用goback()
      goBack
    }
}

通过store.commit执行mutations中定义的方法,并对其传参。

使用watch监听并获取元素DOM

import { watch, ref } from 'vue'

setup() {
    // 获取的时候要先定义一个ref对象
    // 注意 也要在模板元素上加 <audio ref="audioRef">
    const audioRef = ref(null)
    // 监听currentSong的值变化,如果变化了就重新给audio元素赋值
    watch(currentSong, (newSong) => {
      if (!newSong.id || !newSong.url) {
        return
      }
      // 通过audioRef.value获取到audio的DOM元素 固定套路
      const audioEl = audioRef.value
      audioEl.src = newSong.url
      audioEl.play()
    })
}

在setup中获取计算属性的值

 <i :class="playIcon"></i>
 import { computed } from 'vue'
 import { useStore } from 'vuex'

 setup() {
    const store = useStore()
    // 从vuex中取出state.playing字段
    const playing = computed(() => store.state.playing)
    // 因为playing是一个计算属性,所以在js中要通过playing.value获取它的值
    const playIcon = computed(() => {
      return playing.value ? 'icon-pause' : 'icon-play'
    })
    
    return {
        playIcon
    }
 }

这里有个疑问:为什么需要用playing.value进行判断呢?之前判断player是否显示的fullscreen都是直接用的。

答:因为 playing 是一个计算属性,在 JS 中需要通过 .value 获取它的值。

在setup中使用actions

这里从不同文件角度进行分析:

在player.vue中:

// player.vue
<i @click="changeMode"></i>

import useMode from './use-mode'
setup() {
    const { changeMode } = useMode()
    return {
        // 将方法返回给模板
        changeMode
    }
}

在use-mode.js文件中:

// use-mode.js文件
import { useStore } from 'vuex'
import { computed } from 'vue'

export default function useMode() {
  const store = useStore()
  const playMode = computed(() => store.state.playMode)

  function changeMode () {
    // 这样取值结果值就在0,1,2之间
    // 在js中获取计算属性的值要使用.value
    const mode = (playMode.value + 1) % 3
    // 执行一个actions-changeMode,传递参数mode
    store.dispatch('changeMode', mode)
  }

  // 注意 这里返回的必须是一个对象!这样接收的时候使用解构赋值接收
  return {
    changeMode
  }
}

这里执行vuex中的actions中封装的方法,要使用:

const store = useStore()
store.dispatch('actions方法名', 参数);

再来看看actions中封装的changeMoe方法(actions就是封装了多个Mutations修改方法的函数):

// getters也可以被解构 commit是可以提交mutation的方法, state是定义的数据字段状态
export function changeMode({ commit, state, getters }, mode) {
  // 先从getters中拿到当前正在播放歌曲(对象)的Id
  const currentId = getters.currentSong.id
  
  // 设置提交Mutation,通过mutation方法修改state中的属性
  commit('setPlaylist', state.sequenceList)

  // 在修改后的playlist中找到这个ID对应的这首歌,找到它的索引之后再提交commit
  const index = state.playlist.findIndex((song) => {
    return song.id === currentId
  })

  // 修改当前正在播放的歌曲的索引
  commit('setCurrentIndex', index)
  // 修改播放模式
  commit('setPlayMode', mode)
}

如果watch监听vuex中属性的变化

使用mapState

我们可以在组件中通过组件的 watch方法来做, 因为组件可以将state数据映射到 组件的计算属性上,

// vuex中的state数据
  state: {
    count: 0
  },
     
//  A组件中映射 state数据到计算属性
  computed: {
   //  this.$store.state.count
  // mapState       把全局  count 变成 可以直接使用的 数据
    ...mapState(['count'])
  }
// A组件监听 count计算属性的变化
   watch: {
     // watch 可以监听 data 数据 也可以监听 全局 vuex数据
    count () {
      // 用本身的数据进行一下计数
      this.changeCount++
    }
  }

store的watch函数

vuex中store对象本身提供了watch函数 ,可以利用该函数进行监听

响应式地侦听 fn 的返回值,当值改变时调用回调函数。fn 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 vm.$watch 方法的参数。

watch(fn: Function, callback: Function, options?: Object): Functionvuex的实例方法,接收两个参数:第一个参数为fn,响应式地侦听 fn 的返回值,当值改变时调用回调函数。fn 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数;第二个参数为一个可选的对象参数表示 ,类似于vue的watch的回调函数,表示新旧值。当我们不想监听时,可以通过定义变量接收该方法的返回值函数,调用该函数即可停止监听。
  created () {
    this.$store.watch((state, getters) => {
      return state.count
    }, () => {
      this.changeCount++
    })
  }

x如果是在js文件中,那么就如下使用

/* eslint-disable */
import store from "../store/index";
const watchFun = store.watch(
    state => state.pathName,
    (newValue, oldValue) => {
        console.log("search string is changing");
        console.log("rd: newValue", newValue);
        console.log("rd: oldValue", oldValue);
    }
);
setTimeout(() => {
    watchFun();
}, 10000);

调用store的watch实例方法,第一个函数参数返回一个需要监听的state中的值(比如我想监听vuex中的pathName的变化情况,就返回该值)

第二个参数同vue的watch,接收2个参数代表新旧值

通过变量watchFun接收watch的返回值,调用该方法会停止监听

监听store中数据变化的两种方式

方式一

watch: {
  "$store.state.userInfo.Name":{
    handler:function(newVal,oldVal){
      console.log(newVal,oldVal);
    }
  }
}

方式二

computed: {
  isEdit () {
    return this.$store.state.userInfo.Name;  //需要监听的数据
  }
},
watch: {
  isEdit(newVal, oldVal) {
    console.log(newVal,oldVal);
  }
},
  • 第二种方式是先通过计算属性时刻监测store的数据变化,从而触发isEdit的监听函数,明显需要多一步
  • 如果监听store的数据是一个对象,第一种方式只需要加上深度监听,也可以实现数据的变化监听,而第二种方式却无法监听到store的对象数据变化
使用第一种方式监听对象:
watch: {
  //此时我监听的是对象,当$store.state.userInfo.Name发生修改时,此时需要深度监听才能监听到数据变化
  "$store.state.userInfo":{
    deep:true,//深度监听设置为 true    immediate: true, // 刚开始就监听
    handler:function(newVal,oldVal){
      console.log("数据发生变化啦"); //修改数据时,能看到输出结果
      console.log(newVal,oldVal);
    }
  }
}
第二种方式
computed: {
  isEdit () {
    return this.$store.state.userInfo;  //需要监听的数据
  },
},
watch: {
  isEdit(newVal, oldVal) {
    console.log("数据发生变化啦"); //修改数据时,看不到该输出结果,因为无法监听到数据的变化
    console.log(newVal,oldVal);
  }
},

相关资料

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:vuex
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    9094次阅读 588人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    3845次阅读 287人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    14071次阅读 914人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    12908次阅读 442人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    11469次阅读 920人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    15588次阅读 1244人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    14795次阅读 1205人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    28950次阅读 2273人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    24982次阅读 1925人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录