vue基础知识点

256 阅读21分钟

vue的一些概念

  • Vue:一套用于构建用户界面的渐进式javascript框架
  • 渐进式:逐渐增强,通过学习,可以集成更多的功能
  • 库:一些方法的集合
  • 框架:一套完整的项目解决方案
  • MVVM:一种软件架构模式,通过数据双向绑定让数据自动地双向同步,不再需要操作DOM

组件化和模块化的区别

模块化:一个独立的js文件就是一个模块

组件化:一个组件会包含(HTML+CSS+JS) 把一个完整的页面拆分成多个组件构成,可以完成整个结构样式行为的拆分复用

组件化的优点:1.容易维护;2.便于复用

vue/cli脚手架

vue官方提供的一个全局命令工具,可以帮我们快速创建一个vue项目的基础架子(减少我们花在配置webpack上的精力)

优点:

  1. 开箱即用;

  2. 零配置(不用配置webpack);

    vue主动隐藏了webpack.config.js,可以创建一个vue.config.js文件,覆盖webpack配置文件

  3. 内置babel(语法降级)等工具

命令行安装(只装一次):yarn global add @vue/cli;验证vue --version

  • 创建项目

    vue create 项目名(不能用中文)

  • 启动项目

    yarn serve

  • 覆盖webpack的配置

    // 配置端口和自动打开浏览器
    // module.exports模块导出,详见nodejs-d3
    module.exports = {
      devServer: {
        open: true,
        port: 3000
      }
    }
    

目录分析与清理

  • public/index.html不用动,提供一个最基础的页面
  • src/main.js不用动, 渲染了App.vue组件
  • src/App.vue默认有很多的内容,可以全部删除
  • assets文件夹与components直接删除

单文件组件

一个.vue文件就是一个组件,后续开发vue,所有的功能都是基于组件实现。

一个单文件组件由三部分构成:

  • template(必须) 影响组件渲染的结构 html
    • 只能有一个根元素
  • script 逻辑 js
  • style 样式 css less scss
    • style用于提供组件的样式,默认只能用css
    • 可以通过lang="less"开启less的功能,需要安装依赖包
yarn add less-loader@7.2.1 less -D
// less-loader处理less文件
// less识别less语法

vue通过data提供数据

<script>
// 默认导出组件的配置项
export default {
  data () {
    return {
        // data提供的数据=>组件实例
      money: 100,
      msg: 'hello'
    }
  }    
}
</script>

插值表达式

注意点:

  1. 数据在 data 中要存在
  2. 不能在标签属性中使用
  3. 能使用表达式, 支持点语法和三元运算符,但是不能使用 if for

v-bind

动态设置元素的属性

// 简写
:属性名="属性值"
// 完整语法 v-bind:属性名="属性值"

不写:的话,“属性值”就被当作(默认的)字符串了,没有了动态的效果

v-on

注册事件

// 三种语法形式
v-on:事件名=“要执行的少量代码"		// 可直接组件实例
v-on:事件名=“methods中的函数名"		// 想使用组件实例,必须带上`this.` 前缀
v-on:事件名=“methods中的函数名(实参)" 	// 想使用组件实例,必须带上`this.` 前缀
// 简写:
`@事件名`代替`v-on:事件名`

获取事件对象

  • 无实参, 直接使用形参e

  • 有实参, 实参使用$event,形参使用e

<a @click="fn" href="http://www.baidu.com">去百度</a>
<a @click="fn2(100, $event)" 

methods: {
    fn(e) {
      e.preventDefault()
    },
    fn2(num, e) {
      e.preventDefault()
    }
  }

事件修饰符

.prevent 阻止默认行为

.stop 阻止冒泡

<div id="app">
  <a @click.prevent="fn" href="http://www.baidu.com">去百度</a>
</div>

按键修饰符

监听特殊按键的键盘事件

  • @keyup.enter 回车

  • @keyup.esc 返回

v-if 和 v-show

控制盒子的显示隐藏

v-show="布尔值"    (true显示, false隐藏)
v-if="布尔值"   (true显示, false隐藏)

区别:

v-show 实质是在控制元素的 css 样式为 display: none;

v-if 实质是在动态的创建 或者 删除元素节点

用法:

频繁的切换显示隐藏, 用 v-show

不用频繁切换, 要么显示, 要么隐藏的情况, 适合于用 v-if

v-else 和 v-else-if

按条件控制同一位置的不同元素显示/隐藏

v-if/v-else-if/v-else必须紧贴在一起

<div id="app">
  <h1 v-if="age >= 60">60岁以上, 广场舞</h1>
  <h1 v-else-if="age >= 30">30岁以上, 搓麻将</h1>
  <h1 v-else-if="age >= 20">20岁以上, 蹦迪</h1>
  <h1 v-else>20岁以下, 唱跳rap篮球</h1>
</div>

v-model(★★★)

给表单元素使用, 双向数据绑定

<template>
  <div>
    <!-- prevent修饰符阻止默认跳转 -->
    <form @submit.prevent="submit">
      
      <!-- number修饰符一般配合number框一起使用 -->
      账号:<input type="number" v-model.number="form.account" /><br />

      密码:<input type="password" v-model="form.password" /><br />

      <!-- 一组单选框需要配置value值 -->
      性别: 
      男<input type="radio" v-model="form.sex" value="male" />
      女<input
        type="radio"
        v-model="form.sex"
        value="female"
      /><br />

      <!-- 一组复选框需要配置value值,并用v-modle绑定一个新数组来收集这些值,这个新数组影响复选框的初始值 -->
      爱好: 
        学习<input type="checkbox" v-model="form.hobby" value="学习" />
        打游戏<input type="checkbox" v-model="form.hobby" value="打游戏" /> 
        吃饭<input
          type="checkbox"
          v-model="form.hobby"
          value="吃饭"
        /><br />

      <!-- 一组下拉框select配置v-model,option配置value -->
      地区:
      <select v-model="form.city">
        <option value="">--请输入地区--</option>
        <option value="beijing">北京</option>
        <option value="shanghai">上海</option>
        <option value="guangzhou">广州</option>
        <option value="shenzhen">深圳</option>
      </select><br />

      <!-- lazy修饰符只在失焦时收集数据 -->
      其他信息:
      <textarea cols="30" rows="10" v-model.lazy="form.other"></textarea><br />

      <!-- 单个复选框v-model默认绑定的是checked -->
      <input type="checkbox" v-model="form.agree" />阅读并接受<a href="#">《用户协议》</a><br />

      <button>提交</button>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      form:{
        account: '',
        password: '',
        sex: 'male',
        hobby: ['吃饭'],
        city: '',
        other: '',
        agree: false,
      }
    }
  },
  methods:{
    submit() {
      console.log(this.form)
    }
  }
}
</script>

