0.12、Vuex -store存储运行时信息

本文介绍Vue应用中使用Vuex进行状态管理的最佳实践,包括多模块配置、变量及方法使用等,并对比Vuex与localStorage、sessionStorage和cookie的区别。

最佳实践:Vuex 多module 实践

你已经配置好了基础配置

子 module

store/modules/navIsCollapse.js

export default{
  //开启命名空间
  namespaced: true,
  state: {
    isCollapse: false
  },
  mutations:{
    changeIsCollapse(state){
      state.isCollapse = !state.isCollapse;
    }
  }
}


主 module

store/index.js

//import Vue from "vue";
//import Vuex from "vuex";
//const demoModule = require('./modules/demoModule')
//import demoModule from "./modules/demoModule"
import navIsCollapse from "./modules/navIsCollapse.js"

Vue.use(Vuex);

export default new Vuex.Store({
  /* 多 modules 模式*/
  modules: {
    //demoModule: demoModule,
    navIsCollapse,
  },
  state: () => ({
    accessToken: '',
    userInfo:{
      userName: ''
    }

  }),
  //mutations 用来 变更 store 中的信息,同步
  //在<script>中调用 this.$store.commit("setToken","修改下token")
  mutations: {
    setAccessToken(state, access_token) {
      // 这里的 `state` 对象是模块的局部状态
      this.state.accessToken = access_token
    },
    setUserName(state,user_name){
      this.state.userInfo.userName = user_name;
    },

    initAccessToken(state) {
      this.state.accessToken = window.localStorage.getItem("access_token");
    },
    initUserInfo(state){
      this.state.userInfo.userName = window.localStorage.getItem("user_name");
    }

  },


  // store 的计算属性
  //在<template>用于展示 {{this.$store.getters.gettersNumber}}
  getters: {
    gettersNumber(state) {
      return state.number * 2;
    }
  },

  // 异步执行 mutations 中方法
  // 在<script>中调用 this.$store.dispatch("actionsSetNumber")
  actions: {
    //第一种写法,借助context
    actionsSetToken(context, str) {
      context.commit("setToken", str)
    },
    //第二种写法,简化
    actionsSetNumber({
      commit
    }) {
      commit("setNumber2");
    }
  }
});

使用

使用变量

  • 引入 js
  import {
    mapState
  } from "vuex"

