Vue.js状态管理工具【vuex 建议收藏】

Vuex

1.Vuex 介绍

Vuex 是一个专为Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

###1. 为什么要使用 Vuex

为了实现复杂层级的组件间数据传递、共享

  • Vuex 是一个专为 Vue.js 应用程序开发的集中式的状态(数据)管理的工具。
  • Vuex 最主要去做的事情就是 实现 组件与组件之间的数据传递。

###2. Vue**基础学习的组件通信有哪些?

  • 父传子 : props
  • 子传父 : $emit
  • 兄弟组件之间的数据传递 : eventBus 事件中心

3.vuex 核心内容

  • state: state 是一个对象,所有 Vuex 的数据都会保存在 state 中。
  • mutations: 所有要放到 state 的数据,不能直接放进去,必须通过 mutations 来放进去
  • actions: 专门用来处理异步操作的东西。

2.vuex 基础使用

  1. 下包
yarn add vuex
  1. 导入到 main.js
improt Vuex from 'vuex';
  1. 全局注册
Vue.use(Vuex); //调用了 vuex 中的 一个 install() 方法
  1. 创建 Vuex 实例对象
const Vuex = new Vuex.store({});
  1. 将 store 对象挂载到 vue 对象上
new Vue({
  render: (h) => h("#app"),
  store,
});

3. state

state 是放置所有公共状态的属性,如果你有一个公共状态数据 , 你只需要定义在 state 对象中

// 初始化vuex对象
const store = new Vuex.Store({
  state: {
    // 管理数据
    count: 0,
    name: "张三",
    age: 18,
  },
});

调用 state 里的数据的三种方法:

  1. 方式一: 通过 state 属性属性获取 count
<template>
  <p>state的数据:{{ $store.state.count }}</p>
</template>
  1. 方式二 :state 属性定义在计算属性中,自动将指定数据挂载到 computed 上
<template>
  <p>state的数据:{{ count }}</p>
</template>
<script>
export default{
  computed: {
    count() {
      return this.$store.state.count,
    },
  },
}
</script>
  1. 方法三:使用辅助函数 - mapState
<template>
  <div>state的数据:{{ count }}</div>
  <div>state的数据:{{ name }}</div>
  <div>state的数据:{{ age }}</div>
</template>
<script>
//1.引入 mapState 函数
import { mapState } from "vuex";
export default{
  computed: {
    // 2利用延展运算符将导出的状态映射给计算属性
    ...mapState(["count", "name", "age"]),
  },
}
</script>

4. mutations

mutations 是一个对象,对象中存放修改 state 的方法

定义mutations

// 初始化vuex对象
const store = new Vuex.Store({
  state: {
    // 管理数据
    count: 0,
    name: "张三",
    age: 18,
  },
  mutations: {
    //参数一:必须是 state ,不用手动传
    //参数二:调用时传入的参数payload(载荷)
    addCount(state, count) {
      state.count += count;
    },
  },
});

调用 mutations 里的 方法 的两种方法:

  1. 方式一:原始形式-$store
<template>
  <div>
    <button @click="addCount">count加+</button>
  </div>
</template>
<script>
export default {
  // 方法一原始形式-$store
  methods: {
    addCount() {
    // 参数一:方法名 参数二 参数
       this.$store.commit("addCount", 10);
    },
  },
};
</script>
  1. 方法二:辅助函数 - mapMutations
<template>
  <div>
    <button @click="addCount(100)">count加100+</button>
  </div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
  // 方法一原始形式-$store
  methods: {
    addCount() {
    // 参数一:方法名 参数二 参数
      ...mapMutations(["addCount"]),
    },
  },
};
</script>

5. actions

什么时候使用 **actions **?

  • 当我们需要进行异步操作的时候(比如发送请求),我们就应该创建一个 action 来进行异步操作
  • 因为 mutations 不支持异步操作

actions 的使用注意:不能直接修改 state 的数据,必须通过 mutations 方法间接的修改 state 的数据

访问形式有以下两种:

定义actions