<style></style>

v-model 修饰符(★)

  • .number 转数字,以parseFloat转成数字类型
  • .trim去除首尾空白
  • .lazy懒触发(失焦时触发),在change时而非input时更新

v-text 和 v-html

相当于innerText和innerHTML

v-text不常用

v-html会在后端要求输出一段转为字符串的标签时使用到

v-for

一般遍历数组(一般不遍历对象)

遍历数字时,相当于将标签复制数值对应的次数(★)

v-for="(item, index) in arr或数字" :key="数组中具有唯一性的变量"
// item和index不能反

v-bind 对于class和style的增强

动态的操作class和style

// :class 不会影响到原来的 class 属性
// 允许使用对象或者数组
// 对象(常用):如果键值对的值为true,那么就有这个,否则没有这个类
// 数组:数组中所有的类都会添加到盒子上

vue的就地复用策略(★)

就地复用:Vue会尽可能的就地(同层级,同位置),对比虚拟dom,复用旧dom结构,进行差异化更新。

好处:可以复用旧的dom结构,更新高效

虚拟dom:本质就是在内存中创建的一个保存节点信息, 属性和内容的 描述真实dom的 JS 对象

diff算法(★)

策略1:先同层级根元素比较,如果根元素变化,那么不考虑复用,整个dom树删除重建果;如果根元素不变,对比出属性的变化更新,并考虑往下递归复用。

策略2:对比同级兄弟元素时,默认按照下标进行对比复用;如果果指定了 key,就会按照相同 key 的元素来进行对比复用。

key的作用

  1. 设置 和 不设置 key 有的区别:
    • 不设置 key, 默认同级兄弟元素按照下标进行比较
    • 设置了key,按照相同key的新旧元素比较
  2. key值要求:
    • 字符串或者数值,唯一不重复
    • 有 id 用 id, 有唯一值用唯一值,实在都没有,才用索引
  3. key的好处:
    • 提高虚拟DOM的对比复用性能

filters 过滤器

常用于文本的格式化

定义过滤器:

局部过滤器: 组件私有的过滤器,可以在 filters 节点中定义过滤器,该过滤器只能在当前组件中调用

export default {
  filters: {
    upper (input) {
      return input.slice(0, 1).toUpperCase() + input.slice(1)
    }
  }
}

全局过滤器: 如果希望在多个 vue 组件之间共享过滤器,可以在main.js中通过Vue.filter()方法定义全局过滤器

// 参数1: 过滤器名称
// 参数2: 过滤器函数,处理过滤逻辑
Vue.filter('upper', function (input) {
  return input.slice(0, 1).toUpperCase() + input.slice(1)
})

调用多个过滤器

过滤器可以串联地进行调用

<template>
  <!-- 把msg的值,交给filterA进行处理 -->
  <!-- 把filterA处理的结果,再交给filterB进行处理 -->
  <!-- 最终把filterB处理,把最终的值渲染到页面 -->
  <div>{{msg | filterA | filterB}}</div>
</template>

案例:让单词首字母大写,且最多显示10个字符

过滤器传参

在使用过滤器的时候,可以额外传递参数

// 使用过滤器的时候可以传递额外的参数
<p>{{msg | filterA(arg1, arg2)}}</p>

// 定义过滤器的时候,需要接收额外的参数
filters: {
  // 建议给过滤器的额外参数提供默认值
  filterA (input, arg1 = 0, arg2 = 1) {
    
  }
}

computed 计算属性

一个特殊属性, 值依赖于另外一些数据动态计算出来

注意点:

1. 计算属性必须定义在 computed 节点中
2. 计算属性必须是一个 function,计算属性必须有返回值
3. 计算属性不能被当作方法调用, 要作为属性来用,以不要和data里重名

好处:

  1. 计算完了一次,就会自动进行缓存
  2. 如果依赖项不变, 下次使用直接从缓存取
  3. 依赖项改变, 函数自动执行并重新缓存

计算属性的完整写法

应用:

  1. 计算属性默认情况下只能获取,不能修改

  2. 要给计算属性赋值,就必须写计算属性的完整写法

computed: {
    // 默认情况下只能获取,不能修改
    // fullName() {
    //   return this.firstName + ' ' + this.lastName
    // },

    fullName: {
      // 获取时调用
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // 设置时调用 value可以拿到最新值
      set(value) {
        this.firstName = value.split(' ')[0]
        this.lastName = value.split(' ')[1]
        console.log('我调用了', value)
      }
    }
  }

全选反选案例(★)

<template>
  <div>
    <span>全选:</span>
    <input type="checkbox" v-model="checkAll" />
    <button>反选</button>
    <ul>
        // 这里的每一个checkbox都被一个li包裹,所以他们是独立的每一个checkbox,适用于v-model绑定的是checked选中状态
      <li v-for="item in arr" :key="item.name">
        <input type="checkbox" v-model="item.c" />
        <span>{{ item.name }}</span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: '猪八戒',
          c: false,
        },
        {
          name: '孙悟空',
          c: false,
        },
        {
          name: '唐僧',
          c: false,
        },
        {
          name: '白龙马',
          c: false,
        },
      ],
    }
  },

  computed: {
    // 老写法: 计算属性只能获取
    // checkAll() {
    //   return this.arr.every((item) => {
    //     return item.c === true
    //   })
    // },

    // 新写法: 计算属性既能获取. 又能设置
    checkAll: {
      get() {
        return this.arr.every((item) => {
          return item.c === true
        })
      },
      set(value) {
        this.arr.forEach((item) => {
          // 赋值
          item.c = value
        })
      },
    },
  },
}
</script>

<style></style>

watch 属性监听(★)

可以侦听到 data/computed 属性值的改变

如果监听的是复杂数据类型,需要深度监听,需要指定deep为true, 需要用到监听的完整的写法

何让侦听器函数马上执行一次呢?immediate: true

// 基本使用
watch: {
  // 只要属性发生了改变,这个函数就会执行
  // 参数1: value    变化后的值
 // 参数2: oldValue 变化前的值
  简单数据名 (value, oldValue) {

  }
}
/* 
watch: {
  msg (value, oldValue) {
    console.log('你变了', value, oldValue)
  }
}
*/

