Vue-Cli—05.Vuex简单使用 / 自定义插件 / 自定义指令

一、初始化Vuex

Vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
如果一份数据需要在多个组件中使用,组件间传值又比较复杂,就可以使用vuex托管数据。

1、安装

npm install vuex --save

2、导入

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

3、创建状态管理对象 store

state选项:定义状态(状态就是数据)。
mutations选项:定义修改状态的方法(注意:这里面只能定义同步方法)。

export default new Vuex.Store({
  // 定义全局状态(状态就是数据)
  state:{
    car:{
        name:'奔驰',
        price:'40W'
    }
  },
  // 定义修改状态的方法
  mutations:{
    //该方法,修改汽车信息
    updateCar(state,val){
        state.car = val
    }
  }
})

4、注册给Vue

// 导入当前项目中的全局状态管理对象
import store from './store'
new Vue({
  // 在vue实例中使用全局状态管理对象
  store,
  render: h => h(App)
}).$mount('#app')

5、简单使用

$store:返回的是当前项目中的全局状态对象。
commit()方法:用于执行指定的mutations里面的方法。

(1)获取数据

在组件中,直接通过$store.state就可以获取到全局状态对象管理的状态数据,直接渲染到页面。

<div>车辆名称:{{ $store.state.car.name }}</div>
<div>车辆价格:{{ $store.state.car.price }}</div>

(2)修改数据

<div>车辆名称:{{ $store.state.car.name }}</div>
<div>车辆价格:{{ $store.state.car.price }}</div>
<button @click="updateCar">修改汽车信息</button>

methods: {
  updateCar() {
    this.$store.commit("updateCar", { name: "奔驰", price: "60W" });
  }
}

二、核心概念

1、state

state选项:定义状态(状态就是数据)。

  state: {
    name:'张三'
  }

通过$store.state.数据名使用。

<div>姓名:{{ $store.state.name }}</div>

2、getters

getters选项:定义计算属性。方法的参数是状态对象。

  getters:{
    // 方法的第一个参数是全局状态
    nameInfo(state){
      return `我的名字叫${state.name}`
    }
  }

通过$store.getters.属性名使用计算属性。

<div>{{ $store.getters.nameInfo }}</div>

3、mutations

mutations选项:定义修改状态的方法(注意:这里的方法一般都是同步方法)。方法的第一个参数是状态对象,第二个参数是新值。

  mutations:{
    // 修改姓名
    updateName(state,val){
      state.name = val
    }
  },

通过commit()方法,调用mutations里面的方法。

this.$store.commit("updateName", '李四');

4、actions

actions选项:定义操作状态的方法(这里的方法可以定义异步方法)。
注意:actions里的方法最好不要直接操作state状态,而是通过调用mutations里面的方法去修改状态。所以,actions直接操作的是mutations。

  state: {
    carAddress:'意大利'
  },
  mutations:{
    //修改汽车的产地
    updateCarAddress(state,val){
      state.carAddress = val
    }
  },
  actions:{
    //修改汽车的产地
    //方法的第一个参数是全局状态管理对象,第二个参数是具体的值
    updateCarAddress(store,val){
      axios.get(val).then(({data})=>{
        // 方式一:这里可以直接修改状态
        // store.state.carAddress = data.address
        // 方式二:通过调用mutations里面的方法修改状态
        store.commit('updateCarAddress',data.address)
      })
    }
  }

通过dispatch()方法,调用actions里面定义的方法。

this.$store.dispatch('updateCarAddress','data/address.json')

5、modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。

(1)定义模块

namespaced属性:默认情况下,action、mutation 和 getter 是注册在全局命名空间的。通过设置namespaced属性为true,将action、mutation 和 getter全部注册到私有命名空间中。

export default {
    namespaced:true,
    // 状态
    state:{
        planeName:'空客404',
        planePrice:'10Y',
        planeAddress:'中国'
    },
    // 计算属性
    getters:{
        planeInfo(state){
            return `飞机名称:${state.planeName},飞机价格:${state.planePrice},飞机产地:${state.planeAddress}`
        }
    },
    // 同步方法
    mutations:{
        updatePlaneName(state,val){
            state.planeName = val
        },
        updatePlanePrice(state,val){
            state.planePrice = val
        }
    },
    // 异步方法
    actions:{
        updatePlanePrice(store,val){
            setTimeout(() => {
                store.commit('updatePlanePrice',val)
            }, 500);
        }
    }
}

(2)在全局状态管理对象中导入模块

// 导入飞机模块
import plane from './modules/plane.js'

// 创建一个全局状态管理对象并导出
export default new Vuex.Store({
  // 模块
  modules:{
    // 飞机模块
    plane
  }
})

(3)使用模块

① 获取模块中的state状态

要从私有模块中获取数据,方式是:$store.state.模块名称.模块的数据

