Vue学习笔记09:Vuex的基本使用

一、Vuex 概述

1. Vuex 是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

我们可以理解为:它是一个Vue插件工具,它可以解决不同组件数据共享问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-najxXOV1-1638001671059)(image/image-20211127115053294.png)]

在以前我们是如何管理自己的状态呢?

  • 在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为 state
  • 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为 view
  • 在模块中我们会产生一些事件,为了处理这些事件,有可能会修改state,这种操作我们称之为 actions

这三个就对应了vuex的三个核心概念:state、mutations、actions


2. Vuex 好处与用处

使用 Vuex 统一管理状态的好处

  • 能够在 vuex 中集中管理共享的数据,易于开发和后期维护
  • 能够高效地实现组件之间的数据共享,提高开发效率
  • 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步

什么样的数据适合存储到 Vuex 中

  • 一般情况下,只有需要在组件之间共享的数据,才有必要存储到 vuex 中
  • 对于组件中的私有数据,依旧存储在组件自身的 data 中即可

二、Vuex 的核心概念

Vuex 应用的核心就是 store(仓库)。store 基本上就是一个容器,它包含着你的应用中大部分的状态 (state)

Vuex和单纯的全局对象有什么区别呢?

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z2dNUxhK-1638001671061)(image/1573542431789-1635858076974.png)]

看图结论:

  • state:管理数据,管理的数据是响应式的,当数据改变时驱动视图更新。
  • mutations:更新数据,state中的数据只能使用mutations去改变数据。
  • actions:请求数据,响应成功后把数据提交给mutations

三、Vuex 的基本使用

1. 安装配置 Vuex

1、安装 vuex 依赖包:

npm i vuex@4 -S

2、创建 store/index.js,并编写下列示例代码

// 1. 导入
import { createStore } from 'vuex'

// 2. 创建一个新的 store 实例
const store = createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {}
})

// 3. 默认导出
export default store

3、在 main.js 中配置 store

import { createApp } from 'vue'
import App from './App.vue'
// 导入 store
import store from './store'

const app = createApp(App)

// 将 store 实例作为插件安装
app.use(store)
app.mount('#app')

总结: 和 vue-router 的使用方式一样。初始化,编写规则,然后再main.js中使用


2. state

state 提供唯一的公共数据源 ,所有共享的数据都要统一放到 Store 的 state 中进行存储

2.1 定义数据

const store = createStore({
  state: {
    // 表示state需要管理的数据
    count: 0
  }
})

2.2 使用数据

1、原始形式,组件中可以使用 this.$store 获取到的store对象,再通过 state 属性获取 count, 如下

<template>
  <div>state的数据:{{ this.$store.state.count }}</div>
</template>

2、计算属性,将 state 属性定义在计算属性中

<template>
  <div>state的数据:{{ this.$store.state.count }}</div>
  <div>state的数据:{{ count }}</div>
</template>

<script>
export default {
  // 把state中数据,定义在组件内的计算属性中
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}
</script>

3、辅助函数,mapState

mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便用法

  • 导入 mapState 函数,然后使用延展运算符将导出的状态映射给计算属性
// 1. 从 vuex 中按需导入 mapState 函数
import { mapState } from 'vuex'

export default {
  // 2. 将全局数据,映射为当前组件的计算属性
  computed: {
    ...mapState(['count'])
  }
}

3. mutations

Mutation 用于变更 Store中 的数据。

  • 只能通过 mutation 变更 Store 数据,不可以直接操作 Store 中的数据。
  • mutation 必须是同步函数,这样才能立刻捕捉 state 改变前后的快照

3.1 定义 mutations

mutations: {
  // 修改数据的函数
  add (state) {
    state.count++
  },
  // 带参数修改数据的函数,num 是传入的参数
  addn (state, num) {
    state.count += num
  }
},

3.2 使用 mutations

1、原始形式,this.$store.commit

<template>
   <div>state的数据:{{ this.$store.state.count }}</div>
  <button @click="add">累加1</button>
  <button @click="addn(10)">累加10</button>
</template>

<script>
export default {
  methods: {
    add () {
      // 调用store中的mutations,commit('muations名称')
      this.$store.commit('add')
    },
    addn () {
      // 传入参数
      this.$store.commit('add', 10)
    }
  }
}
</script>

2、辅助函数,mapMutations,和 mapState 使用很像

// 1. 从 vuex 中按需导入 mapMutations 函数
import { mapMutations } from 'vuex'