// 初始化vuex对象
const store = new Vuex.Store({
  state: {
  ...
  },
  mutations: {
  ...
  },
  actions: {
    getAsyncCount(context) {
      // cintext 对象-> $store
      // action 不能做直接操作
      //可以通过 context.state 获取状态 也可以通过context.commit 来提交mutations, 也可以 context.diapatch调用其他的action
      setTimeout(() => {
        context.commit("addCount", 123);
      }, 1000);
    },
  },
});
  1. 原始调用 - $store
<template>
  <div>
    <button @click="addAsyncCount">异步原始调用+100</button>
  </div>
</template>
<script>
export default {
  methods: {
    addAsyncCount() {
      //actions 方法一 原始调用 - $store
      this.$store.dispatch("getAsyncCount");
    },
  },
};
</script>
  1. 辅助函数 -mapActions
<template>
  <div>
     <button @click="getAsyncCount">异步辅助函数 -mapActions+111</button>
  </div>
</template>
<script>
import { mapActions } from "vuex";
export default {
  methods: {
    addAsyncCount() {
      // actions 方法二
    ...mapActions(["getAsyncCount"]),
    },
  },
};
</script>

6. getters

在什么时候使用 getters

  • 当我们有一个 state 中的数据经常需要使用,我们就可以为这个数据建立一个 getters
  • 或者我们可以把多个数据整合起来制作成一个 getters ,提供访问
  • 再者也可能我们需要得到数据需要进行计算,我们可以创建一个 getters 进行计算,并得到最后的结果
  • getters 可以作为提供快速访问数据的一种形式,也可以作为类似计算属性的方式来使用

使用方式有以下两种:

定义  getters

// 初始化vuex对象
const store = new Vuex.Store({
  state: {
  ...
  list: [1,2,3,4,5,6,7,8,9,10]
  },
  mutations: {
  ...
  },
  actions: {
  ...
  },
  getters:{
    // getters函数的第一个参数是 state
    // 必须要有返回值
    filterList:  state =>  state.list.filter(item => item > 5)
  }
});
  1. 原始形式-$store
<template>
<div>{{ $store.getters.filterList }}</div>
</template>
  1. 辅助函数 - mapGetters
<template>
  <div>
      {{ filterList }}
  </div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
  computed: {
    ...mapGetters(['filterList'])
  }
};
</script>

7.模块化-Module

Vuex为什么要有模块化?

  • 所有的数据都放在 state 中,所有修改数据的方法都放在 mutations ,所有的异步操作都放在 actions 里面,会导致 Vuexstore 对象非常臃肿,后续做维护,添加新功能都会比较费劲
  • 而通过模块化,我们可以将主模块 Vuexstore 拆分成多个子模块,功能划分更加清晰,后续维护更新更易查找

8.模块化的简单应用

应用

定义两个模块   user 和  setting

user中管理用户的状态  token

setting中管理 应用的名称 name

// 初始化vuex对象
const store = new Vuex.Store({
  state: {...},
  mutations: {...},
  actions: {...},
  getters:{
   // ...
   token: state => state.user.token,
   name: state => state.setting.name,
  },
  modules: {
    user: {
      state: {
        token: "12345",
      },
    },
    setting: {
      state: {
        name: "Vuex实例",
      },
    },
  },
});

调用:

  1. 原始方法
<template>
  <div>
      <div>用户token {{ $store.state.user.token }}</div>
      <div>网站名称 {{ $store.state.setting.name }}</div>
  </div>
</template>
  1. 通过mapGetters引用 访问模块数据
<template>
  <div>
      <div>用户token {{ token }}</div>
      <div>网站名称 {{ name }}</div>
  </div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
  computed: {
    // 通过mapGetters引用 访问模块数据
    ...mapGetters(["token", "name"]),
  }
};
</script>

9. 模块化中的命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

// 初始化vuex对象
const store = new Vuex.Store({
  state: {...},
  mutations: {...},
  actions: {...},
  getters:{
   // ...
   token: state => state.user.token,
   name: state => state.setting.name,
  },
  modules: {
    user: {
    // 想保证内部模块高封闭性,我们可以采用namespaced来进行设置
      namespaced: true,
      state: {
        token: "12345",
      },
      mutations: {
       //  这里的state表示的是user的state
        updateToken(state) {
          state.token = 678910;
        },
      },
    },
    setting: {
      state: {
        name: "Vuex实例",
      },
    },
  },
});

