# 概述
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
在 vue 项目下 npm i vuex --save
每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
vuex 只能有一个仓库 (store)
mutations 函数是同步函数,所以不能有异步操作
# 如何创建和使用
-
App.vue
<template> <div id="app"> <h1>hello vuex</h1> <p>获取 store 中的 count: {{ count }}</p> <button @click="add">加一</button> <button @click="sub">减一</button> <button @click="change">将count改成100</button> </div> </template> <script> export default { components: {}, name: "App", computed: { // 一般来说搭配计算属性获取 store 数据 count() { return this.$store.state.count; }, }, methods: { add() { // this.$store.commit("add"); this.$store.commit({ type: "add" }); }, sub() { this.$store.commit("sub"); }, change() { this.$store.commit("change", 100); // this.$store.commit("change", { count: 100 }); // this.$store.commit({ type: "change", count: 100 }); }, }, }; </script> -
src 下新建的 store 文件夹 中的 index.js 文件
// store // 1.如何创建 import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); // Store 这里的S一定要是大写的,这是一个类 const store = new Vuex.Store({ // state相当于组件data的返回值 state: { count: 10, }, // store 中的数据要修改的话需要定义 mutations 函数 // mutation 函数 就是用来修改 store 数据的 // 每一个 mutation 函数默认第一个参数接收的是 store 内的 state,想要修改哪个数据直接修改即可 mutations: { add(state) { state.count++; }, sub(state) { state.count > 1 && state.count--; }, // mutation 函数的第二个以及后面的参数叫做载荷数据 // mutation 函数 只能接收两个参 change(state, count) { state.count = count; }, /* change(state, { count }) { state.count = count; },// 后两种写法 */ }, // 定义好 mutation 函数之后 // 在组件内可以使用 $store 内的 commit 方法来触发对应 mutation 函数 // 比如 this.$store.commit('add') // 比如 this.$store.commit({type:'add'}) // mutation 函数传参写法 // this.$store.commit('add',payload) 接收的时候 payload 就是第二个参数 // this.$store.commit({type:'add',count:值}) 接收的时候 commit传递的对象就是第二个参数 }); // 2.导出 export default store; // 3.组件中使用 // a.在main.js中导入,然后挂载到 new Vue中; // b.在组件内直接使用 this.$store.state.count 访问 -
main.js 文件
import store from "./store/index"; // 后边的 /index 可以不用引入,因为是 index new Vue({ store, render: (h) => h(App), }).$mount("#app");
# 评论小案例
-
Article.vue
<template> <div class="article"> <ArticleContent /> <ArticleComment /> <button @click="getCommentById">根据id获取对应的评论数据 log出来</button> </div> </template> <script> import ArticleContent from "./ArticleContent"; import ArticleComment from "./ArticleComment"; export default { components: { ArticleContent, ArticleComment }, name: "Article", methods: { // 很多地方都会用到这个功能,所以直接在组件内计算不合适,最好在 store 添加一个 getter getCommentById() { console.log(this.$store.getters.getCommentById(2)); }, }, }; </script> -
ArticleContent.vue
<template> <div class="article-content" v-if="article"> <h2>{{ article.title }}</h2> <p class="comment-num">评论数:{{ commentNum }}</p> <div class="content" v-html="article.content"></div> </div> <div v-else>请稍等</div> </template> <script> export default { name: "ArticleContent", computed: { article() { return this.$store.state.article; }, commentNum() { return this.$store.getters.commentNum; }, }, }; </script> -
ArticleComment.vue
<template> <div class="article-comment"> <h3>评论 <span>评论总数:{{ commentsNum }}</span></h3> <textarea v-model.lazy.trim="text"></textarea> <button @click="submit">提交</button> <ul v-if="comments.length"> <li v-for="comment in comments" :key="comment.id"> <span>{{ comment.text }}</span> <button @click="del(comment.id)">删除</button> </li> </ul> </div> </template> <script> export default { name: "ArticleComment", computed: { comments() { return this.$store.state.comments; }, commentsNum() { return this.$store.getters.commentNum; }, }, data() { return { text: "", }; }, methods: { submit() { const { text } = this; if (text) { this.$store.commit({ type: "addComment", comment: { id: new Date().getTime(), text }, }); this.text = ""; } }, del(id) { this.$store.commit("delComment", id); }, }, }; </script> -
store 文件夹下的 index.js 文件
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { article: { title: "vuex 入门", content: "<p>贼简单</p>", }, comments: [ { id: 1, text: "666" }, { id: 2, text: "哈哈哈" }, { id: 3, text: "gogo" }, ], }, // state 的计算属性 getter getters: { // getter 默认接收 state 作为参数,函数的返回值就是计算属性的值 // 接收 getters 作为第二个参数,该参数指的是所有的getter commentNum(state, getters) { console.log(getters.a); //10 getters是一个对象 return state.comments.length; }, a() { return 10; }, // 组件中使用 this.$store.getters.commentNum // 传参的 getter // 使用的时候需要当成函数调用 // getter 函数要返回一个函数,返回的函数必须设置返回值,这个返回值是计算属性 // 返回的函数接收组件内使用getter的传参 getCommentById: (state) => (id) => state.comments.find((comment) => comment.id === id), // 第一个是 getCommentById 函数本身 // 第二个是返回的函数 }, mutations: { addComment(state, { comment }) { state.comments.push(comment); }, delComment(state, id) { state.comments = state.comments.filter((comment) => comment.id !== id); }, }, }); export default store;
# 对象更新小问题
-
ObjectState.vue
<template> <!-- 更新时会出现的问题 --> <div> <button @click="update">添加一个年龄</button> <div v-for="(value, key) in object" :key="key"> <!-- 循环对象 --> <!-- key 属性名 value属性值 --> <p><span>{{ key }}</span> : <span>{{ value }}</span></p> <!-- name: 毛茸茸 --> <!-- sex: 男 --> </div> </div> </template> <script> export default { name: "ObjectState", computed: { object() { return this.$store.state.testObject; }, }, methods: { update() { this.$store.commit("updateObject", 20); }, }, }; </script> -
index.js
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { testObject: { name: "毛茸茸", sex: "男", }, }, mutations: { updateObject: (state, age) => { // 对象的新增属性操作不能引起视图的变化,需要使用 Vue 的 set 方法添加 // Vue.set(state.testObject, "age", age); // 变量想要当成属性名的话需要加字符串 state.testObject = { ...state.testObject.age }; // state.testObject.age = age; //这个更新不了 }, }, }); export default store;
# 服务器请求数据
-
1. 请求评论
-
自建 src 文件夹下 data 文件夹中的 data.json
{ "comments": [ { "id": 1, "text": "66666", "articleId": 1 }, { "id": 2, "text": "哈哈哈", "articleId": 1 }, { "id": 3, "text": "gogo", "articleId": 2 } ], "articles": [ { "id": 1, "title": "vuex 入门", "content": "<p>贼简单</p>" }, { "id": 2, "title": "vuex 进阶", "content": "<p>不是简单</p>" } ] } -
index.js
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { comments: [], // 这里是空的 }, // state 的计算属性 getter getters: { // 传参的 getter getCommentById: (state) => (id) => state.comments.find((comment) => comment.id === id), // 第一个是 getCommentById 函数本身 // 第二个是返回的函数 }, // mutations 函数是同步函数,所以不能有异步操作 mutations: { // 这里是 请求评论函数 getComments: (state, comments) => { state.comments = comments; }, }, }); export default store; -
自己封装的 plugins 文件夹下的 axios.js
// 封装 axios // http://localhost:8080/ 向 http://localhost:3008/ 取数据,跨域 // 协议 域名 端口号 // axios 默认不支持跨域,浏览器支持 本地跨域(localhost) import axios from "axios"; import Vue from "vue"; // 基地址 axios.defaults.baseURL = "http://localhost:3008/"; // 超时时间 axios.defaults.timeout = 2500; // 响应拦截 axios.interceptors.response.use((res) => res.data); // 这里是响应的成功拦截,res.data 变成了 res // 请求拦截器 // 设置成 Vue 全局 Vue.prototype.$axios = axios; // 导出 export default axios; -
Article.vue
<template> <div class="article"> <button @click="getCommentById"> 根据id获取对应的评论数据 log出来 </button> </div> </template> <script> export default { // 这里是请求 created() { // 因为设置了基地址,所以直接写后面的地址就可以 this.$axios.get("/comments?articleId=1").then((res) => { // 因为设置了响应拦截器,res就是 res.data this.$store.commit("getComments", res); }); }, name: "Article", methods: { getCommentById() { console.log(this.$store.getters.getCommentById(2)); }, }, }; </script>
-
-
2. 请求评论和文章并发
-
Article.vue
<script> export default { created() { // 请求文章评论和详情 this.$axios .all([ this.$axios.get("/comments?articleId=1"), this.$axios.get("/articles/1"), ]) .then((res) => { this.$store.commit("getComments", res[0]); this.$store.commit("getArticle", res[1]); }); }, }; </script> -
index.js
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { article: null, comments: [], }, // mutations 函数是同步函数,所以不能有异步操作 mutations: { // 这里是请求文章函数 getArticle: (state, article) => { state.article = article; }, // 这里是请求评论函数 getComments: (state, comments) => { state.comments = comments; }, }, }); export default store;
-
# Actions
Action 可以包含异步操作
异步操作想要修改 store 中的数据时,放到 Action 中
-
请求小案例
-
Article.vue
<script> export default { created() { // 请求文章评论和详情 this.$store.dispatch("getComments"); this.$store.dispatch("getArticle"); }, }; </script> -
index.js
import axios from "../plugins/axios"; // 这里要引入自己创建的axios,否则会引入默认的axios const store = new Vuex.Store({ state: { // 文章内容 article: null, comments: [], }, // mutations 函数是同步函数,所以不能有异步操作 mutations: { // 这里是请求文章函数 getArticle: (state, article) => { state.article = article; }, // 这里是请求评论函数 getComments: (state, comments) => { state.comments = comments; }, }, // action函数,存放更新 store 数据的异步操作 // 异步执行之后使用 commit 提交 mutation // 在组件中使用 dispatch 方法触发 action // this.$store.dispatch(action函数名) actions: { // action 函数的函数名一般设置成和对应的 mutation 函数名相同 // action 函数默认第一个参数是 context(上下文),它与 store 实例具有相同的属性和方法 // 我们可以从该对象中获取 commit 方法 // context 等价于 this.$store getComments: async ({ commit }) => { // { commit } 解构赋值 取 commit // 发请求获取评论数据,然后使用 commit 提交 mutation const res = await axios.get("/comments?articleId=1"); commit("getComments", res); }, getArticle: async ({ commit }) => { // 发请求获取文章数据,然后使用 commit 提交 mutation const res = await axios.get("/articles/1"); // 用 / 拿到的是对象(对应的那一个条) ?拿到的是数组 commit("getArticle", res); }, }, }); export default store;
-
-
请求添加删除案例
-
index.js
import Vue from "vue"; import Vuex from "vuex"; import axios from "../plugins/axios"; Vue.use(Vuex); const store = new Vuex.Store({ state: { article: null, comments: [], }, // mutations 函数是同步函数,所以不能有异步操作 mutations: { // 这里是请求文章函数 getArticle: (state, article) => { state.article = article; }, // 这里是请求评论函数 getComments: (state, comments) => { state.comments = comments; }, addComment(state, { comment }) { state.comments.push(comment); }, delComment(state, id) { state.comments = state.comments.filter( (comment) => comment.id !== id ); }, }, // action函数,存放更新 store 数据的异步操作 actions: { getComments: async ({ commit }) => { // 发请求获取评论数据,然后使用 commit 提交 mutation const res = await axios.get("/comments?articleId=1"); commit("getComments", res); }, getArticle: async ({ commit }) => { // 发请求获取文章数据,然后使用 commit 提交 mutation const res = await axios.get("/articles/1"); commit("getArticle", res); }, /* // 传参的 action addComment: async ({ commit }, { comment }) => { // comment 必须和后台格式一样,id可以没有 {text,articleId} const res = await axios.post("/comments", comment); commit({ type: "addComment", comment: res }); }, */ // promise 写法,async简单一些 addComment: ({ commit }, { comment }) => { return new Promise((resolve, reject) => { axios .post("/comments", comment) .then((res) => { commit({ type: "addComment", comment: res }); resolve(); // 这里是 ArticleComment中then传的参数 }) .catch((err) => { console.log(err); reject(); // 这里是 ArticleComment中catch传的参数 }); }); // action函数内也可以执行其他的action函数,只需要从context 中拿 dispatch 即可 }, delComment: async ({ commit }, { id }) => { await axios.delete(`/comments/${id}`); commit("delComment", id); }, }, }); export default store; -
ArticleComment.vue
<template> <div class="article-comment"> <textarea v-model.lazy.trim="text"></textarea> <button @click="submit">提交</button> <ul v-if="comments.length"> <li v-for="comment in comments" :key="comment.id"> <span>{{ comment.text }}</span> <button @click="del(comment.id)">删除</button> </li> </ul> </div> </template> <script> export default { name: "ArticleComment", computed: { comments() { return this.$store.state.comments; }, }, data() { return { text: "", }; }, methods: { // async submit() { submit() { const { text } = this; if (text) { // await 只能等 promise 和 async 函数 /* await this.$store.dispatch({ type: "addComment", comment: { text, articleId: 1 }, }); this.text = ""; */ // promise写法 this.$store .dispatch({ type: "addComment", comment: { text, articleId: 1 }, }) .then(() => { this.text = ""; }) .catch(() => { console.log("出错了"); }); } }, del(id) { // this.$store.commit("delComment", id); this.$store.dispatch({ type: "delComment", id }); }, }, }; </script> -
Article.vue
<script> export default { created() { // 请求文章评论和详情 this.$store.dispatch("getArticle"); this.$store.dispatch("getComments"); }, }; </script>
-
# Modules
模块,把 store 分割成模块
-
案例 1 这里没有使用命名空间
-
Article.vue
<template> <div class="article"> <ArticleContent /> </div> </template> <script> import ArticleContent from "./ArticleContent"; export default { created() { // 请求文章评论和详情 this.$store.dispatch("getArticle"); }, components: { ArticleContent }, }; </script> -
ArticleContent.vue
<template> <div class="article-content" v-if="article"> <h2>{{ article.title }}</h2> <div class="content" v-html="article.content"></div> </div> <div v-else>请稍等</div> </template> <script> export default { name: "ArticleContent", computed: { article() { return this.$store.state.article.data; // 这里换成了data,这里的article是modules中的属性名 // this.$store.state.article 拿到的是对应的模块,模块下的state有data属性 }, }, }; </script> -
index.js
import Vue from "vue"; import Vuex from "vuex"; import axios from "../plugins/axios"; import article from "./modules/article"; // 这里一定要导入进来 Vue.use(Vuex); const store = new Vuex.Store({ state: { // 文章内容 // article: null, }, // mutations 函数是同步函数,所以不能有异步操作 mutations: { // 这里是请求文章函数 /* getArticle: (state, article) => { state.article = article; }, */ // 这里是请求评论函数 }, // 用来合并模块 modules: { // key: value // key 是合并之后的名称 value 是对应模块的值 article: article, // 需要把上面跟 article 相关的注释掉 }, }); export default store; -
src 文件夹下自建的 store 文件夹下自建的 modules 文件夹下的 article.js
import axios from "../../plugins/axios"; // 文章模块 // 一个 store 模块就是一个对象,这个对象下可以写 state mutations getters actions const article = { // 模块内的 state 需要写成一个函数返回一个对象 state: () => ({ data: null, }), // 尽管分了模块,如果模块没有命名的话,mutation 和 action 都是全局的, // 也就是说在组件中用 commit 和 dispatch 触发对应函数的时候和原来没区别 mutations: { getArticle: (state, article) => { state.data = article; }, }, actions: { getArticle: async ({ commit }) => { // 发请求获取文章数据,然后使用 commit 提交 mutation const res = await axios.get("/articles/1"); commit("getArticle", res); }, }, }; export default article;
-
-
案例 2 这里使用了命名空间
-
Article.vue
<template> <div class="article"> <ArticleComment /> </div> </template> <script> import ArticleComment from "./ArticleComment"; export default { created() { this.$store.dispatch("commentsModule/getComments"); }, components: { ArticleComment }, }; </script> -
ArticleComment.vue
<template> <div class="article-comment"> <h3>评论 <span>评论总数:{{ commentsNum }}</span></h3> <textarea v-model.lazy.trim="text"></textarea> <button @click="submit">提交</button> <ul v-if="comments.length"> <li v-for="comment in comments" :key="comment.id"> <span>{{ comment.text }}</span> <button @click="del(comment.id)">删除</button> </li> </ul> </div> </template> <script> export default { name: "ArticleComment", computed: { comments() { return this.$store.state.commentsModule.comments; }, commentsNum() { // 默认情况下 action,mutation,getter 是全局的 // return this.$store.getters.commentNum; // 加了命名空间之后要这样写 return this.$store.getters["commentsModule/commentNum"]; }, }, data() { return { text: "", }; }, methods: { submit() { const { text } = this; if (text) { this.$store .dispatch({ type: "commentsModule/addComment", // 这里也需要改,因为使用了命名空间 comment: { text, articleId: 1 }, }) .then(() => { this.text = ""; }) .catch(() => { console.log("出错了"); }); } }, del(id) { // this.$store.commit("delComment", id); this.$store.dispatch({ type: "commentsModule/delComment", id }); // 这里也需要改,因为使用了命名空间 }, }, }; </script> -
src 文件夹下自建的 store 文件夹下自建的 modules 文件夹下的 comments.js
import axios from "../../plugins/axios"; // 文章评论模块 const commentsModule = { namespaced: true, // 当模块添加了命名空间,空间的名称是在 store 中合并时候的属性名 // 组件内访问该空间下的所有内容时需要指定空间名 // 例如执行 action 时,this.$store.dispatch('空间名/action函数名') // 例如执行 mutation 时,this.$store.commit('空间名/action函数名') // 例如获取 getter 时,this.$store.getters['空间名/getter名'] state: () => ({ comments: [], }), getters: { // 模块内的 getter 函数默认接收四个参数 // state(局部) getters(局部) // rootState(全局的state store中的数据) rootGetters(全局) commentNum(state) { return state.comments.length; }, }, mutations: { getComments: (state, comments) => { state.comments = comments; }, addComment(state, { comment }) { state.comments.push(comment); }, delComment(state, id) { state.comments = state.comments.filter( (comment) => comment.id !== id ); }, }, actions: { // 一个模块内的 action 函数内的第一个参数中可以获取到 // commit dispatch rootState(store数据) rootGetters getComments: async ({ commit }) => { // 发请求获取评论数据,然后使用 commit 提交 mutation const res = await axios.get("/comments?articleId=1"); commit("getComments", res); }, addComment: ({ commit }, { comment }) => { return new Promise((resolve, reject) => { axios .post("/comments", comment) .then((res) => { commit({ type: "addComment", comment: res }); resolve(); }) .catch((err) => { console.log(err); reject(); }); }); }, delComment: async ({ commit }, { id }) => { await axios.delete(`/comments/${id}`); commit("delComment", id); }, }, }; export default commentsModule; -
index.js
import Vue from "vue"; import Vuex from "vuex"; // import axios from "../plugins/axios"; import article from "./modules/article"; import commentsModule from "./modules/comments"; Vue.use(Vuex); const store = new Vuex.Store({ state: { // article: null, // comments: [], testObject: { name: "毛茸茸", sex: "男", }, }, getters: {}, mutations: {}, actions: {}, // 用来合并模块 modules: { // key: value // key 是合并之后的名称 value 是对应模块的值 article: article, commentsModule, }, }); export default store;
-