export default {
  methods: {
    // 2. 将指定的 mutations 函数,映射为当前组件的 methods 函数
    ...mapMutations(['add', 'addn'])
  }
}

需要注意的是:mutations 中不能写异步代码,如果有异步的 ajax 请求,应该放置在 actions 中


4. actions

如果通过异步操作变更数据,必须通过 Action,而不能使用 Mutation,但是在 Action 中还是要通过触发
Mutation 的方式间接变更数据。

4.1 定义 actions

actions: {
  // 获取异步的数据,context表示当前的store的实例
  // 可以用context.state获取状态、context.commit提交mutations、context.diapatch调用其他的action
  addAsync (context) {
    setTimeout(() => {
      // 一秒钟之后去修改state
      context.commit('add')
    }, 1000)
  },
  // 带参
  addnAsync (context, payload) {
    setTimeout(() => {
      context.commit('addn', payload)
    }, 1000)
  }
}

4.2 使用 actions

1、原始调用,this.$store.dispatch

<template>
  <div>state的数据:{{ this.$store.state.count }}</div>
  <button @click="addAsync">异步累加1</button>
  <button @click="addnAsync(20)">异步累加20</button>
</template>

<script>
export default {
  methods: {
    addAsync () {
      this.$store.dispatch('addAsync')
    },
    addnAsync (payload) {
      this.$store.dispatch('addnAsync', payload)
    }
  }
}
</script>

2、辅助函数,mapActions

// 1. 从 vuex 中按需导入 mapActions 函数
import { mapActions } from 'vuex'

export default {
  // 2. 将指定的 actions 函数,映射为当前组件的 methods 函数
  methods: {
    ...mapActions(['addAsync', 'addnAsync'])
  }
}

5. getters

getter 用于对 store 中的数据进行加工处理形成新的数据。例如对列表进行过滤并计数

  • store 中数据发生变化, getter 的数据也会跟着变化,可以把 getter 认为是 store 的计算属性

5.1 定义getters

const store = createStore({
  state: {
    count: 10
  },
  // getters函数的第一个参数是 state,必须要有返回值
  getters: {
    cubeCount (state) {
      return Math.pow(state.count, 3)
    }
  }
})

5.2 使用getters

1、原始调用,通过this直接使用

<template>
  <div>{{ $store.getters.cubeCount }}</div>
</template>

2、辅助函数,mapGetters

<template>
  <div>{{ cubeCount }}</div>
</template>

<script>
// 1. 从 vuex 中按需导入 mapGetters 函数
import { mapGetters } from 'vuex'

export default {
  // 2. 将全局数据,映射为当前组件的计算属性
  computed: {
    ...mapGetters(['cubeCount'])
  }
}
</script>

6. modules

当项目变得越来越大的时候,store 对象就会变得相当臃肿,此时就需要 Vuex 的模块化

6.1 定义modules

建议模块都带上命名控件,这样分割的更彻底,更好维护

// 导入
import { createStore } from 'vuex'

// A模块
const moduleA = {
  // 开启命名空间:让你的 state mutations getters actions 完全分割
  namespaced: true,
  // 避免数据污染,模块中state建议写成函数
  state () {
    return { count: 100 }
  },
  mutations: {
    add (state) {
      state.count++
    }
  },
  actions: {
    addAsync (ctx) {
      setTimeout(() => {
        ctx.commit('add')
      }, 1000)
    }
  },
  getters: {
    cubeCount (state) {
      return Math.pow(state.count, 3)
    }
  }
}

// B模块
const moduleB = {
  namespaced: true,
  // 避免数据污染,模块中state建议写成函数
  state () {
    return { count: 1000 }
  },
  mutations: {
    addn (state) {
      state.count += 100
    }
  }
}

// 创建一个新的 store 实例
const store = createStore({
  // modules 配置选项定义模块
  modules: {
    a: moduleA,
    b: moduleB
  }
})

// 默认导出
export default store

6.2 使用带命名空间的模块

使用时带上模块名称: 模块名称/函数名称 ,使用辅助函数:(模块名称,[函数名称,...])

<template>
  A组件 {{ a.count }} --- {{ cubeCount }}
  <br />
  B组件 {{ b.count }}
  <br />
  <button @click="add">A 累计1</button>
  <button @click="addn(100)">B 累计100</button>
  <button @click="addAsync">A 异步累加数据</button>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  computed: {
    // 'a' 是模块名称
    ...mapState({
      a: (state) => state.a,
      b: (state) => state.b
    }),
    ...mapGetters('a', ['cubeCount'])
  },
  methods: {
    // 定义了add函数调用,类似:this.$store.commit('a/add')
    ...mapMutations('a', ['add']),
    ...mapMutations('b', ['addn']),
    // 定义了addAsync函数调用 this.$store.dispatch('a/addAsync')
    ...mapActions('a', ['addAsync'])
  }
}
</script>