方案1:直接调用-带上模块的属性名路径

<template>
  <div>
    <button @click="test">修改token</button>
  </div>
</template>
<script>
export default {
   methods: {
    test () {
      this.$store.dispatch('user/updateToken') // 直接调用方法
    }
  }
};
</script>

方案2:辅助函数-带上模块的属性名路径

<template>
  <div>
    <button @click="test">修改token</button>
  </div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
 methods: {
       ...mapMutations(['user/updateToken']),
       test () {
           this['user/updateToken']()
       }
   }
};
</script>

方案3: createNamespacedHelpers  创建基于某个命名空间辅助函数

<template>
  <div>
    <button @click="updateToken">修改token2</button>
  </div>
</template>
<script>
import { mapGetters, createNamespacedHelpers } from 'vuex'
const { mapMutations } = createNamespacedHelpers('user')
export default {
 methods: {
        // 通过mapMutations调用
    ...mapMutations(["updateToken"]),
   }
};
</script>

10 头条案例

难点总结

先后对比:

Actions => 封装的请求函数

Mutations   =>   获取到数据后,赋值给 data

State    => data

Getters => computed

子模块

调用 dispatch / commit 时 一定要加模块名:

this.$store.dispatch('catagtory/getCatagtry')

对象解构

目标:更方便的读取数据,如果对象解构为您带来了烦恼,就忘掉它。

接口

获取频道列表 
http://ttapi.research.itcast.cn/app/v1_0/channels

获取频道头条
http://ttapi.research.itcast.cn/app/v1_1/articles?channel_id=频道id&timestamp=时间戳&with_top=1

第一步:项目初始化

通过vue-cli脚手架搭建项目

vue create toutiao  #创建项目

选择  vuex / eslint(stanadard) / pre-cssprocesser (less)  / router随意 => 确定

脚手架配置:

选择模式->选择 手动选择(支持更多自定义选项)

Vue CLI v4.2.3
? Please pick a preset:
  default (babel, eslint)
> Manually select features

Tips

default:默认勾选 babeleslint,回车之后直接进入装包

manually:自定义勾选特性配置,选择完毕之后,才会进入装包

选择第 2 种:手动选择特性,支持更多自定义选项

配置对应插件

? Please pick a preset: Manually select features
? Check the features needed for your project:
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 (*) Vuex
 (*) CSS Pre-processors
