vuex解析

第一章Vuex入门

1.1Vuex介绍

什么是vuex?
它是vue团队提供的状态管理模式,简单来说就是针对vue.js提供的数据驱动解决方案。
vue是双向绑定,vuex是单向绑定的,一般来说单向绑定更安全
vuex为什么非常重要?
它可以使vue开发大型复杂的产品变得容易。
安装vuex

npm install vuex --save
yarn add vuex
cnpm install -save vuex

配置vuex

1 创建一个store文件夹,然后在里面创建一个index.js 内容如下
import Vue from 'vue'
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
    state:{}
})

2 找到vue工程的main.js文件 将store导入
import store from './store';

// Vue实例化这里把 store 加入进去
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');

创建全新工程:

安装好vue cli
npm install -g @vue/cli-service-global

创建包含vuex的新工程

打开终端软件(如果是window电脑,请使用 git-bash 软件)
在终端里进入你的常用编程目录,比如 cd /d/work
继续执行命令 vue create hello-world,创建一个hello-world 工程
按回车键后,进入选择页面(可以用键盘的上下键进行移动,按空格键选中)
选择Manually select features,并按回车键
选择:Babel、Router、Vuex、Linter/Formatter,并按回车键
一直按回车键
等待下载完成后,就成功了

vue.config.js配置

在工程目录下创建vue.config.js文件,然后输入配置
module.exports = {
  devServer: {
    disableHostCheck: true,
    host: "0.0.0.0"
  }
};

第二章vuex的核心技术

2.1 state/muation/action

vuex比较复杂,要先在使用框架的过程中加深对框架的理解
定义vue store:我们接下来说的store 都是指Vuex的store文件

state:用来存储数据,表示数据在vuex中的存储状态,可以看成一个全局(到处都可以访问到)的大json对象
编写state
例子:编写一个count变量来纪录数据

export default new Vuex.Store({
  state: {
    count: 0
  }
});

muation:作用就是修改state状态
muation是一个函数,可以接收两个参数 :state 当前上下文的state实例,可以通过这个参数修改state的值
payload 接受外部传入的对象,需要和调用方约定好数据类型,也可以不传参数
例子

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;               //用于count的自增
    },
    decrement(state) {
      state.count--;              //用于count的自减
    }
  }
});

Action :如果想着vue中修改state的值,只能通过action触发muation函数来进行修改

export default new Vuex.Store({
  state: {
    count: 0
  },
  actions: {
    increment: ({ commit }) => commit('increment'),
    decrement: ({ commit }) => commit('decrement')   //也可以定义多个参数 
                                                       increment: ({ state,commit,dispatch }) => commit("increment")
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    }
  }
});

action是一个函数 它有两个参数
第一个参数context对象 包含了以下属性和方法

{
  state, // 等同于 `store.state`,若在模块中则为局部状态
    rootState, // 等同于 `store.state`,只存在于模块中,
    commit, // 等同于 `store.commit`
    dispatch, // 等同于 `store.dispatch`
    getters, // 等同于 `store.getters`
    rootGetters; // 等同于 `store.getters`,只存在于模块中
}

第二个参数是用来接收外部传递的参数

commit:它是个函数,作用就是把数据提交到muation中,也有2个参数
第一个参数是muation函数的名称
第二个是自定义的对象

actions: {
  increment: ({ commit }) => commit('increment');    //可以把对象传递给muation
}

运用vuex store
只要我们定义好state和action我们就可以在任意vue文件中得到数据和执行数据变化

获取state
我们可以通过$store.state.count来获取state中的count值 state只支持单向绑定 muation更新了state的状态之后,vue的值也会更新
例子

<template>
  <div>
    Clicked: {{ $store.state.count }} times
  </div>
</template>

mapState这个函数可以动态的将state和compute绑定

counter.vue

<template>
  <div>
    Clicked: {{ count }} times
  </div>
</template>

<script>
  // 导入 mapState 函数
  import { mapState } from 'vuex';      

  export default {
    computed: {
      ...mapState({
        // 箭头函数可使代码更简练
        count: state => state.count
      })
    }
  };            //根据count的值变化而刷新页面而不是只根据state的状态

执行store的action

<template>
  <div>
    Clicked: {{ count }} times
    <div>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  </div>
</template>

<script>
  // 导入 mapState mapActions 函数
  import { mapState, mapActions } from 'vuex';

  export default {
    computed: {
      ...mapState({
        count: state => state.count
      })
    },
    methods: { ...mapActions(['increment', 'decrement']) }  //把store的action绑定到了vue的methods对象中
  };
</script>

###2.2getter
vuex的gtter函数类似于computed函数的作用,它会将返回值根据依赖缓存起来,只有当依赖发生改变时,值才会被重新计算
定义getter