// 1. 默认情况下,watch只能监听到简单类型的数据变化,如果监听的是复杂类型,只会监听地址是否发生改变,不会监听对象内部属性的变化。
// 2. 需要使用监听的完整写法 是一个对象
watch: {
  复杂数据名: {
    // handler 数据发生变化,需要执行的处理程序
    // deep: true  如果true,代表深度监听,不仅会监听地址的变化,还会监听对象内部属性的变化
    // immediate: 立即 立刻  是否立即监听 默认是false  如果是true,代表页面一加载,会先执行一次处理程序
    // value: 数据最新值
    handler (value) {
      console.log(value)
    },
    deep: true,
    immediate: true
  }
}

/* 
watch: {
  friend: {
    handler (value) {
      console.log('你变了', value)
    },
    deep: true,
    immediate: true
  }
}
*/

组件化开发

组件:可复用的 Vue 实例, 封装标签, 样式和JS代码

什么时候封装组件:遇到重复标签, 可复用的时候

组件化 :运用封装的思想,把页面上 可复用的部分 封装为 组件,方便项目的 开发 和 维护

组件化开发的好处:

  • 提高了 复用性和灵活性
  • 提升了 开发效率 和 后期可维护性

创建和使用组件步骤

  • components文件夹中创建.vue文件 – 标签 – 样式 – JS

  • 引入组件(全局 / 局部)

    import HmHeader from './components/HmHeader'
    
  • 注册组件 (全局 / 局部)

    • 全局

      // 短横线命名法
      Vue.component('hm-header', HmHeader)
      // 大驼峰命名法(推荐)
      Vue.component('HmHeader', HmHeader)
      
      // 区别:
      // 使用 短横线命名法 时 只能使用`<hm-header> </hm-header>`
      // 使用 大驼峰命名法 时 `<HmHeader> </HmHeader>` 和 `<hm-header> </hm-header>`  都可以
      
    • 局部

      <script>
       export default {
        components: {
        		HmHeader
         }
       }
      </script>
      
  • 使用组件 (组件名用作标签)

    • 全局注册的组件 可以在任意的组件中去使用

    • 局部注册的组件 只可以在注册时所在的组件中使用

      <template>
        <div>
          <!-- 组件注册好了,就跟使用html标签一样了 -->
          <HmHeader></HmHeader>
          <HmContent></HmContent>
          <HmFooter></HmFooter>
        </div>
      </template>
      

修改组件在开发者工具中显示的名字

在目录结构复杂时,组件选项name可以让开发者在开发者工具中快速找到目标文件

// 假设此时在HmHeader组件(局部)中
<template>
  <div>
    
  </div>
</template>

<script>
 export default {
  // 指定name后,仅仅是组件在开发者工具中显示的名字变成xxx(name基本等于无用属性)
  name:'xxx'
 }
</script>
// 假设此时在main.js(全局)中
import HmHeader from './components/HmHeader'
Vue.component(HmHeader.name, HmHeader)

scoped解决组件间样式冲突

  • 默认情况下,写在组件中的样式会全局生效

  • 给当前组件的style标签添加scoped属性后,当前组件中的样式只会局部有效

<style lang="less" scoped>
div {
  background-color: pink;
}
</style>

scoped原理:

  1. 当前组件内的标签都被添加 data-v-hash值 的属性
  2. 当前组件内的css选择器都被添加 [data-v-hash值] 的属性选择器

组件通信

每个组件的数据是独立的(在各自的data中), 组件数据无法互相直接访问

组件通信的方式:父传子、子传父等

被引入的是子

父传子

步骤:

  1. 在父组件中,给子组件的标签添加属性

    <Son price="100" title="不错" :info="msg"></Son>
    
  2. 子组件中, 通过组件选项props接收

    props: ['price', 'title', 'info']
    

v-for 遍历展示组件

<template>
  <div class="container">
    <h3>我是app组件的内容</h3>
    <my-product 
      v-for="item in list" :key="item.id" 
      :price="item.proprice" 
      :title="item.proname" 
      :info="msg">
    </my-product>
  </div>
</template>

props校验

子组件通过props默认以数组的形式接收父组件传递的数据且不会进行校验如果希望校验, 需要提供对象形式的 props

①prop默认以数组接收


<script>
 export default {
  props: ['price', 'title', 'info']
 }
</script>

②希望对接收的数据校验时, props以对象接收

文档链接cn.vuejs.org/v2/guide/co…

props 提供了多种数据验证方案,例如:

  • 基础的类型检查 Number
  • 多个可能的类型 [String, Number]
  • 必填项校验 required: true
  • 默认值 default: 100
  • 自定义验证函数
// 希望对接收的数据校验时, 需要提供对象形式的 props
<script>
 export default {
  props: ['price', 'title', 'info']
 }
</script>

单向数据流

在vue中需要遵循单向数据流原则:

  1. 父组件的数据发生了改变,子组件会自动跟着变
  2. 子组件不能直接修改父组件传递过来的props,props是只读的 3. 如果父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,但也应该避免

子传父

步骤:

  1. 子组件可以通过 this.$emit('事件名', 参数1, 参数2, ...) 触发事件的同时传参的

    <template>
      <div id="product">
        
      	...
          // 第一步:注册事件
      	<button @click="cheapBuy">砍价</button>
      </div>
    </template>
    
    <script>
    export default {
      ...
      methods: {
          // 第二步:事件处理函数
      	cheapBuy () {
            // 第三步:传递自定义事件和实参
        	this.$emit('sayPrice', this.id)
      	}
      },
    }
    </script>
    
    <style>
    
    </style>
    
    
  2. 父组件可以给子组件注册对应的自定义事件,并提供对应的函数接收参数

    <template>
      <div id="app">
        <my-product 
      	...
                    
        // 第四步:添加自定义事件
      	@sayPrice="reducePrice">
    	</my-product>
      </div>
    </template>
    
    <script>
    export default {
      ...
      methods: {
          // 第五步:自定义事件的处理函数和形参
      	reducePrice (id) {
        	const produt=this.list.find(item=>item.id===id)
      	}
          produt.price -= 100
      },
    }
    </script>
    
    <style>
    
    </style>
    
    

非父子组件通信