>(*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

Tips:

分别选择:

Babel:es6 转 es5

Vuex:数据容器,存储共享数据

CSS Pre-processors:CSS 预处理器,后面会提示你选择 less、sass、stylus 等

Linter / Formatter:代码格式校验

选择 CSS 预处理器

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
> Less
Stylus

选择代码格式校验的配置方式,采用标准配置即可

? Pick a linter / formatter config:
  ESLint with error prevention only
  ESLint + Airbnb config
> ESLint + Standard config
  ESLint + Prettier

Tips:这里选择 ESLint + Standard config

选择校验工具,并且选择什么时机下触发代码校验工具

? Pick additional lint features:
>(*) Lint on save
( ) Lint and fix on commit
  • Lint on save:每当保存文件的时候
  • Lint and fix on commit:每当执行 git commit 提交的时候

选择工具配置文件保存位置

? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
  In package.json

TipsBabelESLint 等工具的一些额外配置信息写入位置:

  • In dedicated config files:分别保存到单独的配置文件
  • In package.json:保存到 package.json 文件中

这里建议选择第 1 个,保存到单独的配置文件,这样方便我们做自定义配置

选择是否保存模板

? Save this as a preset for future projects? (y/N) N

Tips:这里里是问你是否需要将刚才选择的一系列配置保存起来,然后它可以帮你记住上面的一系列选择,以便下次直接重用

这里根据自己需要输入 y 或者 n,我这里输入 n 不需要

安装 axios

yarn add axios

在main.js中引入样式(该样式在资源/vuex样式中,拷贝到styles目录下)

import './styles/index.css'

拷贝图片资源到assets目录下(在资源/vuex样式目录下的图片

封装组件并引入

components/category.vue

<template>    
   <ul class="catagtory">
        <li class='select'>开发者资讯</li>
        <li>ios</li>
        <li>c++</li>
        <li>android</li>
        <li>css</li>
        <li>数据库</li>
        <li>区块链</li>
        <li>go</li>
        <li>产品</li>
        <li>后端</li>
        <li>linux</li>
        <li>人工智能</li>
        <li>php</li>
        <li>javascript</li>
        <li>架构</li>
        <li>前端</li>
        <li>python</li>
        <li>java</li>
        <li>算法</li>
        <li>面试</li>
        <li>科技动态</li>
        <li>js</li>
        <li>设计</li>
        <li>数码产品</li>
        <li>html</li>
        <li>软件测试</li>
        <li>测试开发</li>
      </ul>
</template>

components/news.vue

<template> 
  <div class="list">
        <div class="article_item">
          <h3 class="van-ellipsis">python数据预处理 :数据标准化</h3>
          <div class="img_box">
             <img src="@/assets/back.jpg"
            class="w100" />
          </div>
          <!---->
          <div class="info_box">
            <span>13552285417</span>
            <span>0评论</span>
            <span>2018-11-29T17:02:09</span>
          </div>
        </div>
      </div>
</template>

App.vue

<template>
  <!-- app.vue是根组件 -->
  <div id="app">
    <category />
    <news />
  </div>
</template>
<script>
import category from "./components/category";
import news from "./components/news";

export default {
  components: {
    category,
    news,
  },
};
</script>

第二步:设计 Vuex 子模块

在store目录下新建目录modules, 新建 category.js 和 news.js

模块结构

export default {
  namespaced: true,
  state: {},
  mutations: {},
  actions: {}
}

在 store/index.js 中引入定义的两个模块

import category from './modules/category'
import news from './modules/news'
 export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    category,
    news
  }
})

第三步:分类组件 - 渲染

  1. 分类模块的 store 中定义数据
  2. 分类组件初始化时调用 action 获取数据
  3. 分类模块的 store 中定义 actions、mutations
  4. 全局模块定义 getters 以方便获取分类数据
  5. 模板中使用 v-for 进行渲染

store/modules/category.js

import axios from 'axios'
// 第三步:分类模块 - 渲染
// 3.1 store 中定义数据
export default {
  namespaced: true,
  state: {
    categoryDatas: []
  },
  mutations: {
    updateCategoryDatas(state, categoryDatas) {
      // 3.5 将 action 传过来的数据设置到 state 中
      state.categoryDatas = categoryDatas
    }
  },
  actions: {
    async getCategoryDatas(context) {
      // console.log('getCategoryDatas 触发了')
      // 3.3 使用 axios 发送请求 获取数据
      let result = await axios.get('http://ttapi.research.itcast.cn/app/v1_0/channels')
      // 3.4 调用 mutations 中的方法设置获取的数据
      // console.log(result)
      context.commit('updateCategoryDatas', result.data.data.channels)
    }
  },
}

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import category from './modules/category'
import news from './modules/news'

// 第二步: 设计 Vuex 子模块
export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    category,
    news
  },
  getters: {
    // 3.6 全局模块定义 categoryDatas 以便组件中获取数据
    categoryDatas: state => state.category.categoryDatas
  }
})

components/category.vue

<template>
  <ul class="catagtory">
    <li
      :class="{ select: item.id == currentId }"
      v-for="item in categoryDatas"
      :key="item.id"
    >
      {{ item.name }}
    </li>
  </ul>
</template>