getters: {
    evenOrOdd: state => (state.count % 2 === 0 ? "偶数" : "奇数")   //state作为第一个参数 位置 
                                                                    放在store中和state mutation action并列
  }

使用getter,在组件中使用

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'evenOrOdd'
    ])
  }
}

###2.3 vuex request
我们知道action是用来接受数据并处理逻辑的地方。当vuex需要从远处获取数据时即action接受外部参数然后执行网络请求需要添加async关键字

api

https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/users

这个api返回的数据
{
  "data": {
    "users": [
      {
        "name": "Richard Harris",
        "email": "k.yyziq@sdewmv.net"
      }
    ]
  }
}

书写store
state

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    users: []
  }
});

mutation

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    users: []
  },
  mutations: {
    load(state, payload) {           //定义一个函数load来更新user
      state.users = payload;
    }
  }
});

action :因为网络请求是一个异步行为,所以我们需要使用await/async。action函数声明上添加async关键字,执行request请求也需要添加await关键字
做网络请求时一般使用axios框架

import axios from "axios";

const response = await axios.get(                //await关键字
        "https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/users"
      );
console.log(response.data);      //可以获取服务器的内容

api的返回数据

{
  "data": {
    "users": [
      {
        "name": "Richard Harris",
        "email": "k.yyziq@sdewmv.net"
      }
    ]
  }
}

console.log(response.data.data.users);  //获取user api的返回值

在action中运用axios 例子

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    users: []
  },
  actions: {
    load: async ({ commit }) => {
      const response = await axios.get(
        "https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/users"
      );
      commit("load", response.data.data.users);
    }
  },
  mutations: {
    load(state, payload) {
      state.users = payload;
    }
  }
});

运用:在user.vue中添加store的依赖和配置

<template>
  <div>
    <ul v-for="user in users" :key="user.name">
      <li>
        <p>
          <label>用户名</label>:
          <span>{{user.name}}</span>
        </p>
        <p>
          <label>邮箱</label>:
          <span>{{user.email}}</span>
        </p>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapActions } from "vuex";

export default {
  async mounted() {
    await this.load();
  },
  computed: {
    ...mapState({
      users: state => state.users
    })
  },
  methods: { ...mapActions(["load"]) }
};
</script>

注意点 :vuex进行远程数据操作时的await/async关键字设置
养成按顺序编写store的习惯

2.4 vuex表单

通过功能来熟悉通过传递参数去请求数据
api url

https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/login

api methods:post
api 请求参数

{
 "username": "string",
 "password": "string"
}

返回模型

{
  "success": boolean,
  "data": {
    "username": "string",
    "email": "string"
  }
}

登录成功
测试参数

{
 "username": "admin",
 "password": "123456"
}

提交参数后返回的结果

{
  "success": true,
  "data": {
    "username": "admin",
    "email": "r.owukoelkl@scrdunktvw.kn"
  }
}

登录失败
如果传递的参数是有问题的,返回的结果是

{
  "success": true,
  "data": {
    "username": "admin",
    "email": "r.owukoelkl@scrdunktvw.kn"
  }
}

注意点:一定要正确识别api的约定和定义

store的定义:按照s(state)m(mutation)a(action)的顺序来
state:将user作为state的属性来定义

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: {}
  }
});

mutation:定义一个函数来更新state

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: {}
  },
  mutations: {
    setUser(state, payload) {
      state.user = payload;
    }
  }
});

action:由于api的post参数,所以我们使用axios.post

const response = await axios.post('api...', {
    username: 'admin',
    password: '123456'
});

完整的store代码

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    user: {}
  },
  mutations: {
    setUser(state, payload) {
      state.user = payload;
    }
  },
  actions: {
    login: async ({ commit }, param) => {
      // 执行远程请求
      const response = await axios.post(
        "https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/login",
        {
          username: param.username,
          password: param.password
        }
      );
      if (response.data.success) {
        commit("setUser", response.data.data);
      }
    }
  }
});

在组件中使用

<template>
  <div>
    <div>
      <label>用户名</label>
      <input type="text" v-model="username" />
    </div>
    <div>
      <label>密码</label>
      <input type="password" v-model="password" />
    </div>
    <button @click="onLogin">登录</button>
    <div v-if="loginError">登录失败</div>
  </div>
</template>

<script>
  import { mapState, mapActions } from "vuex";

  export default {
    data() {
      return {
        username: "",
        password: "",
        loginError: false
      };
    },
    computed: {
      ...mapState({
        user: state => state.user
      })
    },
    methods: {
      ...mapActions(["login"]),
      async onLogin() {
        // 执行 store 的登录 action
        await this.login({
          username: this.username,
          password: this.password
        });

        if (this.user && this.user.username) {
          // 登录成功,随便跳转一下
          window.location.href = "https://m.baidu.com";
        } else {
          // 登录失败
          this.loginError = true;
        }
      }
    }
  };