语法:

  1. 创建事件总线

    src/EventBus/index.js – 创建空白Vue对象并导出

    cd src
    md EventBus
    cd EventBus
    new-item index.js
    
    // 在 EventBus/index.js 中,创建空白Vue对象并导出
    import Vue from 'vue'
    const eventBus = new Vue()
    export default eventBus
    
    // 在要通信的两个组件中都引入eventBus
    // (路径中如果目标文件的文件名为index,可以省略)
    import eventBus from '../EventBus'
    
  2. 在监听事件的组件(类似于子传父的父) eventBus.$on('事件名', 函数)

    <template>
      <div class="rose">
        <h3>我是rose</h3>
        <div>等着jack来找俺</div>
        {{ msg }}
      </div>
    </template>
    
    <script>
    import eventBus from '../EventBus/index'
    // 跟中介打招呼 越早越好
    export default {
      // created会在组件创建时触发,在此时就跟中介打招呼
      created() {
        // this => vue实例 rose
        console.log(this)
        console.log('我是rose,我要跟中介打招呼了')
        // 2. 跟中介打好招呼,等着jack来电
        eventBus.$on('roseWaitJack', (info) => {
          console.log(info)
          // this => vue实例 rose
          this.msg = info
        })
      },
      data() {
        return {
          msg: '',
          str: '台风会走位,回来了,我先走了',
        }
      }
    }
    </script>
    
  3. 在触发事件的组件(类似于子传父的子) eventBus.$emit('事件名', 值)

    <template>
      <div class="jack">
        <h3>我是jack</h3>
        <button @click="clickFn">我想跟rose说话</button>
        {{ str2 }}
      </div>
    </template>
    
    <script>
    import eventBus from '../EventBus'
    export default {
      data() {
        return {
          msg: 'you jump, i look look',
          str2: '',
        }
      },
      methods: {
        clickFn() {
          // 告诉中介,想和rose通话
          // 通话线路是roseWaitJack,要说的话是this.msg
          eventBus.$emit('roseWaitJack', this.msg)
        },
      },
    }
    </script>
    

vue组件的生命周期

Vue实例的生命周期就是它从创建 到 销毁 的整个过程

生命周期函数(钩子函数)

生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。

三大阶段和8个方法

  • 初始化阶段
    • beforeCreate:data数据初始化之前,组件还没有数据
    • created: data数据初始化之后,可以获取到组件的数据
    • beforeMount:DOM渲染之前,DOM还没渲染
    • mounted:DOM渲染之后,可以操作DOM了
  • 运行阶段
    • beforeUpdate: 数据更新,DOM更新前
    • updated: 数据更新,DOM更新后
  • 销毁阶段
    • beforeDestroy: 组件销毁前
    • destroyed: 组件销毁后

八个生命周期钩子函数中,最常用的两个:

created可以开始发送ajax请求,获取数据

mounted可以开始操作dom了

ref 和 $refs获取 dom元素或组件实例

步骤:

  1. 给需要获取的 dom 元素或者组件, 添加 ref 属性

    <template>
      <div>
        <div ref="box">我是div盒子</div>
        <jack ref="jack"></jack>
        <button @click="fn">按钮</button>
      </div>
    </template>
    
  2. 通过 this.$refs.xxx 获取, 拿到组件可以调用组件的方法

    import Jack from './jack.vue'
    export default {
      methods: {
        fn () {
          console.log(this.$refs.box)
          console.log(this.$refs.jack)
          this.$refs.jack.sayHi()
        }
      },
      components: {
        Jack
      }
    }
    

$nextTick等待DOM更新

data改变,更新DOM是异步的,this.$nextTick里的函数体会在DOM完成更新后才执行,此时才可以获取到更新好的DOM

<template>
  <div>
    <!-- 需求: 点击按钮, 切换显示输入框 -->
    <input ref="inp" type="text" v-if="isShowInput">
    <button @click="fn" v-else>点此搜索</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      isShowInput: false
    }
  },
  methods: {
    fn () {
      this.isShowInput = true
      this.$nextTick(() => {
        // 显示输入框后立即获取焦点
        // 由于原本vue更新DOM是异步的,所以使用$nextTick让获取焦点在DOM更新完成才执行
        this.$refs.inp.focus()
      })
    }
  }
}
</script>

dynamic 动态组件

解决多组件同一位置, 切换显示的需求

步骤:

  1. 准备被切换的 2个组件, 并引入注册
  2. 准备变量来承载要显示的"组件名"
  3. 设置挂载点<component>, is属性设置要显示的组件
  4. 点击按钮 – 修改comName变量里的"组件名" ( 修改 is 的值)
<template>
  <div>
    <h3>动态组件的演示</h3>
    <!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 -->
    <button @click="comName = 'MySwiper'">swiper</button>
    <button @click="comName = 'MyNav'">nav</button>
    
    <component :is="comName"></component>
  </div>
</template>

<script>
import MyNav from './MyNav.vue'
import MySwiper from './MySwiper.vue'
export default {
  components: {
    MyNav,
    MySwiper
  },
  data () {
    return {
      comName: 'MyNav'
    }
  }
}
</script>

keep-alive 保持组件的状态

默认情况下,切换动态组件时无法保持组件的状态。会将组件销毁, 将来显示时, 又会重新创建

  • keep-alive 包裹动态组件时,会缓存不活动的组件实例
  • 缓存的组件是不会被销毁的,所以beforeDestroy和Destroyed两个钩子函数不会执行
  • 缓存组件有两个新增的钩子函数
    • activated( ) { } 缓存组件激活时触发
    • deactivated( ) { } 缓存组件失活时触发
<template>
  <div>
    <h3>动态组件的演示</h3>
    <!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 -->
    <button @click="comName = 'MySwiper'">swiper</button>
    <button @click="comName = 'MyNav'">nav</button>
      
    <keep-alive>
        <component :is="comName"></component>
    </keep-alive>
  </div>
</template>


<script>
import MyNav from './MyNav.vue'
import MySwiper from './MySwiper.vue'
export default {
  components: {
    MyNav,
    MySwiper
  },
  data () {
    return {
      comName: 'MyNav'
    }
  },
  // 缓存组件激活的时候触发
  activated () {
    console.log('组件激活了')
  },
  // 缓存组件失活的时候触发
  deactivated () {
    console.log('组件失活了')
  }
}
</script>

插槽

能够自定义组件内部的一些结构

插槽基本语法:

  1. 组件内用<slot></slot>占位
  2. 使用组件时<MyDialog></MyDialog>夹着的地方, 传入标签替换slot
// 组件内
<template>
  <div class="MyDialog">
    <div class="header">
      <h3>友情提示</h3>
    </div>
    <div class="content">
      // 插槽位置
      <slot></slot>
    </div>
    <div class="footer">
      <button>关闭</button>
    </div>
  </div>
</template>