四、Vuex 的 todoList 案例

1. 案例效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZif4cb1-1638001671065)(image/image-20211127143645397.png)]

2. 实现步骤

  1. 搭建项目基本结构
  2. 动态加载任务列表数据,实现文本框与store数据的双向同步
  3. 完成添加、删除任务事项的操作
  4. 动态绑定复选框的选中状态
  5. 剩余项统计与清除完成事项
  6. 点击选项卡切换事项

3. 搭建项目基本结构

1、创建项目名,安装依赖

vue create vuex-todolist
npm install vuex axios ant-design-vue –S

2、然后打开 public 文件夹,创建一个 list.json 文件,文件代码如下:

[
  {
      "id": 0,
      "info": "Racing car sprays burning fuel into crowd.",
      "done": false
  },
  {
      "id": 1,
      "info": "Japanese princess to wed commoner.",
      "done": false
  },
  {
      "id": 2,
      "info": "Australian walks 100km after outback crash.",
      "done": false
  },
  {
      "id": 3,
      "info": "Man charged over missing wedding girl.",
      "done": false
  },
  {
      "id": 4,
      "info": "Los Angeles battles huge wildfires.",
      "done": false
  }
]

3、再接着,打开main.js,添加store.js的引入,如下:

import { createApp } from 'vue'
import App from './App.vue'
// 1. 导入 store
import store from './store'
// 2. 导入 ant-design-vue 组件库,样式表
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'

const app = createApp(App)

// 3. store 和 Antd 安装
app.use(store).use(Antd)
app.mount('#app')

4. 动态加载任务列表数据

1、打开 store.js,添加 axios 请求 json 文件获取数据,代码如下:

import { createStore } from 'vuex'
import axios from 'axios'

export default createStore({
  state: {
    // 所有任务列表
    list: [],
    // 文本输入框中的值
    inputValue: 'AAA',
    // 下一个id
    nextId: 5
  },
  mutations: {
    initList (state, list) {
      state.list = list
    },
    setInputValue (state, value) {
      state.inputValue = value
    }
  },
  actions: {
    getList (context) {
      axios.get('/list.json').then(({ data }) => {
        context.commit('initList', data)
      })
    }
  }
})

2、打开 App.vue 文件,将 store 中的数据获取并展示:

<template>
  <div id="app">
    <a-input
      placeholder="请输入任务"
      class="my_ipt"
      :value="inputValue"
      @change="handleInputChange"
    />
    <a-button type="primary">添加事项</a-button>

    <a-list bordered :dataSource="list" class="dt_list">
      <template #renderItem="{ item }">
        <a-list-item>
          <!-- 复选框 -->
          <a-checkbox :checked="item.done">{{ item.info }}</a-checkbox>
          <!-- 删除链接 -->
          <a>删除</a>
        </a-list-item>
      </template>

      <!-- footer区域 -->
      <div class="footer">
        <!-- 未完成的任务个数 -->
        <span>0条剩余</span>
        <!-- 操作按钮 -->
        <a-button-group>
          <a-button type="primary">全部</a-button>
          <a-button>未完成</a-button>
          <a-button>已完成</a-button>
        </a-button-group>
        <!-- 把已经完成的任务清空 -->
        <a>清除已完成</a>
      </div>
    </a-list>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  data () {
    return {}
  },
  created () {
    this.$store.dispatch('getList')
  },
  methods: {
    handleInputChange (e) {
      this.$store.commit('setInputValue', e.target.value)
    }
  },
  computed: {
    ...mapState(['list', 'inputValue'])
  }
}
</script>