<div>飞机名称:{{ planeName }}</div>

planeName() {
  return this.$store.state.plane.planeName;
}

② 获取模块中的getters计算属性

要从私有模块中获取计算属性,方式是:$store.getters['模块名/计算属性']

<div>{{planeInfo}}</div>

planeInfo(){
    return this.$store.getters['plane/planeInfo']
}

③ 调用模块中的mutations定义的方法

调用私有模块里面的mutations定义的方法,方式是:$store.commit('模块名/方法名',新值)

<button @click="updatePlaneName">修改飞机名称</button>
updatePlaneName(){
  this.$store.commit('plane/updatePlaneName','波音747')
}

④ 调用模块中的actions定义的方法

调用私有模块里面的actions定义的方法,方式是:$store.dispatch('模块名/方法名',新值)

<button @click="updatePlanePrice">修改飞机价格</button>
updatePlanePrice(){
  this.$store.dispatch('plane/updatePlanePrice','20Y')
}

三、Vuex使用

1、计算属性中转

直接在模板中使用全局状态管理数据,表达式会写的很长。所以可以使用计算属性。

  // getters选项定义计算属性
  getters:{
    carInfo(state){
      return `汽车名称:${state.carName},汽车价格:${state.carPrice},汽车产地:${state.carAddress}`
    }
  }

 <!-- 直接在模板中使用全局状态里面的计算属性 -->
 <div>{{$store.getters.carInfo}}</div>
 <div>{{carInfo}}</div>

  //计算属性
  computed:{
    // 汽车信息
    carInfo(){
      // 返回全局状态管理里面的计算属性
      return this.$store.getters.carInfo
    }
  }

2、映射函数

通过映射函数mapState、mapGetters、mapActions、mapMutations,可以将vuex.store中的属性映射到vue实例身上,这样在vue实例中就能访问vuex.store中的属性了,便于操作vuex.store。

(1)导入映射函数

// 从vuex中,导入映射函数
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";

(2)使用映射函数生成计算属性

如果vuex里面state的数据名称 跟 页面中的计算属性名称相同,就可以使用mapState映射函数,自动生成页面中的计算属性。
如果vuex里面getters的数据名称 跟 页面中的计算属性名称相同,就可以使用mapGetters映射函数,自动生成页面中的计算属性。
注意:如果要映射模块里面的state/getters,函数的第一个参数设置为模块的名称。

<div>汽车名称:{{ carName }}</div>
<div>汽车价格:{{ carPrice }}</div>
<div>汽车产地:{{ carAddress }}</div>
<div>{{ carInfo }}</div>
<hr/>
<div>飞机名称:{{ planeName }}</div>
<div>飞机价格:{{ planePrice }}</div>
<div>飞机产地:{{ planeAddress }}</div>
<div>{{ planeInfo }}</div>

computed: {
  // mapState映射state
  ...mapState(["carName", "carPrice", "carAddress"]),
  // mapGetters映射getters
  ...mapGetters(["carInfo"]),

  // 映射私有模块里面的数据
  ...mapState('plane',['planeName','planePrice','planeAddress']),
  ...mapGetters('plane',['planeInfo'])
}

(3)使用映射函数生成方法

如果定义的方法名跟全局管理对象中mutations里面的方法名相同,并且定义的方法会带有一个参数,通过参数传递数据。满足该规则,就可以使用mapMutations映射函数生成方法。
如果定义的方法名跟全局管理对象中actions里面的方法名相同,并且定义的方法会带有一个参数,通过参数传递数据。满足该规则,就可以使用mapActions映射函数生成方法。
注意:如果要映射私有模块中mutations/actions里面的方法,函数的第一个参数设置为模块的名称。

<button @click="updateCarName('宾利')">修改汽车名称</button>
<button @click="updateCarPrice('300W')">修改汽车价格</button>
<button @click="updateCarAddress('data/address.json')">修改汽车产地</button>
<hr/>
<button @click="updatePlaneName('波音747')">修改飞机名称</button>
<button @click="updatePlanePrice('20Y')">修改飞机价格</button>

methods: {
  // 映射全局管理对象中mutations里面的方法
  ...mapMutations(["updateCarName", "updateCarPrice"]),
  // 映射全局管理对象中actions里面的方法
  ...mapActions(["updateCarAddress"]),

  // 映射私有模块里面的方法
  ...mapMutations('plane',['updatePlaneName']),
  ...mapActions('plane',['updatePlanePrice'])
}

3、购物车模块