// 使用组件时
<template>
  <div id=>
    <MySwiper>
        <component :is="comName"></component>
    </MySwiper>
  </div>
</template>

<script>
import MySwiper from './MySwiper.vue'
export default {
	components: {
		MySwiper
  	}
}
</script>

后备内容(slot的默认内容)

<slot> 插槽可以包裹后备内容,如果组件的使用者没有为插槽提供任何内容,则后备内容会生效

<template>
  <div class="my-dialog">
    <div class="content">
      <slot>这是后备内容</slot>
    </div>
  </div>
</template>

具名插槽

插槽的分类:

  • 默认插槽(匿名插槽)

    <slot></slot> 等价于<slot name="default"></slot>

  • 具名插槽

    具有名字的插槽, 可以实现定向分发

具名插槽的使用步骤:

  • 给插槽起名字

    <div class="header">
      <slot name="header"></slot>
    </div>
    <div class="content">
      <slot>这是后备内容</slot>
    </div>
    <div class="footer">
      <slot name="footer"></slot>
    </div>
    
  • 需要使用 template 标签, 将内容包裹成一个整体

  • 通过v-slot:插槽名, 指定具体分发给谁(可以用#替换 v-slot:)

    <my-dialog>
      <template v-slot:header>
        <h3>这是大标题</h3>
      </template>
    
      <template v-slot:default>
        <p>这是内容</p>
      </template>
    
      <template v-slot:footer>
        <button>确认</button>
        <button>取消</button>
      </template>
    </my-dialog>
    

作用域插槽

给插槽绑定数据,让对应的组件可以使用这些数据

步骤:

  • 给 slot 标签, 以 添加属性的方式传值

    <slot name="bottom" :yes="yes" :no="no" money="100"></slot>
    
    
  • 所有属性都会被收集到一个对象中

  • template中, 通过 v-slot:插槽名= "obj" 接收,也可以使用解构赋值简化数据的接收

    <template #bottom="obj">
      <!-- {{ obj }} -->
      <button>{{ obj.yes }}</button>
      <button>{{ obj.no }}</button>
      <button>{{ obj.money }}</button>
    </template>
    // 或
    <template #bottom="{ yes, no, money }">
      <button>{{ yes }}</button>
      <button>{{ no }}</button>
      <button>{{ money }}</button>
    </template>
    

自定义指令

自己定义指令, 封装dom操作,扩展额外功能

局部注册:

<template>
  <div>
    <h3>自定义指令</h3>
      
     // 以 v-指令名 方式使用
    <div v-color="color">我是内容</div>
  </div>
</template>

<script>
export default {
  data(){
    return {
      color:'green'
    }
  },
  directives: {
    // 自定义一个局部指令
    // 指令名后跟一个对象
    color: {
      // 钩子函数inserted,在指令所在的元素渲染的时候触发
      inserted (el, binding) {
        // 第一个参数el,代表指令所在标签的DOM元素
        // binding.value,代表指令后面跟着的值的最新值
        el.style.color = binding.value
      },
      // 钩子函数update,在指令所在的元素改变时触发
      update (el, binding) {
        el.style.color = binding.value
      }
    }
  }
}
</script>

全局注册:

Vue.directive('指令名', {
  inserted (el) {
    el.focus()
  }
})

单页应用程序与路由

SPA - 单页应用程序

SPA: Single Page Application 单页面应用程序

MPA : Multiple Page Application多页面应用程序

定义:

SPA是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序

优点:

  1. 不整个刷新页面,每次请求仅获取需要的部分,用户体验更好

  2. 数据传递容易, 开发效率高

缺点:

  1. 开发成本高(需要使用vue-router)
  2. 首次加载会比较慢一点,不利于seo

路由介绍

路由 : 是浏览器 URL 中的哈希值( # hash) 与 展示视图内容(组件) 之间的对应规则

  • 路由是由开发人员制定的一套映射规则
  • 当 URL 中的哈希值( # hash) 发生改变后,路由会根据制定好的规则, 展示对应的视图内容(即切换到对应的组件)

vue 中的路由 : 是 hashcomponent 的对应关系, 一个哈希值对应一个组件

前端路由 - 工作模式原理

基本思路:

  1. 用户点击了页面上的路由链接
  2. 导致了 URL 地址栏中的 Hash 值发生了变化
  3. 前端路由监听了到 Hash 地址的变化
  4. 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

实现简单的前端路由:

  1. 导入并注册 my-home.vue my-movie my-about 三个组件
<script>
import MyAbout from './components/my-about.vue'
import MyHome from './components/my-home.vue'
import MyMovie from './components/my-movie.vue'
export default {
  components: {
    MyHome,
    MyAbout,
    MyMovie
  }
}
</script>
  1. 通过 comName 动态组件, 控制要显示的组件
<template>
  <div>
    <h1>App组件</h1>
    <component :is="comName"></component>
  </div>
</template>

<script>
import MyAbout from './components/my-about.vue'
import MyHome from './components/my-home.vue'
import MyMovie from './components/my-movie.vue'
export default {
  data () {
    return {
      comName: 'my-home'
    }
  },
  components: {
    MyHome,
    MyAbout,
    MyMovie
  }
}
</script>
  1. 声明三个导航链接, 点击时修改地址栏的 hash 值
<template>
  <div>
    <h1>App组件</h1>
    <a href="#/home">首页</a>&nbsp;
    <a href="#/movie">电影</a>&nbsp;
    <a href="#/about">关于</a>&nbsp;
    <component :is="comName"></component>
  </div>
</template>
  1. 在 created钩子函数 中, 监视地址栏 hash 的变化, 一旦变化, 动态切换展示的组件
created () {
  // 原生JS中window.onhashchange可以监视监视地址栏 hash 的变化
  window.onhashchange = () => {
    // location.hash可以获取当前地址的hash
    console.log(location.hash)
    switch(location.hash) {
      case '#/home':
        this.comName = 'my-home'
        break
      case '#/movie':
        this.comName = 'my-movie'
        break
      case '#/about':
        this.comName = 'my-about'
        break
    }
  }
},

vue-router

中文文档

路由 - 组件分类

.vue文件本质无区别, 一般用法如下:

  • views下的.vue文件是页面组件, 配合路由切换,

  • components下的.vue文件是复用组件,一般引入到views下的.vue中复用展示数据

创建路由

vue-router文档

App.vue - 页面标签和样式准备

<template>
  <div>
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/part">朋友</a>
    </div>
    <div class="top">
      
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.footer_wrap {
  position: fixed;
  left: 0;
  top: 0;
  display: flex;
  width: 100%;
  text-align: center;
  background-color: #333;
  color: #ccc;
}
.footer_wrap a {
  flex: 1;
  text-decoration: none;
  padding: 20px 0;
  line-height: 20px;
  background-color: #333;
  color: #ccc;
  border: 1px solid black;
}
.footer_wrap a:hover {
  background-color: #555;
}
.top {
  padding-top: 62px;
}
</style>

步骤:

  1. 安装
yarn add vue-router
  1. 导入路由
// 在main.js中
import VueRouter from 'vue-router'
  1. 使用路由插件
// 在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
  1. 创建路由对象
const router = new VueRouter({
})
  1. 关联到vue实例
new Vue({
  router
})

配置路由规则

// 4. 创建一个路由对象
const router = new VueRouter({
  // 路由的规则routes
  routes: [
    {
      // 路径 锚点
      // 组件
      path: '/find',
      component: Find,
    },
    {
      path: '/my',
      component: My,
    },
    {
      path: '/friend',
      component: Friend,
    },
  ],
})

指定路由的出口

<div class="top">
  <!-- 路由的出口:将来路由对应的组件显示位置 -->
  <router-view></router-view>
</div>

路由的封装

  • 新建文件router/index.js
// 在router/index.js中
import Vue from 'vue'
// 导入VueRouter
import VueRouter from 'vue-router'

import Find from '../views/Find'
import My from '../views/My'
import Friend from '../views/Friend'

// 使用vue插件  vue的插件想要生效必须调用Vue.use()方法  
Vue.use(VueRouter)

// 创建一个路由对象
const router = new VueRouter({
  // 路由的规则
  // route: 一条路由规则
  routes: [
    {
      // 路径 锚点
      // 组件
      path: '/find',
      component: Find,
    },
    {
      path: '/my',
      component: My,
    },
    {
      path: '/friend',
      component: Friend,
    },
  ],
})

export default router

  • main.js中
import router from './router'

new Vue({
  // 关联路由对象和vue实例
  render: (h) => h(App),
  router,
}).$mount('#app')

vue路由 - 声明式导航

声明式导航 - 基础使用

  1. vue-router提供了一个全局组件 router-link
  2. router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
  3. router-link提供了声明式导航高亮的功能(自带类名)
<template>
  <div>
    <div class="footer_wrap">
      <!-- 
        router-link
        1.将来还是会渲染成a
        2.to  不加#
       -->
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/part">朋友</router-link>
    </div>
    <div class="top">
      <!-- 组件展示在这里 -->
      <!-- 路由出口: 将来路由对应的组件显示位置 -->
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
/* 省略了 其他样式 */
.router-link-exact-active,
.router-link-active {
  background-color: #555 !important;
}
</style>

导航高亮

RouterLink会自动给当前的链接添加两个类名:

  • router-link-active: 激活的导航链接 模糊匹配

    将来地址栏的地址 必须和 to="/my" 完全相同 才会加这个类名

  • router-link-exact-active: 激活的导航链接 精确匹配

    将来地址栏的地址只要是以/find 开头 to="/find/..." 就会加这个类名

<RouterLink to="/" exact>发现音乐</RouterLink>
<RouterLink to="/my">我的音乐</RouterLink>
<RouterLink to="/friend">朋友</RouterLink>

可以修改默认高亮的类名

const router = new VueRouter({
  // 修改高亮类名
  // 模糊
  linkActiveClass: 'active',
  // 精确
  linkExactActiveClass: 'exact-active',
}

声明式导航 - 跳转传参

在跳转路由时, 可以给路由对应的组件内传值

通过router-link上的to属性传值, 语法格式如下:

  • /path?参数名=值

  • /path/:id – 需要路由对象提前配置 path: “/path/参数名”

对应页面组件接收传递过来的值

  • $route.query.参数名
  • $route.params.参数名

路由传参步骤:

  1. 创建components/Part.vue - 准备接收路由上传递的参数和值

    // 在components/Part.vue中
    <template>
      <div>
          <p>关注明星</p>
          <p>发现精彩</p>
          <p>寻找伙伴</p>
          <p>加入我们</p>
          <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p>
      </div>
    </template>
    
  2. 路由定义

    // 在router/index.js中
    {
        path: "/part",
        component: Part
      },
      {
        path: "/part/:username", // 有:的路径代表要接收具体的值
        component: Part
      },
    
  3. 导航跳转, 传值给Part.vue组件

    // 在App.vue中
    <router-link to="/part?name=小明">小明</router-link>
    <router-link to="/part/小红">小红</router-link>
    

vue路由 - 重定向和模式

路由 - 重定向

匹配path后, 强制切换到目标path上

  • 网页打开url默认hash值是/路径
  • redirect是设置要重定向到哪个路由路径

例如: 网页默认打开, 匹配路由"/", 强制切换到"/find"上

const routes = [
  {
    path: "/", // 默认hash值路径
    redirect: "/find" // 重定向到/find
    // 浏览器url中#后的路径被改变成/find-重新匹配数组规则
  }
]

路由 - 404页面

如果路由hash值, 没有和数组里规则匹配,默认给一个404页面

语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面

  1. 创建NotFound页面

    <template>
      <img src="../assets/404.png" alt="">
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style scoped>
        img{
            width: 100%;
        }
    </style>
    
  2. 在main.js - 修改路由配置

    import NotFound from '@/views/NotFound'
    
    const routes = [
      // ...省略了其他配置
      // 404在最后(规则是从前往后逐个比较path)
      {
        path: "*",
        component: NotFound
      }
    ]
    

路由 - 模式设置

修改路由在地址栏的模式

hash路由例如: http://localhost:8080/#/home

history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)

模式文档

router/index.js

const router = new VueRouter({
  routes,
  mode: "history" // 打包上线后需要后台支持, 模式是hash
})

vue路由 - 编程式导航

编程式导航用JS代码跳转, 声明式导航用a标签

编程式导航 - 基础使用

语法:

// 字符串
this.$router.push('home')

// 对象
this.$router.push({ path: 'home' })

// 命名的路由
this.$router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
  1. main.js - 路由数组里, 给路由起名字
{
    path: "/find",
    name: "Find",
    component: Find
},
{
    path: "/my",
    name: "My",
    component: My
},
{
    path: "/part",
    name: "Part",
    component: Part
},
  1. App.vue - 换成span 配合js的编程式导航跳转
<template>
  <div>
    <div class="footer_wrap">
      <span @click="btn('/find', 'Find')">发现音乐</span>
      <span @click="btn('/my', 'My')">我的音乐</span>
      <span @click="btn('/part', 'Part')">朋友</span>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
  methods: {
    btn(targetPath, targetName){
      // 方式1: path跳转
      this.$router.push({
        // path: targetPath,
        name: targetName
      })
    }
  }
};
</script>

编程式导航 - 跳转传参

语法 query / params 任选 一个

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

// 对应路由接收   $route.params.参数名   取值
// 对应路由接收   $route.query.参数名    取值

$route.query$route.params的区别:

  • 如果提供了 pathparams 会被忽略

  • $route.query一般用path来引入,也可以用name来引入

  • $route.params传参只能用name来引入

App.vue

<template>
  <div>
    <div class="footer_wrap">
      <span @click="btn('/find', 'Find')">发现音乐</span>
      <span @click="btn('/my', 'My')">我的音乐</span>
      <span @click="oneBtn">小明</span>
      <span @click="twoBtn">小红</span>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
  methods: {
    btn(targetPath, targetName){
      // 方式1: path跳转
      this.$router.push({
        // path: targetPath,
        name: targetName
      })
    },
    oneBtn(){
      this.$router.push({
        name: 'Part',
        params: {
          username: '小明'
        }
      })
    },
    twoBtn(){
      this.$router.push({
        name: 'Part',
        query: {
          name: '小红'
        }
      })
    }
  }
};
</script>

vue路由 - 嵌套

vue路由 - 路由嵌套

在现有的一级路由下, 再嵌套二级路由

router-view嵌套架构图

  1. 创建需要用的所有组件

    src/views/Find.vue -- 发现音乐页

    src/views/My.vue -- 我的音乐页

    src/views/Second/Recommend.vue -- 发现音乐页 / 推荐页面

    src/views/Second/Ranking.vue -- 发现音乐页 / 排行榜页面

    src/views/Second/SongList.vue -- 发现音乐页 / 歌单页面

  2. main.js– 继续配置2级路由

    一级路由path从/开始定义

    二级路由往后path直接写名字, 无需/开头

    嵌套路由在上级路由的children数组里编写路由信息对象

  3. 说明:

    App.vue的router-view负责发现音乐和我的音乐页面, 切换

    Find.vue的的router-view负责发现音乐下的, 三个页面, 切换

  4. 配置二级导航和样式(==可直接复制==) - 在Find.vue中

<template>
  <div>
    <!-- <p>推荐</p>
    <p>排行榜</p>
    <p>歌单</p> -->
    <div class="nav_main">
      <router-link to="/find/recommend">推荐</router-link>
      <router-link to="/find/ranking">排行榜</router-link>
      <router-link to="/find/songlist">歌单</router-link>
    </div>

    <div style="1px solid red;">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.nav_main {
  background-color: red;
  color: white;
  padding: 10px 0;
}
.nav_main a {
  text-align: center;
  text-decoration: none;
  color: white;
  font-size: 12px;
  margin: 7px 17px 0;
  padding: 0px 15px 2px 15px;
  height: 20px;
  display: inline-block;
  line-height: 20px;
  border-radius: 20px;
}
.nav_main a:hover {
  background-color: brown;
}
.nav_main .router-link-active{
  background-color: brown;
}
</style>
  1. 配置路由规则-二级路由展示
const routes = [
  // ...省略其他
  {
    path: "/find",
    name: "Find",
    component: Find,
    children: [
      {
        path: "recommend",
        component: Recommend
      },
      {
        path: "ranking",
        component: Ranking
      },
      {
        path: "songlist",
        component: SongList
      }
    ]
  }
  // ...省略其他
]
  1. 说明:
  • App.vue, 外层的router-view负责发现音乐和我的音乐页面切换

  • Find.vue 内层的router-view负责发现音乐下的子tab对应的组件切换

  1. 运行 - 点击导航观察嵌套路由在哪里展示

vuex

vuex的定义

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

处理方式:把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,任何组件都能获取状态

作用:解决多组件状态共享的问题

特点:

  • 响应式 只要数据修改了, 所有用到该数据的地方, 自动更新 (数据驱动)
  • 操作更简洁, 逻辑清晰

适用场景: 多个组件均需要共享的数据,才有必要存储在vuex中,对于某个组件中的私有数据,依旧存储在组件自身的data中

vuex创建一个仓库

  1. 创建项目
// 在桌面shirft+右键,s键,运行windowspowershell
md vuex-demo
cd vuex-demo
vue create demo01
// 按默认项配置
cd demo01
yarn serve
// 复制Local地址在浏览器打开
// ctrl+c终止服务
code . // 在vscode中打开

  1. 配置端口和自动打开浏览器
// ctrl+~打开vscode终端
// 在根目录新建vue.config.js文件
new-item vue.config.js
// 在vue.config.js中配置端口
module.exports={
    devServer:{
        // 端口号
        port:3000,
        // 自动打开浏览器
        open:true
	}
}
  1. 更改文件
删除src/assets文件夹
删除src/components文件夹下的所有文件
删除App.vue文件中的所有内容
终端命令yarn serve
开启服务成功
ctrl+c终止服务
  1. 安装vuex
yarn add vuex
  1. 在src文件夹下,新建store/index.js文件,专门存放 vuex
cd src
md store
cd store
new-item index.js
  1. 在store/index.js中,创建一个空仓库store
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store() // 固定语法

// 导出仓库
export default store
  1. 在 main.js 中导入挂载到 Vue 实例上
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store
}).$mount('#app')