</script>

第三章

3.1 配置模块

对于小应用我们可以把代码写在一个index.js文件中,但是企业级项目是非常复杂的,所以我们会使用模块来维护store
配置vuex模块:就是把一个store分割为多个js文件,每个文件就是一个模块,每个模块都有自己的smag,甚至模块还可以嵌套子模块

多个模块在一个文件里

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

如果我们想获取一个模块,我们可以
store.state.a; // -> moduleA 的状态
store.state.b; // -> moduleB 的状态

分拆模块文件
count.js

export default {
  namespaced: true,  //开启命名空间,使同名action也没有影响
  state: {
    count: 0
  },
  actions: {
    increment: ({ commit }) => commit("increment"),
    decrement: ({ commit }) => commit("decrement")
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    }
  }
};

集成module

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

import count from "./count";

export default new Vuex.Store({
  modules: {
    count:count                         //module对象里面添加的属性名称就是module的名称
  }
});

vue中使用module store
vuex提供一个createNamespacedHelpers函数来创建指定namespace下的module引用。
createNamespacedHelpers这个函数接受的参数名称就是module的名称。

import { createNamespacedHelpers } from "vuex";
// 创建 count 模块的引用
const { mapState, mapActions } = createNamespacedHelpers("count");

export default {
  computed: {
    ...mapState({
      count: state => state.count
    })
  },
  methods: { ...mapActions(["increment", "decrement"]) }
};

也可以使用多个模块

const { mapState, mapActions } = createNamespacedHelpers("count");                             //引用count

const { mapState:demoMapState, mapActions:demoMapActions} = createNamespacedHelpers("demo");  //引用demo


export default {
  computed: {
    ...mapState({
      count: state => state.count
    }),
     ...demoMapState({
      demoCount: state => state.count
    })
  },
  methods: { ...mapActions(["increment", "decrement"]) }
};

3.2 多模块(一)

我们用一个将上面添加到购物车的例子来方便理解
api

1 products
request

GET https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/products 
Accept: application/json, */*
Content-Type: application/json

response
[{
    id:"int",// 主键
    title:"string",// 商品名称
    price:"number" // 商品价格
}]

2  cart 
request 

POST https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/buyproducts 
Accept: application/json, */*
Content-Type: application/json
Body:
{
  "products":[
    {
        id:""
    }
  ]
}

response
{
  "success": false
}

API开发
当项目复杂时,我们可以把api封装在api文件中,可以在src目录下面创建一个api文件夹,然后在文件夹下面创建不同的文件
如 例子中 我们就创建product.js、cart.js 这两个文件来存放商品和购物车数据
product.js

import axios from "axios";

export default {
  async getProducts() {
    const response = await axios.get(
      "https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/products"
    );
    return response.data;
  }
};

cart.js

import axios from "axios";

export default {
  async buyProducts(products) {
    const response = await axios.post(
      "https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/buyproducts",
      {
        products
      }
    );
    return response.data;
  }
};

3.3 多模块(二)

一般我们都把module store都存放在src/store/modules这个目录下
product state 定义一个all数组存放数据

export default {
  namespaced: true,
  state: {
    all: []
  }
};

product mutation 定义一个setproducts方法使加载所有商品是触发修改状态

export default {
 namespaced: true,
 state: {
   all: []
 },

 mutations: {
   setProducts(state, products) {
     //更新所有的商品
     state.all = products;
   }
 }
};

product actions 需要一个getAllProducts方法来加载所有的商品数据

// 加载 api
import productApi from "../../api/product";

export default {
  namespaced: true,
  state: {
    all: []
  },
  mutations: {
    setProducts(state, products) {
      state.all = products;
    }
  },
  actions: {
    async getAllProducts({ commit }) {
      // 加载所有商品记录
      const products = await productApi.getProducts();
      // 触发数据状态变化
      commit("setProducts", products);
    }
  }
};

store集成

import Vue from "vue";
import Vuex from "vuex";
import product from "./modules/product";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    product
  }
});

3.4 多模块(三)

cart state
可以定义一个items数组 每个item对象可以是
还需要一个checkoutStatus属性来存储购买状态

export default {
  namespaced: true,
  state: {
    items: [],
    checkoutStatus: null
  }
};

cart mutation 可以分解出多个数据状态更新的mutation
添加到购物车

export default {
  mutations: {
    // 接收商品id,完成添加商品到购物车
    pushProductToCart(state, { id }) {
      // 添加一条商品购买记录
      state.items.push({
        id,
        quantity: 1
      });
    }
  }
};

添加购物车商品数量