<script>
// import { mapGetters, mapActions } from 'vuex'
import { mapGetters, createNamespacedHelpers } from "vuex";
let { mapActions } = createNamespacedHelpers("category");
export default {
  created() {
    // 3.2 在分类组件初始化时发送请求(调用 action 的方法)
    // 如果只有一个 action 时不需要使用辅助函数导入
    // this.$store.dispatch('category/getCategoryDatas')
    // this['category/getCategoryDatas']()
    this.getCategoryDatas();
  },
  computed: {
    ...mapGetters(["categoryDatas"]),
  },
  methods: {
    // ...mapActions(['category/getCategoryDatas'])
    ...mapActions(["getCategoryDatas"])
  },
};
</script>

第四步:分类组件 - 点击切换类样式

  1. 分类模块的 store 中定义当前分类 id 数据
  2. 全局模块定义 getters 以方便获取当前分类 id 数据
  3. 模板中使用 :class 动态绑定类名,实现高亮
  4. 分类模块的 store 中定义 mutations
  5. 获取分类数据时,调用 mutation 设置默认高亮分类 id
  6. 绑定点击事件,点击时触发 mutation 修改当前分类 id

store/modules/category.js

import axios from 'axios'
// 第三步:分类模块 - 渲染
// 3.1 store 中定义数据
// 4.1 store 中定义当前分类 id
export default {
  namespaced: true,
  state: {
    categoryDatas: [],
    currentId: ''
  },
  mutations: {
    updateCategoryDatas(state, categoryDatas) {
      // 3.5 将 action 传过来的数据设置到 state 中
      state.categoryDatas = categoryDatas
    },
    // 4.4 分类模块的 store 中定义 mutations
    updateCurrentId(state, id) {
      state.currentId = id
    }
  },
  actions: {
    async getCategoryDatas(context) {
      // console.log('getCategoryDatas 触发了')
      // 3.3 使用 axios 发送请求 获取数据
      let result = await axios.get('http://ttapi.research.itcast.cn/app/v1_0/channels')
      // 3.4 调用 mutations 中的方法设置获取的数据
      // console.log(result)
      context.commit('updateCategoryDatas', result.data.data.channels)
      // 4.5 获取分类数据时,调用 mutation 设置默认高亮分类 id
      context.commit('updateCurrentId', result.data.data.channels[0].id)
    }
  },
}

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import category from './modules/category'
import news from './modules/news'

// 第二步: 设计 Vuex 子模块
export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    category,
    news
  },
  getters: {
    // 3.6 全局模块定义 categoryDatas 以便组件中获取数据
    categoryDatas: state => state.category.categoryDatas,
    // 4.2 全局模块定义 currentId
    currentId: state => state.category.currentId
  }
})

components/category.vue

<template>
  <ul class="catagtory">
    <!-- $store.commit('category/updateCurrentId', item.id) -->
    <!-- 
      4.3 模板中使用 :class 动态绑定类名,实现高亮 
      4.6 绑定点击事件,点击时触发 mutation 修改当前分类 id
      -->
    <li
      @click="updateCurrentId(item.id)"
      :class="{ select: item.id == currentId }"
      v-for="item in categoryDatas"
      :key="item.id"
    >
      {{ item.name }}
    </li>
  </ul>
</template>

<script>
// import { mapGetters, mapActions } from 'vuex'
import { mapGetters, createNamespacedHelpers } from "vuex";
let { mapActions, mapMutations } = createNamespacedHelpers("category");
export default {
  created() {
    // 3.2 在分类组件初始化时发送请求(调用 action 的方法)
    // 如果只有一个 action 时不需要使用辅助函数导入
    // this.$store.dispatch('category/getCategoryDatas')
    // this['category/getCategoryDatas']()
    this.getCategoryDatas();
  },
  computed: {
    ...mapGetters(["categoryDatas", "currentId"]),
  },
  methods: {
    // ...mapActions(['category/getCategoryDatas'])
    ...mapActions(["getCategoryDatas"]),
    ...mapMutations(["updateCurrentId"]),
  },
};
</script>

第五步:新闻组件 - 渲染

  1. 新闻模块的 store 中定义数据
  2. 在新闻组件中监听当前分类 id 的变化,改变时触发 action 获取数据
  3. 新闻模块的 store 中定义 actions、mutations
  4. 全局模块定义 getters 以方便获取新闻数据
  5. 模板中使用 v-for 进行渲染
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值