import axios from "axios";
export default {
  namespaced: true,
  state: {
    //商品数组
    goodses: [],
  },
  getters: {
    //总价
    totalPrice(state) {
      return state.goodses.filter((r) => r.ck).map((r) => r.price * r.count).reduce((a, b) => a + b, 0);
    },
    // 是否全选
    isCkAll(state) {
      return state.goodses.length>0 & state.goodses.every((r) => r.ck);
    },
  },
  mutations: {
    // 加载数据的同步方法
    loadGoodses(state, val) {
      state.goodses = val;
    },
    // 设置所有商品的状态
    ckAll(state, val) {
      state.goodses.forEach((r) => {
        r.ck = val;
      });
    },
    // 根据id删除商品
    delGoodsById(state, id) {
      let index = state.goodses.findIndex((r) => r.id == id);
      state.goodses.splice(index, 1);
    },
  },
  actions: {
    // 加载数据的异步方法
    loadGoodses(store, val) {
      axios.get(val).then(({ data }) => {
        store.commit("loadGoodses", data);
      });
    },
    // 设置所有商品的状态
    ckAll(store, val) {
      store.commit("ckAll", val);
    },
    // 根据id删除商品
    delGoodsById(store, id) {
        store.commit("delGoodsById", id);
    },
  },
};

<template>
  <div class="shopcart">
    <h4>购物车</h4>
    <table>
      <thead>
        <tr>
          <th>
            <input type="checkbox" v-model="isCkAll" />
          </th>
          <th>名称</th>
          <th>图片</th>
          <th>单价</th>
          <th>数量</th>
          <th>小计</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in goodses" :key="item.id">
          <td>
            <input type="checkbox" v-model="item.ck" />
          </td>
          <td>{{ item.name }}</td>
          <td>
            <img :src="item.img" />
          </td>
          <td>{{ item.price }}</td>
          <td>
            <button @click="item.count--" :disabled="item.count === 1">
              -
            </button>
            <input type="text" v-model="item.count" />
            <button @click="item.count++" :disabled="item.count === 10">
              +
            </button>
          </td>
          <td>{{ item.price * item.count }}</td>
          <td>
            <button @click="delGoodsById(item.id)">删除</button>
          </td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <td colspan="7">
            <span>总价:</span>
            <span>{{ totalPrice }}</span>
          </td>
        </tr>
      </tfoot>
    </table>
  </div>
</template>
<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
  name: "Shopcart",
  computed: {
    ...mapState("shopcart", ["goodses"]),
    ...mapGetters("shopcart", ["totalPrice"]),
    // 定义可以修改的计算属性
    isCkAll: {
      set(val) {
        this.ckAll(val);
      },
      get() {
        return this.$store.getters["shopcart/isCkAll"];
      },
    },
  },
  methods: {
    // 映射actions对应的方法
    ...mapActions("shopcart", ["ckAll", "delGoodsById"]),
  },
  // 页面挂载完成
  mounted() {
    this.$store.dispatch("shopcart/loadGoodses", "data/shopcart.json");
  },
};
</script>

四、key

v-for绑定key值,为什么不建议使用index作为key值?
如果只是展示列表数据,key值可以是索引;如果列表中的数据会经常发生变化,特别是列表数据的位置会发生变化,这时候key一定要设置为对象身上的唯一属性,比如:学号、工号、身份证号、手机号等等。目的是:当列表更新时,会大大提高列表重新渲染的性能损耗。
因为vue在渲染数据时,先将数据生成一份虚拟DOM,再将虚拟DOM生成对应的真实DOM挂载到页面中。当vue中的数据修改后,会重新生成一份虚拟DOM,并跟之前的虚拟DOM进行匹配,如果两份虚拟DOM中的key和key对应的值完全相同,不会重新生成对应的真实DOM;只有key和key对应的值不同的虚拟DOM,才会生成新的真实DOM并挂载的页面中。

<div class="home">
  <button @click="add">添加</button>
  <ul>
    <!-- 如果key是索引,当位置发生变化时,所有数据都会重新渲染 -->
    <!-- <li v-for="(item, index) in list" :key="index">{{ item }}</li> -->
    <!-- 如果id是索引,当位置发生变化时,只会渲染更新的数据 -->
    <li v-for="item in list" :key="item.id">{{ item }}</li>
  </ul>
</div>

data() {
  return {
    list: [
      {
        id: 1001,
        name: "张学友",
        age: 21,
        sex: "男",
      },
      {
        id: 1002,
        name: "郭富城",
        age: 22,
        sex: "男",
      },
      {
        id: 1003,
        name: "黎明",
        age: 24,
        sex: "男",
      },
      {
        id: 1004,
        name: "周润发",
        age: 26,
        sex: "男",
      },
    ],
  };
},
methods: {
  // 添加员工的方法
  add() {
    let em = {
      id: Date.now(),
      name: "蔡依林",
      age: 22,
      sex: "女",
    };
    this.list.unshift(em);
  },
}

五、$nextTick()

$nextTick():需要传一个回调函数。将回调函数里面的代码延迟到DOM渲染完毕后执行。在修改数据之后立即使用它,然后等待 DOM 更新。