核心概念

state

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

// 创建仓库 store
const store = new Vuex.Store({
  // state 状态, 即数据, 类似于vue组件中的data,
  // 区别在于 data 是组件自己的数据, 而 state 中的数据整个vue项目的组件都能访问到
  state: {
    count: 100,
    num:10
  }
})

组件获取state中的数据:

  • 原始形式

    • 插值表达式

      <h1>state的数据 - {{ $store.state.count }}</h1>
      
    • 计算属性

      // 把state中数据,定义在组件内的计算属性中
        computed: {
          count () {
            return this.$store.state.count
          },
      	num () {
            return this.$store.state.num
          }
        }
      
  • mapState辅助函数

    使用步骤:

    1. 按需导入 mapState (mapState是vuex中的一个函数)

      import { mapState } from 'vuex'
      
    2. 采用字符串数组形式引入state属性(简写时计算属性的名称要与 state 中数据的名称相同)

      mapState(['count','num'])
      // 等于
      {
      	count () {
            return this.$store.state.count
          },
      	num () {
            return this.$store.state.num
          }
      }
      
    3. 配合展开运算符将导出的状态映射成组件的计算属性

      computed: {
        ...mapState(['count']),
      	(其它非公共计算属性)
      }
      

mutations

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

开启严格模式,state数据的修改只能通过mutations,并且mutations必须是同步