export default {
    computed: {
      ...mapState({
        isCollapse: state => state.navIsCollapse.isCollapse
      })
    },
  • 直接使用变量
:collapse="isCollapse"

使用方法

将数据变化的方法引入到 template 中的方法中

  • 引入 js
 import {
    mapMutations
  } from "vuex"

  export default {
    data() {
      return {
        isShow: true
      }
    },
    methods: {
      ...mapMutations({
        changeIsCollapse: "navIsCollapse/changeIsCollapse"
      }),
      changeNavBar() {//调用这个方法
        this.isShow = !this.isShow;
        this.changeIsCollapse();//引用 子 store 中的方法
      }
    }
  }
  • 通过 click 触发方法
<template>
  <div class="header">
    <el-button icon="el-icon-s-unfold" v-show="!isShow" @click="changeNavBar"></el-button>
    <el-button icon="el-icon-s-fold" v-show="isShow" @click="changeNavBar"></el-button>
  </div>
</template>

1、安装版本

Vue2 安装 Vuex:

npm i vuex@3  

2、官方说明

https://vuex.vuejs.org/zh/

2.1、Vuex 的核心 store 概念

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

3、安装

//如果你的vue版本是 2.X ,将vuex升到 3.X.X 就能够解决
npm i vuex@3  

//如果你的vue版本是 3.X ,将vuex升到 4.X.X 就能够解决
//npm install --save vue@3.0.2
npm install --save vuex@4.0.0`

4、简单用法

4.1、基础语法

4.1.1、store对象声明、state、mutations、getters、actions

这个集合不错

5、Vuex 和 localstorage、sessionstorage或cookie 比较

语法参考

5.1、Vuex store 信息浏览器刷新即失效-借助钩子函数 beforeCreate

在vue项目中用vuex来做全局的状态管理, 发现当刷新网页后,保存在vuex实例store里的数据会丢失。
那么我们如何保证响应能力呢?比如对于登陆登出,可以借助钩子函数来完成localStore 信息向 store 信息的同步。

export default {
  mounted() {
    console.log(`the component is now mounted.`)
  },
  //使用钩子来初始化登陆信息
    beforeCreate() {
        this.$store.commit("setMsgLogin");
      },
}

在这里插入图片描述

5.2、localStorage 生命周期是永久

localStorage: localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。

5.3、sessionStorage 仅在当前会话下有效

sessionStorage的生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。

5.4、cookie 在设置有效期内有效,有存储空间、个数限制

cookie生命期为只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。 存放数据大小为4K左右,有个数限制(各浏览器不同),一般不能超过20个。缺点是不能储存大数据且不易读取。

6、Vuex 多module 实践(初学者版)

6.1、文件结构

index.js 包含一个默认的state信息,demoModules.js 包含一个子state信息,index.js 引入 demoModules.js

/store/index.js
/store/modules/demoModules.js

6.1.1、demoModule.js

const demoModule = {
  //开启命名空间
  namespaced: true,
  state: () => ({
    token: '',
    number: 1,
    number2: 1,
    msg: '请登陆',
    userInfo: {
      userName: 'bestcxx'
    }
  }),
  //mutations 用来 变更 store 中的信息,同步
  //在<script>中调用 this.$store.commit("setToken","修改下token")
  mutations: {
    setToken(state, str) {
      // 这里的 `state` 对象是模块的局部状态
      state.token = str
    },
    setNumber(state) {
      state.number = state.number * 2;
    },
    setNumber2(state) {
      state.number2 = state.number2 * 2;
    },
    setMsgLogin(state) {
      this.state.msg = "bestcxx 已登陆";
      localStorage.setItem("msg", "bestcxx 已登陆");
    }
  },


  // store 的计算属性
  //在<template>用于展示 {{this.$store.getters.gettersNumber}}
  getters: {
    gettersNumber(state) {
      return state.number * 2;
    }
  },

  // 异步执行 mutations 中方法
  // 在<script>中调用 this.$store.dispatch("actionsSetNumber")
  actions: {
    //第一种写法,借助context
    actionsSetToken(context, str) {
      context.commit("setToken", str)
    },
    //第二种写法,简化
    actionsSetNumber({
      commit
    }) {
      commit("setNumber2");
    }
  }
}

export default demoModule

6.1.2、/store/index.js

//import Vue from "vue";
//import Vuex from "vuex";
//const demoModule = require('./modules/demoModule')
import demoModule from "./modules/demoModule"

Vue.use(Vuex);

export default new Vuex.Store({
  /* 多 modules 模式*/
  modules: {
    demoModule: demoModule
  },
  state: () => ({
    token: '',
    number: 1,
    number2: 1,
    msg: '请登陆',
    userInfo: {
      userName: 'bestcxx'
    }
  }),
  //mutations 用来 变更 store 中的信息,同步
  //在<script>中调用 this.$store.commit("setToken","修改下token")
  mutations: {
    setToken(state, str) {
      // 这里的 `state` 对象是模块的局部状态
      state.token = str
    },
    setNumber(state) {
      state.number = state.number * 2;
    },
    setNumber2(state) {
      state.number2 = state.number2 * 2;
    },
    setMsgLogin(state) {
      this.state.msg = "bestcxx 已登陆";
      localStorage.setItem("msg", "bestcxx 已登陆");
    }
  },


  // store 的计算属性
  //在<template>用于展示 {{this.$store.getters.gettersNumber}}
  getters: {
    gettersNumber(state) {
      return state.number * 2;
    }
  },

  // 异步执行 mutations 中方法
  // 在<script>中调用 this.$store.dispatch("actionsSetNumber")
  actions: {
    //第一种写法,借助context
    actionsSetToken(context, str) {
      context.commit("setToken", str)
    },
    //第二种写法,简化
    actionsSetNumber({
      commit
    }) {
      commit("setNumber2");
    }
  }
});

7、开启调试模式

Vue.config.devtools = true;

参考资料

1、 简单demo
2、监听用户退出
3、从概念到具体
4、localStory
5、Vuex module 数据操作

很好,继续保持! uni-card-game/ ├── common/ # 公共资源 │ ├── card-data.js # 卡牌基础配置 │ └── utils.js # 工具函数 ├── components/ # 通用组件 │ ├── card-view.vue # 卡牌展示组件(带放大/缩小功能) │ ├── rarity-badge.vue # 稀有度徽章组件 │ └── blank-card-editor.vue # 空白卡编辑器 ├── pages/ # 页面组件 │ ├── home/ # 主页面 │ │ └── home.vue # 导航页面 │ ├── gacha/ # 抽卡页面 │ │ └── gacha.vue │ ├── shop/ # 商店页面 │ │ └── shop.vue │ ├── warehouse/ # 仓库页面 │ │ └── warehouse.vue │ └── merge/ # 合成页面 │ └── merge.vue ├── static/ # 静态资源 │ ├── card-back.png # 卡牌背面 │ ├── blank-card.png # 空白卡模板 │ └── icons/ # 图标资源 ├── store/ # Vuex状态管理 │ ├── index.js # 主store │ ├── card.js # 卡牌状态模块 │ └── user.js # 用户状态模块 ├── App.vue # 全局组件 ├── main.js # 入口文件 ├── manifest.json # 应用配置 └── pages.json # 页面路由配置 为我用uni-app生成本地简易抽卡游戏,要求每天只能抽一张,抽完的卡可以按照稀有度兑换同等稀有度的每天商店随机刷新的卡牌,抽卡和商店刷新卡都依照稀有度进行概率刷新,不存在保底,抽取和兑换的卡牌都会储存在本地,主页有抽取、商店兑换和仓库按钮。同一种卡可以有复数个存在。十张相通稀有度的卡可以合并形成更高一级稀有度的空白卡(卡类、卡名、字段为空白的卡,这种卡也可以抽出来)。 每张卡已经定下有稀有度、卡类、卡名、字段、内容属性,区分每张卡的独立性则内置标识码。卡类包含多种卡名,字段相当于tag标签置于内容前,内容就是多段文字描述。 仓库不要求有检索能力,但要求按照默认排序方式储存展示卡牌,卡牌点击后可以放大观看(在仓库外任何场景也实现该功能),再点击就可以缩回原样,卡牌有不同的优先展示值(排序的根据),稀有度的优先值按照千为单位,卡类的优先值按照万为单位,卡名的优先值按照十为单位,字段的优先值按照个为单位,每张牌的优先值即为以上四个优先值的合。在仓库点开卡牌(即放大观看后,角落或低端)有删除按钮,点击删除后删除仓库里这张卡(如果有复数张,数量减一)。仓库角落有合成按钮,点击后进入合成模式(合成按钮变为退出合成模式按钮),添加相应数量的同稀有度卡牌后点击确认,便删除选中的卡牌并添加进仓库相应数量的更高一级稀有度的空白卡牌,空白卡牌默认优先值为十万加稀有度优先值。 现在请为我编写pages/下的shop/ 代码(如果已经编写完毕,则提醒我,但不输出其他环节的代码),此外不要再有多余的动作,并记住你还有哪些已经编写和尚未编写。
最新发布
10-02
<template> <div class="nav has-pe" :class="isDark ? 'dark' : ''"> <div class="header-bg"> <!-- <img :src="headerBg" style="width:100%;"/>--> </div> <!-- <div class="animate-blocks">--> <!-- <div v-for="i in 3" class="animate-block-item"></div>--> <!-- </div>--> <div class="animate-1-con"><img :src="animate1Image" class="animate-1-img"/></div> <div class="animate-2-con"><img :src="animate2Image" class="animate-2-img"/></div> <!-- <div class="star"></div>--> <div style="height:100%;display: flex;align-items: center;"> <div class="logo-con has-pe" style="height:100%;"> <div class="logo-border"> <logo-svg class="logo-svg" /> </div> <router-link :to="{name:'home'}" class="logo-text"> {{appStore.title}} </router-link> </div> <div class="menu-con has-pe"> <template v-for="m in menus"> <a v-if="m.href" :href="m.href" target="_blank" class="menu-item nav-ab t" :class="m.code"> <span class="mdi mgr-5" :class="m.icon"></span> <span>{{m.text}}</span> </a> <router-link v-else :to="{name: m.code}" @click="play" class="menu-item nav-ab t" :class="m.code"> <span class="mdi mgr-5" :class="m.icon"></span> <span>{{m.text}}</span> </router-link> </template> </div> <div class="right-side text-right mgr-10 mgt-5"> <div class="font-18">欢迎你,超级管理员</div> <div class="font-ntf font-20 mgt-5 op-60"> <el-button size="small" type="primary" plain class="is-square" icon="Sunny" @click="appStore.toggleTheme()"></el-button> <el-button size="small" type="primary" plain class="is-square" icon="FullScreen" @click="toggleFullscreen()"></el-button> <el-button size="small" type="primary" plain class="is-square" icon="MoreFilled"></el-button> <el-button size="small" type="danger" plain>退出</el-button> </div> </div> </div> </div> </template> <script setup lang="ts"> import _ from "lodash" import {computed, onMounted, ref, onUnmounted} from "vue"; import {useAppStore} from "@/services/store/app"; import {Howl} from 'howler'; import menuSound from "@/assets/medias/menu.mp3" import {useNow, useDateFormat, useDark} from '@vueuse/core' import animate1Image from "./images/animate-1.png" import animate2Image from "./images/animate-2.png" import logoSvg from "@/assets/images/logo.svg" const appStore = useAppStore(); const now = useNow(); const nowTime = ref('') const timer = ref() const isDark = useDark(); //region sound var sound = new Howl({ src: [menuSound], volume: .4 }); const play = () => { sound.play(); }; //endregion const menus = [ {code:'home', text:'首页', icon:'mdi-home'}, {code:'network', text:'网络', icon:'mdi-lan'}, {code:'transfer', text:'数据', icon:'mdi-transfer'}/*, {code:'stat', text:'检索统计', icon:'mdi-chart-bar-stacked', href: headerQueryMenuUrl}, {code:'manage', text:'后台管理', icon:'mdi-monitor-dashboard', href:headerManageMenuUrl}*/ ]; import {usePageAnimate} from "@/services/hook/usePageAnimate"; import axios from "axios"; usePageAnimate("nav-ab"); onMounted(() => { }); onUnmounted(()=> { }) import { useFullscreen } from '@vueuse/core' import {headerManageMenuUrl, headerQueryMenuUrl} from "@/services/config"; const { isFullscreen, enter, exit, toggle: toggleFullscreen } = useFullscreen() </script> <style lang="scss" scoped> @use "sass:math"; @import "@/assets/css/_var"; $padding: 36px; .nav { position:absolute; left:$padding;right:$padding;top:$padding; z-index:10; height:80px; backdrop-filter: blur(3px); //box-shadow: 0 1px 10px rgba(0,0,0,.3) //background-color: rgba(red,.5); } .header-bg { //height: 90px; width:100%; //background: url(./images/header-bg.png) no-repeat bottom center; //background-size: contain; position: absolute; z-index:0; top:0;left:0;right:0;bottom:0; border-top: transparent solid 6px; border-bottom: transparent solid 1px; background: rgba($-primary-color, 0.3); border-top-color:$-primary-color; border-bottom-color: $-primary-color; box-shadow: 0 0 6px #035f71; &:after { content:""; position: absolute;top:0;bottom:0;left:0;right:0; //background: linear-gradient(45deg , rgba($-primary-color , .5) 60%, $-primary-color); } } .right-side { position: relative; } .logo-con { display: flex; align-items: center; justify-content: center; } .logo-border { //background:rgba(255,255,255,.1); padding:0 25px;display:flex;align-items:center; height:100%; position:relative;z-index:1; border-top:transparent solid 6px; border-bottom:transparent solid 6px; box-shadow: 1px 0 10px rgba(0,0,0,.2); border-color: rgba($-primary-color, 1);; } .logo-svg { width:60px;margin-left1:10px; //filter: drop-shadow( 0px 0px 6px rgba(#bcffff, .3)); //fill: #00c8e1; :deep { path {fill:#253223} path.lightning { fill:#ffcc00; stroke-width: 0; } //.circle-middle { // fill: #ffcc00; //} .dot {fill:#ffcc00;} .circle-bg {fill:transparent} } } .logo-text { padding-left:20px; font-family: shuhei;font-size:42px; margin-top:3px; text-shadow1: 5px 5px 1px rgba(255,255,255,.1); filter: drop-shadow( 1px 1px 2px #253223); background-image: -webkit-linear-gradient(90deg, rgb(245, 255, 250) , $-primary-color ); //background-image: linear-gradient(180deg,#fff,#2af1f8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .menu-con { display: flex; flex:1; justify-content: center; position:relative;z-index:1; margin-top:6px; } .menu-item { display: flex; align-items: center;justify-content: center; border:rgba(#253223,.9) solid 1px; background: rgba($-primary-color,.4); color:rgba(#253223, .8); border-radius:2px; width:150px;height:44px; margin:0 15px; background-size:cover; text-align:center; font-size:20px; font-family: fzzzh; font-weight: normal; letter-spacing: 5px; transition: all .3s; span.mdi {opacity: .9} &:after, &:before { transition: all .3s; content:''; position: absolute; width:3px;height:24px; background: rgba(#253223,.6) } &:before {left:0;} &:after { right:0; } &:hover { border-color:#253223; color:#253223; background: rgba(#fff, .8); &:after, &:before { height:28px; background: rgba(#253223,.9) } &:after {right:3px} &:before {left:3px} } &.router-link-active { span.mdi {opacity: 1} color: $-primary-color; border-width:2px; border-radius:4px; border-color:$-primary-color; background: rgba(14, 31, 14,.9); font-weight:bold; box-shadow: 0 0 8px #253223; &:after, &:before { width:4px; background: rgba($-primary-color,.8) } } &.job, &.live, &.vehicle{ text-decoration: line-through; } } .animate-blocks { width: 55px; height: 25px; left: 662px; top: 74px; position: absolute; visibility: visible; pointer-events: none; transition: transform 600ms; display: flex; align-items: center; justify-content: space-evenly; } .animate-block-item { width:8px;height:10px; background: linear-gradient(to right , rgba(120, 255, 255, .8) , transparent); transform: skewX(-40deg); border-radius:1px; } .animate-1-con { position: absolute; pointer-events: none; left: 399px; top: 0; width: 120px; height: 100%; opacity:.3; } .animate-1-img { width:100%;height:100%; border-radius: unset; cursor: pointer; pointer-events: none; animation-name: animate-1; animation-iteration-count: infinite; animation-timing-function: linear; animation-duration: 5s; animation-delay: 0s; -webkit-user-drag: none; filter: none; } @keyframes animate-1 { 0% { opacity: 0; transform: none; } 15% { opacity: 0.75; transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) scaleX(1) scaleY(1) translate3d(150px, 0px, 0px); } 30% { opacity: 1; transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) scaleX(1) scaleY(1) translate3d(300px, 0px, 0px); } 45% { opacity: 0.75; transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) scaleX(1) scaleY(1) translate3d(450px, 0px, 0px); } 60% { opacity: 0; transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) scaleX(1) scaleY(1) translate3d(600px, 0px, 0px); } 100% { opacity: 0; transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) scaleX(1) scaleY(1) translate3d(600px, 0px, 0px); } } .animate-2-con { position: absolute; pointer-events: none; left: 646px; bottom: 0; width: 120px; height: 60%; opacity:.3; } .animate-2-img { width:100%;height:100%; border-radius: unset; cursor: pointer; pointer-events: none; animation-name: animate-2; animation-iteration-count: infinite; animation-timing-function: linear; animation-duration: 4s; animation-delay: 0s; -webkit-user-drag: none; filter: none; } @keyframes animate-2 { 0% { opacity: 0; transform: none; } 50% { opacity: .78; transform: translateX(450px); } 100% { opacity: 0; transform: translateX(900px); } } $star-width: math.div(1120px, 1.5); $star-height: math.div(660px, 1.5); .star { position:absolute;top:-113px;left:-10px; width:$star-width;height:$star-height; background: url(./images/star.png) no-repeat bottom center; background-size: cover; animation-name: star-animate; animation-iteration-count: infinite; animation-timing-function: linear; animation-duration: 10s; animation-delay: 10s; -webkit-user-drag: none; //filter: brightness(150%); } @keyframes star-animate { 0% { opacity: 1; } 50% { opacity: .5; } 100% { opacity: 1; } } div.nav.dark { .header-bg { border-top-color:rgb(71, 128, 76); border-bottom-color:rgba(6, 253, 253, .15); background: rgba($-primary-color, 0.3); //background-color: rgba(106, 211, 137, 0.12); &:after { background: linear-gradient(-45deg , rgba(0,0,0,.4), transparent); } } .logo-border { border-color: rgb(71, 128, 76); } .logo-text { background-image: -webkit-linear-gradient(90deg, rgb(150, 248, 140) 0%, rgb(245, 255, 250) 90%); } .logo-svg { :deep { path {fill:rgba(113, 228, 102,.6)} path.lightning { fill:#ffcc00; stroke-width: 0; } //.dot {fill:rgba(#fff,.7);} .circle-bg {fill:transparent} } } $-menu-color: #71e466; //7cffff .menu-item { border:rgba($-menu-color,.3) solid 1px; background: rgba(0,0,0,.2); color:rgba($-menu-color, .5); &:after, &:before { background: rgba($-menu-color,.3) } &:hover { border-color:$-menu-color; color:$-menu-color; background: rgba($-menu-color, .1); &:after, &:before { background: rgba($-menu-color,.6) } } &.router-link-active { color: $-menu-color; border-color:$-menu-color; &:after, &:before { background: rgba($-menu-color,.8) } } } } </style>
07-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值