<input type="text" v-model="carName" />
<button @click="addCar">添加汽车</button>
<ul ref="list">
  <li v-for="item in list" :key="item.id">
    <input type="text" :value="item.name" />
  </li>
</ul>

data() {
  return {
    // 汽车名称
    carName: "",
    // 汽车数组
    list: [
      {
        id: 1001,
        name: "宝马",
      },
      {
        id: 1002,
        name: "玛莎拉蒂",
      },
    ]
  };
},
methods: {
  addCar() {
    let car = {
      id: Date.now(),
      name: this.carName,
    };
    this.list.push(car);
    this.carName = "";
    // $nextTick方法,需要传一个回调函数。回调函数里面的代码在DOM更新完成后执行。
    this.$nextTick(() => {
      //  让最后一个li元素里面的input元素获取焦点
      //  focus()方法用于为元素设置焦点。
      this.$refs.list.lastChild.lastChild.focus();
    });
  }
}

六、$forceUpdate()

$forceUpdate():进行强制更新。调用这个方法会更新视图和数据,触发updated生命周期。

<button @click="person.name = '张三'">修改姓名</button>
<button @click="addSex">添加性别</button>
<div>{{ person }}</div>

data() {
  return {
    person: {
      name: "张学友",
      age: 20,
    }
  };
},
methods: {
  addSex() {
    // 添加响应式属性
    // this.$set(this.person,"sex","男")

    //   直接添加的属性,不具备响应式
    this.person.sex = "男";
    //  通过$forceUpdate()方法,迫使vue实例重新渲染
    this.$forceUpdate();
  }
}

数据更新之前
数据更新之后

七、自定义指令

自定义指令就是一个方法,方法的第一个参数传递的是指令所在的DOM元素,方法的第二个参数是给指令绑定的数据。

1、局部指令

(1)定义指令

data() {
    return {
        car:'<h2>保时捷卡宴真好看</h2>'
    }
},
// 定义局部指令,所有的指令背后都是在操作DOM,我们将这种功能称之为:造轮子。
directives:{
    // 注册一个局部自定义指令 'v-red',设置字体颜色为红色
    red:function(el){
        el.style.color="red"
    },
    // 注册一个局部自定义指令 'v-myhtml',渲染html标签数据
    myhtml(el,bind){
        el.innerHTML = bind.value
    }
}

(2)使用指令

<div v-red>好好学习</div>
<div v-myhtml="car"></div>

2、全局指令

(1)定义指令

// 定义全局自定义指令
import Vue from 'vue'
Vue.directive('mycolor',function(el,bind){
    el.style.color = bind.value
})

(2)main.js文件中导入指令

// 导入全局自定义指令
import './directives'

(3)使用指令

<divv-color="'red'">好好学习Vue</div>

八、自定义插件

1、定义插件

定义一个插件,插件就是将给Vue添加的全局成员,归类到一起去,这样做利于后期维护。
插件本质上就是一个对象,该对象中必须包含一个install方法,方法的第一个参数是Vue,第二个参数是配置对象。install()方法,会在use的时候执行。Vue.use(插件),这里的Vue会作为install方法的第一个参数。

// 插件本质上就是一个对象
export default {
    // 该对象中必须包含一个install()方法
    install:function(Vue,options){
        // 可以直接给Vue添加成员
        Vue.sayHi = function(){
            console.log('大家好!我是Vue');
        },
        Vue.msg = "欢迎使用插件",
        // 可以在Vue的原型上扩展成员
        Vue.prototype.sayHello = function(){
            console.log('哈哈!我是Vue原型上的方法');
        },
        // 给Vue混入成员
        Vue.mixin({
            data() {
                return {
                    plane:{
                        name:'奔驰',
                        price:'100W'
                    }
                }
            },
            methods: {
                showPlane(){
                    console.log(this.plane.name,this.plane.price);
                }
            },
        }),
        // 注册全局组件
        Vue.component('b-box', {
            // 在脚手架环境中,只能通过渲染函数定义全局组件
            render(h) {
                return h('div',this.$slots.default)
            },
        }),
        // 注册全局指令
        Vue.directive('bgcolor', function(el,bind){
            el.style.backgroundColor = bind.value
        })
    }
}

2、导入插件

// 导入自定义插件
import myPlugin from './plugins'
// 注意:一定要use
Vue.use(myPlugin)

3、使用插件

<!-- 调用插件中定义的vue原型上的方法 -->
<button @click="sayhello">sayHello</button>
<!-- 调用插件中定义的vue方法 -->
<button @click="sayHi">sayHello</button>
<!-- 调用插件中定义的混入成员 -->
<button @click="showPlane">showPlane</button>
<div v-bgcolor="'lightblue'">我是淡蓝色</div>
<b-box>哈哈</b-box>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容