const store  = new Vuex.Store({
  state: {
    count: 0
  },
  // 定义mutations
  mutations: {
    // 方法里的第一个参数是当前store的state属性
    // payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
    addCount (state) {
      state.count += 1
    }
  }
})

组件中提交 mutations

this.$store.commit('addCount')
  • 带参数的 mutation:

    1. 提供mutation函数

      mutations: {
        ...
        inputCount (state, count) {
          state.count = count
        }
      },
      
    2. 注册事件提交

      <input type="text" :value="count" @input="handleInput">
      
    3. 提交mutation

      handleInput (e) {
        this.$store.commit('inputCount', +e.target.value)
      }
      

      注意:提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象

      this.$store.commit('inputCount', {
        count: e.target.value
      })
      
  • mapMutations辅助函数

    使用步骤:

    1. 按需导入 mapMutations (mapMutations是vuex中的一个函数)

      import { mapMutations } from 'vuex'
      
    2. 将mutations中的方法导入到methods中(简写时事件处理函数的名称要与 mutations中方法名称相同)

      methods: {
          ...mapMutations(['addCount'])
      }
      // 等于
      methods: {
            // commit(方法名, 载荷参数)
            addCount () {
                this.$store.commit('addCount')
            }
       }
      
    3. 此时,就可以直接通过this.addCount调用了

      <button @click="addCount">值+1</button>
      