<style scoped>
#app {
  padding: 10px;
}
.my_ipt {
  width: 500px;
  margin-right: 10px;
}
.dt_list {
  width: 500px;
  margin-top: 10px;
}
.footer {
  padding: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

5. 添加和删除任务事项

1、打开App.vue文件,给"添加事项"按钮、"删除"超链接,分别绑定点击事件

<!-- 绑定事件 -->
<a-button type="primary" @click="addItemToList">添加事项</a-button>

<!-- 删除链接 -->
<a @click="removeItemById(item.id)">删除</a>

2、在 methods 下编写处理函数:

addItemToList () {
  // 向列表中新增事项
  if (this.inputValue.trim().length <= 0) {
    return this.$message.warning('文本框内容不能为空')
  }
  this.$store.commit('addItem')
},
removeItemById (id) {
  // 根据id删除事项
  this.$store.commit('removeItem', id)
}

3、打开 store.js 文件,在 mutations 下编写 addItem 和 removeItem:

// 添加列表项
addItem (state) {
  const obj = {
    id: state.nextId,
    info: state.inputValue.trim(),
    done: false
  }
  // 将创建好的事项添加到数组list中
  state.list.push(obj)
  // 将nextId值自增
  state.nextId++
  state.inputValue = ''
},
// 根据id删除事项数据
removeItem (state, id) {
  const index = state.list.findIndex((x) => x.id === id)
  if (index !== -1) state.list.splice(index, 1)
}

6. 动态绑定复选框的选中状态

1、打开App.vue文件,给“复选”按钮绑定点击事件,编写处理函数

<!-- 复选框 -->
<a-checkbox
  :checked="item.done"
  @change="cbStateChanged(item.id, $event)"
  >{{ item.info }}</a-checkbox

2、在 methods 下编写函数

// 复选框状态改变时触发
cbStateChanged (id, e) {
  const param = {
    id: id,
    status: e.target.checked
  }
  // 根据id更改事项状态
  this.$store.commit('changeStatus', param)
}

3、打开 store.js 在 mutations 下编写 changeStatus

changeStatus (state, param) {
  // 根据id改变对应事项的状态
  const index = state.list.findIndex((x) => x.id === param.id)
  if (index !== -1) state.list[index].done = param.status
}

7. 剩余项统计与清除完成事项

1、打开store.js,在 mutations 下添加 cleanDone 清除方法,添加 getters 完成剩余项统计

cleanDone (state) {
  state.list = state.list.filter((x) => x.done === false)
}

// ... 省略中间代码
getters: {
  unDoneLength (state) {
    const temp = state.list.filter((x) => x.done === false)
    return temp.length
  }
}

2、打开 App.vue,使用 getters 展示剩余项,并给“清除已完成”按钮绑定点击事件

<!-- 未完成的任务个数 -->
<span>{{ unDoneLength }}条剩余</span>

... 省略中间代码

<!-- 把已经完成的任务清空 -->
<a @click="clean">清除已完成</a>

3、在 methods 下编写 clean 函数,并添加 getters 的映射

// 导入getters
import { mapState,mapGetters } from 'vuex'

// 映射
computed: {
  ...mapState(['list', 'inputValue']),
  ...mapGetters(['unDoneLength'])
}

// 编写事件处理函数
clean () {
  // 清除已经完成的事项
  this.$store.commit('cleanDone')
}

8. 点击选项卡切换事项

1、打开App.vue,给全部,未完成,已完成三个选项卡绑定点击事件,修改 a-list 数据源为 infoList

<a-list bordered :dataSource="infoList" class="dt_list">
// ...省略其他代码

<!-- 操作按钮 -->
<a-button-group>
  <a-button
    :type="viewKey === 'all' ? 'primary' : 'default'"
    @click="changeList('all')"
  >全部</a-button>
  <a-button
    :type="viewKey === 'undone' ? 'primary' : 'default'"
    @click="changeList('undone')"
  >未完成</a-button>
  <a-button
    :type="viewKey === 'done' ? 'primary' : 'default'"
    @click="changeList('done')"
  >已完成</a-button>
</a-button-group>

2、编写处理函数,并将列表数据来源更改为一个 getters。

// 编写事件处理函数以及映射计算属性
methods:{
  // ...省略其他代码
  changeList( key ){
    // 点击“全部”,“已完成”,“未完成”时触发
    this.$store.commit('changeKey',key)
  }
},
computed:{
  ...mapState(['list','inputValue','viewKey']),
  ...mapGetters(['unDoneLength','infoList'])
}

3、打开 store.js,添加 getters,mutations,state

state: {
  // ...省略其他代码
  // 保存默认的选项卡值
  viewKey: 'all'
},
mutations: {
  // ...省略其他代码
  changeKey (state, key) {
    // 当用户点击“全部”,“已完成”,“未完成”选项卡时触发
    state.viewKey = key
  }
},
// ...省略其他代码
getters: {
  // ...省略其他代码
  infoList (state) {
    if (state.viewKey === 'all') {
      return state.list
    }
    if (state.viewKey === 'undone') {
      return state.list.filter((x) => x.done === false)
    }
    if (state.viewKey === 'done') {
      return state.list.filter((x) => x.done === true)
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值