export default {
  mutations: {
    // 接收商品id,完成购物车商品数量+1
    incrementItemQuantity(state, { id }) {
      // 根据 id,找出对应的 item 记录
      const cartItem = state.items.find(item => item.id === id);
      // 购买数量+1
      cartItem.quantity++;
    }
  }
};

更新全部购物车商品:可以用于清空购物车或者从其他地方获取购物车

export default {
  mutations: {
    // 全量更新购物车商品
    setCartItems(state, { items }) {
      state.items = items;
    }
  }
};

更新购物车状态

export default {
  mutations: {
    setCheckoutStatus(state, status) {
      state.checkoutStatus = status;
    }
  }
};

cart actions
如何在当前模块调用其他模块?可以通过指定命名空间来调用
例如 调用product store的mutation方法

commit(
  "products/decrementProductInventory",  //命名空间/mutation函数的名称
  { id: product.id },                    //decrementProductInventory函数的第二个参数
  { root: true }                         //设置在全局变量空间内分发
);

调用product模块的getAllProducts这个action

export default {
  actions: {
    async demo({ state, commit,dispatch }) {
      // 触发 product module的 getAllProducts 这个 action
      dispatch('product/getAllProducts', null, { root: true })
    }
  }
}

添加到购物车

export default {
  actions: {
    addProductToCart({ state, commit }, product) {
      // 重制购物车状态
      commit("setCheckoutStatus", null);

      if (product.inventory <= 0) {
        // 如果商品库存没有了则不用继续执行了,直接返回
        return;
      }
      // 查找购物车商品
      const cartItem = state.items.find(item => item.id === product.id);
      // 判断购物车里没有商品,则执行添加商品,否则执行添加数量
      if (!cartItem) {
        commit("pushProductToCart", { id: product.id });
      } else {
        commit("incrementItemQuantity", cartItem);
      }
      // 执行商品的库存减法,确保商品不会超卖
      commit(
        "products/decrementProductInventory",
        { id: product.id },
        { root: true }
      );
    }
  }
};

store 集成

import Vue from "vue";
import Vuex from "vuex";
import cart from "./modules/cart";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    cart
  }
});

3.5 多模块(四)

商品组件

<template>
  <ul>
    <li v-for="product in products" :key="product.id">
      {{ product.title }} - {{ product.price }}
      <br />
      <button :disabled="!product.inventory" @click="addProductToCart(product)">
        Add to cart
      </button>
    </li>
  </ul>
</template>

<script>
  import { createNamespacedHelpers } from "vuex";

  const {
    mapState: productMapState,
    mapActions: productMapActions
  } = createNamespacedHelpers("product");
  const { mapActions: cartMapActions } = createNamespacedHelpers("cart");

  export default {
    computed: {
      ...productMapState({
        products: state => state.all
      })
    },
    methods: {
      ...cartMapActions(["addProductToCart"]),
      ...productMapActions(["getAllProducts"])
    },
    created() {
      this.getAllProducts();
    }
  };
</script>

购物车组件

<template>
  <div class="cart">
    <h2>Your Cart</h2>
    <p v-show="!cartProducts.length">
      <i>Please add some products to cart.</i>
    </p>
    <ul>
      <li v-for="product in cartProducts" :key="product.id">
        {{ product.title }} - {{ product.price }} x {{ product.quantity }}
      </li>
    </ul>
    <p>
      <button :disabled="!cartProducts.length" @click="checkout(cartProducts)">
        Checkout
      </button>
    </p>
    <p v-show="checkoutStatus">Checkout {{ checkoutStatus }}.</p>
  </div>
</template>

<script>
  import { createNamespacedHelpers } from "vuex";

  const { mapState, mapActions } = createNamespacedHelpers("cart");
  const { mapState: productMapState } = createNamespacedHelpers("product");

  export default {
    computed: {
      ...mapState({
        items: state => state.items,
        checkoutStatus: state => state.checkoutStatus
      }),
      ...productMapState({
        allProducts: state => state.all
      }),
      cartProducts() {
        // 遍历 items,并修改数据
        return this.items.map(({ id, quantity }) => {
          const product = this.allProducts.find(product => product.id === id);
          // 通过 return 修改原始数据,把product 的名称和价格赋值给对象
          return {
            id,
            title: product.title,
            price: product.price,
            quantity
          };
        });
      }
    },
    methods: {
      ...mapActions(["checkout"])
    }
  };
</script>

store集成

<template>
  <div id="app">
    <h1>Shopping Cart Example</h1>
    <hr>
    <h2>Products</h2>
    <ProductList/>
    <hr>
    <ShoppingCart/>
  </div>
</template>

<script>
import ProductList from './components/ProductList.vue'
import ShoppingCart from './components/ShoppingCart.vue'
export default {
  components: { ProductList,ShoppingCart }
}
</script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值