actions

state是存放数据的

mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化),

actions则负责进行异步操作

  • 定义仓库中定义actons

    actions: {
      // mapActions中方法名称setAsyncCount与actions中方法名称与组件中事件名称相同
      setAsyncCount (context, num) {
        // 一秒后, 给一个数, 去修改 num
        setTimeout(() => {
          // addCount为异步中调用的mutations中的方法
          context.commit('addCount', num)
        }, 1000)
      }
    },
    
  • 原始调用

    methods:{
    	setAsyncCount () {
      		this.$store.dispatch('setAsyncCount', 200)
    	}
    }
    
  • mapActions辅助函数

    使用步骤:

    1. 按需导入 mapActions (mapActions是vuex中的一个函数)

      import { mapActions } from 'vuex'
      
    2. 将actions中的方法导入到methods中(简写时事件处理函数的名称要与 mapActions中方法名称相同)

      methods: {
          ...mapActions(['setAsyncCount'])
      }
      // 等于
      methods: {
            // dispatch(方法名, 载荷参数)
            setAsyncCount () {
                this.$store.dispatch('setAsyncCount', 200)
            }
       }
      
    3. 此时,就可以直接通过this.setAsyncCount调用了

      <button @click="setAsyncCount(200)">+异步</button>
      

getters

getters定义了一些从state中派生出的状态,这些状态依赖于state

就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

  • 定义

    const store = new Vuex.Store({
        state: {
            money: 100,
            msg: 'www',
        },
        getters:{
            // getters函数的第一个参数是 state
            // 必须要有返回值
            moneyTip(state){
                return state.money
            }
        }
    })
    
  • 原始调用

    computed:{
    	moneyTip () {
      		return this.$store.getters.moneyTip
    	}
    }
    
  • mapGetters辅助函数

    使用步骤:

    1. 按需导入 mapGetters(mapGetters是vuex中的一个函数)

      import { mapGetters } from 'vuex'
      
    2. 将getters中的方法导入到computed中(简写时事件处理函数的名称要与 mapActions中方法名称相同)

      computed: {
          ...map.mapGetters(['moneyTip'])
      }
      // 等于
      computed: {
      	// getters.计算属性名
      	moneyTip () {
      		return this.$store.getters.moneyTip
      	}
       }
      
    3. 此时,就可以直接通过this.moneyTip调用了

      <p>{{moneyTip}}</p>
      

模块 module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

Vuex模块化步骤

  1. 在store文件夹下,新建模块文件

    new-item user.js
    new-item setting.js
    
  2. 完善各模块数据

    // user.js
    export default {
      // 命令空间
      namespaced: true, // 有了该代码,把mutations的方法变成该模块内的局部方法
    
      state: {
        name: 'xm',
        age: 19,
        desc: '小小的小明,大大的暖男',
      },
      mutations: {
        // 修改desc数据
        changeDesc(state) {
          console.log('user 模块下的desc描述')
    
          state.desc = 'user 模块下的desc描述'
        },
      },
      actions: {},
      getters: {},
    }
    
    // setting.js
    const state = {
      title: '这是好网站',
      desc: '该网站已运行超10年',
    }
    
    const mutations = {
      changeDesc(state) {
        console.log('setting 模块的desc描述')
    
        state.desc = 'setting 模块的desc描述'
      },
    }
    
    const actions = {}
    
    const getters = {}
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions,
      getters,
    }
    
    
  3. 在store/index.js中的modules下导入模块

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    // 导入user模块
    import user from './user'
    import setting from './setting'
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
      ...
      modules: {
        /* 模块名: {
          state: {},
          mutations: {},
          actions,: {},
          getters: {}
        }, */
        user,
        setting,
      },
    })
    
    export default store
    
  4. 使用模块中的数据

    $store.state.模块名.xxx
    

命名空间 namespaced

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

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名

// modules/user.js
const state = {
  userInfo: {
    name: 'zs',
    age: 18
  },
  myMsg: '我的数据'
}

const mutations = {
  updateMsg (state, msg) {
    state.myMsg = msg
  }
}

const actions = {}

const getters = {}

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

提交模块中的mutation

全局的:   this.$store.commit('mutation函数名', 参数)

模块中的: this.$store.commit('模块名/mutation函数名', 参数)

提交模块中的action

全局的:   this.$store.dispatch('action名字', 参数)

模块中的: this.$store.dispatch('模块名/action名字', 参数)

namespaced: true 后, 要添加映射, 可以加上模块名, 找对应模块的 state/mutations/actions/getters

computed: {
  // 全局的
  ...mapState(['count']),
  // 模块中的
  ...mapState('user', ['myMsg']),
},
methods: {
  // 全局的
  ...mapMutations(['addCount'])
  // 模块中的
  ...mapMutations('user', ['updateMsg'])
}