Vue全家桶知识点总结(持续更新中)

Lou.Chen
大约 60 分钟

1.MVVM 思想

  • M:即Model,模型,包括数据和一些基本操作

  • V:即View,视图,页面渲染结果

  • VM:即View-Model,模型与视图间的双向操作(无需开发人员干涉)

在MVVM 之前,开发人员从后端获取需要的数据模型,然后要通过DOM 操作Model 渲染到View 中。而后当用户操作视图,我们还需要通过DOM 获取View 中的数据,然后同步到Model 中。

而MVVM 中的VM 要做的事情就是把DOM 操作完全封装起来,开发人员不用再关心Model和View 之间是如何互相影响的:

  • 只要我们Model 发生了改变,View 上自然就会表现出来。

  • 当用户修改了View,Model 中的数据也会跟着改变。

把开发人员从繁琐的DOM 操作中解放出来,把关注点放在如何操作Model 上。

2.创建示例项目

官网文档提供了3 中安装方式:

  1. 直接script 引入本地vue 文件。需要通过官网下载vue 文件。

  2. 通过script 引入CDN 代理。需要联网,生产环境可以使用这种方式

  3. 通过npm 安装。这种方式也是官网推荐的方式,需要nodejs 环境。

  • 新建文件夹hello-vue,并使用vscode 打开

  • 使用vscode 控制台,npm install -y

  • 项目会生成package.json 文件,类似于maven 项目的pom.xml 文件。

  • 使用npm install vue,给项目安装vue;项目下会多node_modules 目录,并且在下面有一个vue 目录。

3.指令

什么是指令?

  • 指令(Directives) 是带有v- 前缀的特殊特性。
  • 指令特性的预期值是:单个JavaScript 表达式。
  • 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。

3.1 插值表达式{{xxx}}

  • 该表达式支持JS 语法,可以调用js内置函数(必须有返回值)

  • 表达式必须有返回结果。例如1 + 1,没有结果的表达式不允许使用,如:let a = 1 + 1;

  • 可以直接获取Vue实例中定义的数据或函数,不需要加this关键字

插值闪烁

使用{{}}方式在网速较慢时会出现问题。在数据未加载完成时,页面会显示出原始的{{}},加载完毕后才显示正确数据,我们称为插值闪烁。

3.1.1 v-cloak

使用v-cloak可以解决插值闪烁

<div v-cloak>
  {{ message }}
</div>

3.2 v-text和v-html

可以使用v-textv-html 指令来替代{{}}, 不会出现插值闪烁,当没有数据时,会显示空白或者默认数据

说明:

  • v-text:将数据输出到元素内部,如果输出的数据有HTML 代码,会作为普通文本输出

  • v-html:将数据输出到元素内部,如果输出的数据有HTML 代码,会被渲染

<div id="app1">
            <p v-html="rawhtml"></p>
        </div>
        <script>
            var vue1=new Vue({
                el:"#app1",
                data:{
                    rawhtml:"<span style='color:red'>This should be red.</span>"
                }
            })
          
        </script>

3.3 v-bind

html 属性不能使用双大括号形式绑定,我们使用v-bind 指令给HTML 标签属性绑定值;

而且在将v-bind 用于classstyle 时,Vue.js 做了专门的增强。

3.3.1 绑定class
  <div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }">
  </div>
<script>
    let vm = new Vue({
    el: "#app",
    data: {
      isActive: true,
      hasError: false
}
})
</script>
3.3.2 绑定style

v-bind:style 的对象语法十分直观,看着非常像CSS,但其实是一个JavaScript 对象。style属性名可以用驼峰式(camelCase) 或短横线分隔(kebab-case,这种方式记得用单引号括起来) 来命名。

例如:font-size-->fontSize

<div id="app" v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<script>
let vm = new Vue({
    el: "#app",
    data: {
    activeColor: 'red',
    fontSize: 30
}
})
</script>
3.3.3 v-bind绑定多个属性

如果你想要将一个对象的所有 property 都绑定,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。

  • 同时给input标签的idvalue进行绑定
<template>
  <div>
    <input type="text" v-bind="post">
  </div>
</template>
<script>
import testComponent from "@/components/C";
export default {
  name: "Home",
  components:{testComponent},
  data() {
    return {
      post: {
        id: 1,
        value: 'My Journey with Vue'
      }
    }
  },
}
</script>

绑定disabled属性:

<div id="app1">
  					<!--当绑定的disabled的值为`null`,`false`,`undefined` 那么这个disabled属性可能就不会渲染 只有为`true`才渲染-->
            <button v-bind:disabled="isButtonDisabled">Button</button>
        </div>
        <script>
            var vue1=new Vue({
                el:"#app1",
                data:{         
                    isButtonDisabled:true
                }
            })
            
        </script>
3.3.4 v-bind缩写

v-bind:disabled="isButtonDisabled" ==> :disabled="isButtonDisabled"

<div id="app1">
            <button :disabled="isButtonDisabled">Button</button>
        </div>
        <script>
            var vue1=new Vue({
                el:"#app1",
                data:{         
                    isButtonDisabled:true
                }
            })
            
        </script>

3.4 v-once

只能绑定一次属性值

<div id="app1">
            <span v-once>这个值将永远不会改变:{{msg}}</span>
        </div>
        <script>
            var vue1=new Vue({
                el:"#app1",
                data:{
                    msg:"哈哈"
                }
            })
            //不会改变msg的值
            vue1.msg="hehe"
        </script>

3.5 v-model

刚才的v-text、v-html、v-bind 可以看做是单向绑定,数据影响了视图渲染,但是反过来就不行。即视图值的改变,并不会影响真实的数据值

v-model 是双向绑定,视图(View)和模型(Model)之间会互相影响

既然是双向绑定,一定是在视图中可以修改数据,这样就限定了视图的元素类型。目前 v-model 的可使用元素有:

  • input
  • select
  • textarea
  • checkbox
  • radio
  • components(Vue 中的自定义组件)
  • 多个CheckBox对应一个model 时,model 的类型是一个数组,单个checkbox 值默认是 boolean 类型

  • radio 对应的值是input 的value 值

  • texttextarea 默认对应的model 是字符串

  • select单选对应字符串,多选对应也是数组

基本上除了最后一项,其它都是表单的输入项

<div id="d1">
    <input type="text" name="" id="" v-model="num">
    <p>数量:{{num}}</p>
</div>
    <script src="node_modules/vue/dist/vue.js"></script>
    <script>
        let vm=new Vue({
            el:"#d1",
            data:{
                num:0
            }
        })
    </script>

3.6 v-on

3.6.1 基本用法

v-on 指令用于给页面元素绑定事件

语法:v-on:事件名="js片段或函数名"

  • 方法中使用data中的属性,必须要加this

  • v-on:事件 可以简写为 :事件

    v-on:click="openBtn" ==> @click="openBtn"

<div id="d1">
    <button :disabled="isButtonDisabled">Button</button>
    <button v-on:click="openBtn">启用</button>
    <button @click="isButtonDisabled=true">隐藏</button>
</div>
    <script src="node_modules/vue/dist/vue.js"></script>
    <script>
        let vm=new Vue({
            el:"#d1",
            data:{
                isButtonDisabled:false
            },
            methods:{
                openBtn() {
                    this.isButtonDisabled=false
                }
            }
        })
    </script>
3.6.2 事件修饰符

语法:v-on:事件名.修饰符="js片段或函数名",简写 @事件名.修饰符="js片段或函数名"

常见修饰符:

  • .stop :阻止事件冒泡到父元素

  • .prevent:阻止默认事件发生

  • .capture:使用事件捕获模式

  • .self:只有元素自身触发事件才执行。(冒泡或捕获的都不执行)

  • .once:只执行一次

<div id="d1">
    点赞数:{{ num }}
    <div @click="preduceGoood">
        <!-- 阻止默认跳转事件的发生 -->
        <a href="http://www.baidu.com" @click.prevent="preduceGoood">去百度1</a>
        <!--阻止自身默认事件的发生,并且阻止事件冒泡到父元素-->
        <a href="http://www.baidu.com" @click.prevent.stop="preduceGoood">去百度2</a>
    </div>
    <button @click="preduceGoood">减赞</button>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            num: 10
        },
        methods: {
            preduceGoood() {
                this.num--
            }
        }
    })
</script>
3.6.3 按键修饰符

在监听键盘事件时,我们经常需要检查常见的键值。Vue 允许为v-on 在监听键盘事件时添加按键修饰符,一般按键修饰符都有对应的keycode码,但是不方便记忆,这里可以使用按键修饰符的别名

语法:v-on:按键.修饰符="js片段或函数名" 简写 @按键.修饰符="js片段或函数名"

按键:

  • keyup 表示按下
  • keydown 表示 松开

常见修饰符别名:

  • .enter

  • .tab

  • .delete (捕获“删除”和“退格”键)

  • .esc

  • .space

  • .up

  • .down

  • .left

  • .right

<div id="d1">
    <!--按住⬆会增加数字 按住⬇后松开,会减数字️ -->
    点赞数: <input type="text" name="" id="" @keydown.up="num++" @keyup.down="num--" v-model="num">
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            num: 10
        },
        methods: {
        }
    })
</script>
3.6.4 组合按键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器

<div id="d1">
    <!--同时按住「⬆」和「alt」键才会增加数字  -->
    点赞数: <input type="text" name="" id="" @keydown.alt.up="num++"  v-model="num">
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            num: 10
        },
        methods: {
        }
    })

3.7 v-for

3.7.1 遍历数组

语法:v-for="item in items"

  • items:要遍历的数组,需要在vue 的data 中定义好。

  • item:迭代得到的当前正在遍历的元素

<div id="d1">
    <ul>
        <li v-for="user in users">
            {{user.name}} - {{user.gender}} - {{user.age}}
        </li>
    </ul>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            users: [
                { name: '柳岩', gender: '女', age: 21 },
                { name: '张三', gender: '男', age: 18 },
                { name: '范冰冰', gender: '女', age: 24 },
                { name: '刘亦菲', gender: '女', age: 18 },
                { name: '古力娜扎', gender: '女', age: 25 }
            ]
        }
    })
</script>
3.7.2 数组角标

语法:v-for="(item,index) in items"

  • items:要迭代的数组

  • item:迭代得到的数组元素别名

  • index:迭代到的当前元素索引,从0 开始。

<div id="d1">
    <ul>
        <li v-for="(user,index) in users">
           索引:{{index}}    {{user.name}} - {{user.gender}} - {{user.age}}
        </li>
    </ul>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            users: [
                { name: '柳岩', gender: '女', age: 21 },
                { name: '张三', gender: '男', age: 18 },
                { name: '范冰冰', gender: '女', age: 24 },
                { name: '刘亦菲', gender: '女', age: 18 },
                { name: '古力娜扎', gender: '女', age: 25 }
            ]
        }
    })
</script>
3.7.3 遍历对象

v-for 除了可以迭代数组,也可以迭代对象。

语法基本类似:

v-for="value in object"

  • value为属性值

v-for="(value,key) in object"

  • value属性值,key属性名

v-for="(value,key,index) in object"

  • value属性值,key属性名,index为索引(从0开始)
<div id="d1">
    <ul>
        <!--索引:0 张三 - name
            索引:1 男 - gender
            索引:2 21 - age  -->
        <li v-for="(value,key,index) in user">
           索引:{{index}} {{value}} - {{key}}
        </li>
    </ul>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            user: { name: '张三', gender: '男', age: 21 }
        }
    })
</script>
3.7.4 key

用来标识每一个元素的唯一特征,这样Vue 可以使用就地复用策略有效的提高渲染的效率

如果items 是数组,可以使用index 作为每个元素的唯一标识

如果items 是对象数组,可以使用item.id 作为每个元素的唯一标识

  <ul>
    <li v-for="(item,index) in items" :key="index"></li>
  </ul>

  <ul>
  	<li v-for="item in items" :key="item.id"></li>
  </ul>

3.8 v-if和v-show

3.8.1 基本用法

真值和假值false0""nullundefinedNaN 以外皆为真值

v-if 条件判断。当得到结果为true 时,所在的元素才会被渲染,结果为false会将元素从dom中移除

v-show 当得到结果为true 时,所在的元素才会被显示,结果为false会将元素隐藏style="display: none;"

语法:v-if="布尔表达式", v-show="布尔表达式"

<div id="d1">
    <button v-on:click="show = !show">点我呀</button>
    <br>
    <h1 v-if="show">
        看到我啦?!
    </h1>
    <h1 v-show="show">
        看到我啦?!show
    </h1>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            show: false
        }
    })
</script>
3.8.2 与v-for结合

当v-if 和v-for 出现在一起时,v-for 优先级更高。也就是说,会先遍历,再判断条件。

3.8.3 v-if和v-show比较

v-if真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

3.9 v-else和v-else-if

语法:v-else-if="布尔表达式"

v-else 元素必须紧跟在带v-if 或者v-else-if 的元素的后面,否则它将不会被识别。

4.计算属性和侦听器

4.1 计算属性(computed)

计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算,即只要返回的计算结果发生变化,就会重新计算

计算属性普通方法的区别:

**相同点:**实现功能效果相同

不同点:

  • 计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
  • 计算方法每当触发重新渲染时,调用方法将总会再次执行函数。

⚠️ 在computed中已经声明的属性total方法不能data中再次声明属性total

<template>
  <div>
    total:{{ total }}
    <button @click="changeObject">改变数字</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      num1: 1,
      num2: 3,
    }
  },
  computed:{
    total() {
      return this.num1+this.num2
    }
  },
  methods: {
    changeObject() {
      //当num1或者num2发生变化就会重新计算total。
      this.num1=5
    }
  }
}
</script>

<style scoped>

</style>
get/set 双向修改
var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
})
vm.aPlus   // => 2
vm.aPlus = 3
vm.a       // => 2
vm.aDouble // => 4

4.2 侦听(watch)

watch 可以监控属性值的的变化

只能监听data/props中的属性

两种使用方式:

  • 在watch方法中声明的语法

    export default {
      name: "Hello",
      data() {
        return{
        }
      },
      watch:{
        要监听的属性(newVal,oldVal){
    
        },
       '要监听的属性.内部属性':{
          handler(newVal,oldVal){
          },
          deep:true, //可选,监听对象内部值变化,所有属性(包括多级对象中的属性)
          immediate:true  //可选,立即以表达式的当前值触发回调
        },
    }
    
  • this.$watch('要监听的属性名',function(newVal,oldVal){},{deep: true,immediate:true})

实例:

<template>
  <div>
    student:{{ student }}
    <button @click="changeObject">改变对象</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      student: {
        name: '张三',
        age: '20',
        teacher:{
          t_name:"李老师",
          t_age:40
        },
        gender: '男',
        likes:['boll','play']
      }
    }
  },
  watch: {
    //监听student对象中的name属性
    'student.name': {
      handler(newVal, oldVal) {
        console.log("student.age改变了:", newVal)
      }
    },
    //监听student对象中的likes属性
    'student.likes': {
      handler(newVal, oldVal) {
        console.log("student.likes改变了:", newVal)
      }
    },
    //监听student对象中的teacher.t_name属性
    'student.teacher.t_name':{
      handler(newVal, oldVal) {
        console.log("student.teacher.t_name改变了:", newVal)
      },
    },
    //监听student,student对象中的属性变化不会执行此方法,只有当此对象的引用改变时才会执行此方法。
    //若其它属性有监听方法,那么变化的属性会执行自己的监听方法
    // student(newVal, oldVal) {
    //   console.log("student改变了:", newVal)
    // },

    //deep:true 开启监听student中所有的对象属性,只要任意属性发生变化则会执行此方法
    //immediate:true 立即执行一次该监听方法。默认只要属性变化时才会执行此方法
    student: {
      handler(newVal, oldVal){
        console.log("student改变了:", newVal)
      },
      // deep:true,
      // immediate:true
    },
  },
  methods: {
    changeObject() {
      //执行'student.name'方法
      this.student.name = '李四'

      //执行'student.likes'方法
      this.student.likes.push('game')
      //不会执行任何方法,通过索引改变数组为非响应式的
      this.student.likes[0]='game'

      //执行student方法,引用指向改变
      // this.student={
      //   name: '张三',
      //   age: '20',
      //   gender: '男'
      // }

      //执行'student.teacher.t_name'方法
      this.student.teacher.t_name='张老师'

      this.$watch('student.teacher.t_age',function (newVal, oldVal){
        console.log("t_age变化了",newVal)
      },{deep: true,immediate:true})
    },
  }
}
</script>

4.3 过滤器(filters)

过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本。在很多不同的情况下,过滤器都是有用的,比如尽可能保持API 响应的干净,并在前端处理数据的格式。

过滤器常用来处理文本格式化的操作。过滤器可以用在两个地方双花括号插值v-bind表达式

注意点以及其它用法:

  • | 管道符号:表示使用后面的过滤器处理前面的数据
  • 当全局过滤器和局部过滤器重名时,会采用局部过滤器。
  • 串联过滤器:{{ message | filterA | filterB }}
    • 在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。
  • 过滤器是 JavaScript 函数,因此可以接收参数:{{ message | filterA('arg1', arg2) }}
    • 这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。

局部过滤器:

注册在当前vue 实例中,只有当前实例能用

<template>
  <div>
    <table>
      <tr v-for="user in userList">
        <td v-bind:id="user.id|idFilter">{{user.id}}</td>
        <td>{{user.name}}</td>
        <td>{{user.gender|genderFilter}}</td>
      </tr>
    </table>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      userList: [
        { id: 1, name: 'jacky', gender: 1 },
        { id: 2, name: 'peter', gender: 0 }
      ]
    }
  },
  filters: {
    genderFilter(gender) {
      return gender === 1 ? '男~' : '女~'
    },
    idFilter(id) {
      return id + '~'
    }
  }
}
</script>

全局过滤器:

在创建Vue 实例之前全局定义过滤器, 任何vue 实例都可以使

<td>{{user.name | capitalize}}</td>
<script>
  Vue.filter('capitalize', function (value) {
    return value.charAt(0).toUpperCase() + value.slice(1)
  })
</script>

5.深入理解组件

在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。

但是如果每个页面都独自开发,这无疑增加了我们开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。

在vue 里,所有的vue 实例都是组件

5.1 组件命名规范

定义组件名的方式有两种:

使用 kebab-case:

Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

使用PascalCase

Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name><MyComponentName> 都是可接受的。

注意,尽管如此,直接在 DOM (即非字符串的模板) 中,因为HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。即页面标签中使用时只有 kebab-case 是有效的。

即若有大小写的组件,则使用kebab-case命名方式比较稳妥

5.2 全局/局部组件

全局组件:

  • 组件其实也是一个Vue 实例,因此它在定义时也会接收:datamethods生命周期函数

  • 不同的是组件不会与页面的元素绑定,否则就无法复用了,因此没有el 属性。

  • 但是组件渲染需要html 模板,所以增加了template 属性,值就是HTML 模板

  • 全局组件定义完毕,任何vue 实例都可以直接在HTML 中通过组件名称来使用组件了

  • data 必须是一个函数,不再是一个对象,因此每个实例可以维护一份被返回对象的独立的拷贝

<div id="d1">
    <!--使用定义好的全局组件-->
    <counter></counter>
  	 <!--可以复用多次,多个组件数据相互独立-->
    <counter></counter>
    <counter></counter>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component("counter", {
        template: '<button v-on:click="count++">你点了我{{ count }} 次,我记住了.</button>',
        data() {
            return {
                //为每个组件初始化值
                count: 0
            }
        }
    })
    let vm = new Vue({
        el: "#d1"
    })
</script>

局部组件:

一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着Vue 的加载而加载。因此,对于一些并不频繁使用的组件,我们会采用局部注册

  • components 就是当前vue 对象子组件集合。
    • 其key 就是子组件名称
    • 其值 就是组件对象名

效果与刚才的全局注册是类似的,不同的是,这个counter 组件只能在当前的Vue 实例中使用

<div id="d1">
    <!--使用定义好的全局组件-->
    <counter></counter>
    <counter></counter>
    <counter></counter>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    const counter = {
        template: '<button v-on:click="count++">你点了我{{ count }} 次,我记住了~~~.</button>',
        data() {
            return {
                count: 0
            }
        }
    }
    let vm = new Vue({
        el: "#d1",
        components:{
            // 将定义的对象注册为组件
            // counter:counter

            //如果组件名和变量名一样,那么可以简写
            counter
        }
    })
</script>

5.3 父子组件传值prop

https://cn.vuejs.org/v2/guide/components-props.html#Prop-类型open in new window

5.3.1 Prop大小写

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

重申一次,如果你使用字符串模板,那么这个限制就不存在了。

5.3.2 父子组件传递参数示例
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
  v-bind:author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
5.3.3 Prop类型和验证

我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。

type 可以是下列原生构造函数中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 datacomputed 等) 在 defaultvalidator 函数中是不可用的。

5.3.4 单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

  • **这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。**在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:

    props: ['initialCounter'],
    data: function () {
      return {
        counter: this.initialCounter
      }
    }
    
  • **这个 prop 以一种原始的值传入且需要进行转换。**在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

5.3.5 案例

父组件:

<template>
  <div>
   <el-button @click="openDialog">打开弹窗</el-button>
    <addEvents
        :queryType="queryType"
        :visible="isVisible"
        :likes="likes"
        :comment-ids="ids"
        :author="author"
        @closeDialog="addCloseDialog"
        />
  </div>
</template>
<script>
import addEvents from '@/components/addEvents'
export default {
  name: "studentList",
  components: {addEvents},
  data() {
    return {
      //字符串类型
      queryType: 'hello',
      //布尔类型
      isVisible: false,
      //int类型
      likes:20,
      //数组类型
      ids:[234, 266, 273],
      //对象类型
      author:{
        name: 'Veronica',
        company: 'Veridian Dynamics'
      }
    }
  },
  methods:{
    addCloseDialog(flag) {
       this.isVisible = flag;
    },
    openDialog() {
      this.isVisible=true
    }
  }
}
</script>

<style scoped>

</style>

子组件:

<template>
  <div>
    <el-dialog
        :before-close="close"
        title="提示"
        :visible.sync="visible"
        width="30%"
    >
      queryType:{{ queryType }}, <br>
      visible:{{ visible }}, <br>
      likes:{{ likes }}, <br>
      commentIds:{{ commentIds }}, <br>
      author:{{ author }}, <br>
      <span slot="footer" class="dialog-footer">
    <el-button @click="close">取 消</el-button>
  </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "addEvents",
  props: {
    queryType: {
      //默认值
      default: '',
      //限定类型
      type: String,
      //是否必填
      require:true
    },
    visible: '',
    likes: "",
    commentIds: '',
    author: '',
  },
  data() {
    return {
      //作为一个本地的 prop 数据来使用
      sonVisible: this.visible
    }
  },
  methods: {
    //关闭弹窗的回调
    close() {
      //避免直接操作父组件中的值,即props中的值,否则会提示警告
      // this.visible=false

      //通过$emit传入事件处理函数和参数,让父组件来修改props中的值
      //this.$emit('子组件标签中定义的事件名',参数1,参数2...)
      this.$emit('closeDialog',false)
    }
  }

}
</script>

<style scoped>

</style>

5.4 监听子组件事件$emit

5.4.1 事件名大小写

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。

举个例子,如果触发一个 camelCase(驼峰命名) 名字的事件:this.$emit('myEvent')

则监听这个名字的 kebab-case 版本是不会有任何效果的:

<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>

不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此,我们推荐你始终使用 kebab-case(短横线分隔命名) 的事件名

5.4.2 基本使用

当父组件需要对子组件上定义的的一个回调方法进行监听,并由子组件使用$emit方法来通知父组件执行此事件并携带参数。

语法:

  • 父组件中定义的子组件:

    <子组件名 v-on:事件名='执行方法' />

    export default {
      name: "studentList",
      components: {子组件名},
      methods: {
        执行方法(参数1,参数2...) {
        		//其它操作
        }
      }
    }
    

    其中:v-on:'事件名'可以写为:@事件名

  • 子组件通知父组件调用此方法:

    this.$emit('事件名',参数1,参数2...)

5.5 插槽slot

5.5.1 插槽内容/后备内容

当在父组件中使用子组件时,并且要向子组件传入一些标签内容等其它数据

父组件:

  <div>
    <!--子组件-->
    <test-component>
      <!--下面都是传入子组件的内容-->
      Your Profile
      <button>按钮</button>
      <input value="值1">
    </test-component>
  </div>

子组件:

  • 在组件中获取父组件传入的所有内容 <slot></slot>
  • <slot>后备内容</slot>当父组件没有向子组件中定义任何内容时,则会使用后备内容,否则将会替换后备内容
<div>
  子组件
  <slot>后备内容</slot>
</div>

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

  • 即在父组件中不能直接访问子组件中的内容
5.5.2 具名插槽

若想定义多个插槽,并将不同内容渲染到不同到插槽处

父组件:

  • <template> 元素中的所有内容都将会被传入相应的插槽,name为指定的插槽名。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。
  • v-slot 只能添加在 <template>
    <!--子组件-->
    <test-component>
      <!--会将template中的内容渲染到子组件中到名为header的插槽处-->
      <template v-slot:header>
        <h1>Here might be a page title</h1>
      </template>

      <!--若没有被template包裹的内容则会被渲染到子组件中默认插槽处,即没有名字的slot处或者名为default的slot插槽处-->
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>

      <!--会将template中的内容渲染到子组件中到名为footer的插槽处-->
      <template v-slot:footer>
        <p>Here's some contact info</p>
      </template>

       <!--也会渲染到默认插槽处-->
       其它内容
    </test-component>

子组件

  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <!--默认插槽位置-->
      <slot name="default"></slot>
      <!--或者-->
      <!--<slot></slot>-->
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
5.5.3 作用域插槽

有时需要在父组件中,在定义的子组件中使用子组件中定义的数据

当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

  • 若还在子组件标签中定义template则会编译错误
<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
  
  <!--当在子组件标签上定义默认的插槽时,再定义其他template会报错-->
  <!--<template v-slot:other='otherSlot'></template>-->
</current-user>

或者

<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:如下案例

父组件:

    <!--子组件-->
    <test-component>
      <!--默认插槽-->
      <template v-slot:default="slotProps">
      <!--获取子组件中定义的值-->
      {{ slotProps.username }}
      {{ slotProps.userid }}
      </template>
      <!--名为footer的插槽-->
      <template v-slot:footer="otherSlot">
        <!--获取子组件中定义的值-->
        {{ otherSlot.person.personName}}
        {{ otherSlot.person.personAge}}
      </template>
      <!--名为header的插槽-->
      <!--使用es6对象解构-->
      <template v-slot:header="{person}">
        <!--获取子组件中定义的值-->
        {{person.personName}}
        {{person.personAge}}
      </template>
    </test-component>

子组件:

<template>
  <div>
 <span>
  <!-- 默认插槽,绑定username和userid参数在父组件中使用-->
  <slot name="default" v-bind:username="user.name" :userid="user.id">
  </slot>
   <!--名为footer的插槽,绑定person对象在父组件中使用-->
   <slot name="footer" :person="person"></slot>

   <!--名为footer的插槽,绑定person对象在父组件中使用-->
   <slot name="header" :person="person"></slot>
</span>
  </div>
</template>
<script>
export default {
  name: "C",
  data() {
    return {
      user: {
        id: 1,
        name: '张三',
        age: 20
      },
      person:{
        personName:'李老师',
        personAge:40
      }
    }
  }
}
</script>

<style scoped>

</style>

6.生命周期和钩子函数

每个Vue 实例在被创建时都要经过一系列的初始化过程:创建实例,装载模板,渲染模板等等。Vue 为生命周期中的每个状态都设置了钩子函数(监听函数)。每当Vue 实例处于不同

的生命周期时,对应的函数就会被触发调用。

6.1 生命周期

image-20220524214950702image-20220524215011466

6.2 钩子函数

  • beforeCreated:我们在用Vue 时都要进行实例化,因此,该函数就是在Vue 实例化时调用,也可以将他理解为初始化函数比较方便一点,在Vue1.0 时,这个函数的名字就是

init。

  • created:在创建实例之后进行调用。

  • beforeMount:页面加载完成,没有渲染。如:此时页面还是

  • mounted:我们可以将他理解为原生js 中的window.onload=function({.,.}),或许大家也在用jquery,所以也可以理解为jquery 中$(document).ready(function(){….}),他的功能就是:在dom 文档渲染完毕之后将要执行的函数,该函数在Vue1.0 版本中名字为compiled。此时页面中的已被渲染成张三

  • beforeDestroy:该函数将在销毁实例前进行调用。

  • destroyed:改函数将在销毁实例时进行调用。

  • beforeUpdate:组件更新之前。

  • updated:组件更新之后。

<div id="d1">
    <span id="num">{{ num }}</span>
    <button v-on:click="num++">赞!</button>
    <h2>
        {{ name }},非常帅!!!有{{ num }}个人点赞。
    </h2>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#d1",
        data: {
            name: "张三",
            num: 100
        },
        methods: {
            show() {
                return this.name;
            },
            add() {
                this.num++;
            }
        },
        beforeCreate() {
            console.log("=========beforeCreate=============");
            //均为undefined
            console.log("数据模型未加载:" + this.name, this.num);
            //Error in beforeCreate hook: "TypeError: this.show is not a function"
            console.log("方法未加载:" + this.show());
            console.log("html 模板已加载:" +document.getElementById("num"))
            console.log("html 模板未渲染:" + document.getElementById("num").innerText);
        },
        created: function () {
            console.log("=========created=============");
            console.log("数据模型已加载:" + this.name, this.num);
            console.log("方法已加载:" + this.show());
            console.log("html 模板已加载:" + document.getElementById("num"));
            console.log("html 模板未渲染:" + document.getElementById("num").innerText);
        },
        beforeMount() {
            console.log("=========beforeMount=============");
            console.log("html 模板未渲染:" + document.getElementById("num").innerText);
        },
        mounted() {
            console.log("=========mounted=============");
            console.log("html 模板已渲染:" + document.getElementById("num").innerText);
        },
        //每次data数据变化之前就会执行方法
        beforeUpdate() {
            console.log("=========beforeUpdate=============");
            console.log("数据模型已更新:" + this.num);
            console.log("html 模板未更新:" + document.getElementById("num").innerText);
        },
        //每次data数据变化之后就会执行方法
        updated() {
            console.log("=========updated=============");
            console.log("数据模型已更新:" + this.num);
            console.log("html 模板已更新:" + document.getElementById("num").innerText);
        }
    })
</script>

7.Vue模块化开发

7.1 脚手架安装

https://cli.vuejs.org/zh/guide/installation.htmlopen in new window

  • 安装vue/cli脚手架

    npm install -g @vue/cli

    Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -gyarn global remove vue-cli 卸载它。

    Node 版本要求

    Vue CLI 4.x 需要 Node.jsopen in new window v8.9 或更高版本 (推荐 v10 以上)。

  • 检查版本

    vue --version

7.2 项目创建

  • vue create 项目名

    按照提示选择自定义的选项

  • 启动项目

    vue run serve

7.3 插件

7.3.1 使用插件Vue.use

通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成

注册插件后,可以在组件中使用this.组件的中的方法或实例使用

import ElementUI from 'element-ui';
import { Message } from 'element-ui';
//全局修改size属性为small
Vue.use(ElementUI,{size:'small'})
Vue.use(ElementUI)
new Vue({
  //引用store 存储菜单
  store,
  router,
  render: h => h(App)
}).$mount('#app')

awesome-vueopen in new window 集合了大量由社区贡献的插件和库。

7.3.2 开发插件

在js使用export定义导出的方法,经Vue.prototype注册后可以直接在组件中使用

  • 定义方法
export const getRequest = function (url, params) {
    console.log('this is get request url:', url, 'params:', params);
};
export function postRequest(url, params){
    console.log('this is post request url:', url, 'params:', params);
};
export const keyValueRequest = (url, params) => {
    console.log('this is keyValue request url:', url, 'params:', params);
};
  • main.js中注册全局方法
import Vue from 'vue'
import App from './App.vue'

import router from './router/index.js'

import {getRequest, postRequest, keyValueRequest} from './utils/api'

//设置为 false 以阻止 vue 在启动时生成生产提示。
Vue.config.productionTip = false

Vue.prototype.getRequest=getRequest
Vue.prototype.postRequest=postRequest
Vue.prototype.$keyValueRequest=keyValueRequest
Vue.prototype.$deleteRequest = function (url, params) {
  console.log('this is delete request url:', url, 'params:', params);
};

new Vue({
  render: h => h(App),
  //导入router
  router
}).$mount('#app')

  • 组件中使用
<script>
export default {
  name: "Hello",
  data() {
    return{

    }
  },
  methods:{
    sendGetRequest() {
        this.getRequest("http://www.baidu.com",{name:"张三"})
    },
    sendKeyValueRequest() {
      this.$keyValueRequest("http://www.ailiyun.com",10)
    },
    sendDeleteRequest() {
      this.$deleteRequest("http://www.tencent.com","李四")
    }
  }
}
</script>

7.4 export/import

模块化就是把代码进行拆分,方便重复利用。类似java 中的导包:要使用一个包,必须先导包。而JS 中没有包的概念,换来的是模块。

模块功能主要由两个命令构成:export/export defaultimport

export default:

  • 只允许向外暴露1次

  • 使用import导入时,可以另取别名,不能使用{}来接收

    //无需声明导出的变量名
    export default {
        sum(a, b) {
            return a + b;
        }
    }
    //或者
    export default sum
    
    //自定义变量名导入
    import defineUtil from 'hello.js'
    // 调用util 中的属性
    defineUtil.sum(1,2)
    
  • export defaultexport不能同时使用

export:

  • export 可以使用多次
  • 只能使用 { } 的形式来接收
  • export 可以向外暴露多个成员, 同时,如果某些成员,我们在 import 的时候,不需要,则可以 不在 {} 中定义
  • 使用 export 导出的成员,必须严格按照导出时候的名称,来使用{}按需接收;
  • 使用 export 导出的成员,如果就想换个名称来接收,可以使用 as 来起别名;
  • export不仅可以导出对象,一切JS 变量都可以导出。比如:基本类型变量、函数、数组、对象
const util = {
    sum(a, b) {
        return a + b;
    }
}
var name='tom'
var age=12
export var title = '呵呵呵'
export var content = '哈哈哈'
//导出多个变量
export {util,name,age}
import  { title as title123, content } from 'xxxx.js'

7.5 响应式原理Vue.set/$set

数据响应式原理:

​ 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.definePropertyopen in new window 把这些 property 全部转为 getter/setteropen in new window

​ 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更

​ 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

用法:

  • vm.$set即在组件中this.$set()方法是全局方法Vue.set的一个别名
  • Vue.set('更新的对象/数组','更新的对象属性/索引位置','更新的值')

对于对象

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

例如:

  • 对象中已存在的属性对其操作是响应式
  • 向对象中添加属性或者删除属性非响应式

解决方式:

  • this.$set(this.hrs,'gender','男')
export default {
  name: "Home",
  data() {
    return {
      hrs:{
        name:'超管',
        age:22,
        city:"武汉"
      }
    }
  },
  methods:{
    changeIds() {
    
    },
    //执行此方法
    changeObject() {
      //age是已存在的属性,是响应式的。
      this.hrs.age=20
      //添加gender属性,非响应式
      this.hrs.gender='男'
    }
  }

}

对于数组

Vue 不能检测以下数组的变动:

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你修改数组的长度时,例如:vm.items.length = newLength

解决方式:

  • 使用push()、pop()、splice()等方法操作数组是响应式的
  • this.$set(this.ids,0,100)
export default {
  name: "Home",
  data() {
    return {
      ids: [1,2,3,4,5],
    }
  },
  methods:{
    changeIds() {
      //通过索引修改数组值,非响应式
      this.ids[0]=100
      //修改数组长度,非响应式
      this.ids.length=10
    }
  }
}
7.5.1 异步更新队列$nextTick

https://cn.vuejs.org/v2/guide/reactivity.htmlopen in new window

使用原理:

  • vue是异步执行dom更新的,一旦观察到数据变化,vue就会开启一个队列,然后把在同一事件循环当中观察到数据变化的watcher推送进这个队列,如果这个watcher被触发多次,只会被推送到队列

    一次,这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和dom操作,这样可以提高渲染效率。

  • 如果要获取更新后的dom元素,可以使用vue内置的$nextTick方法,参数是一个函数。它的作用类似setTimeout,进行执行异步的操作。

应用

  • vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick可以获取数据更新后最新dom的变化。

场景:

  • 第三方插件,在vue生成的某些dom动态发生变化时重新应用该插件。
  • 视图更新之后,基于新的视图进行操作
<template>
  <div>
    <div ref="msgDiv">{{msg}}</div>
    <div>Message got outside $nextTick: {{msg1}}</div>
    <div>Message got inside $nextTick: {{msg2}}</div>
    <div>Message got outside $nextTick: {{msg3}}</div>
    <button @click="changeMsg">
      Change the Message
    </button>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      msg: 'Hello Vue.',
      msg1: '',
      msg2: '',
      msg3: ''
    }
  },
  methods:{
    changeMsg() {
      this.msg = "Hello world."
      //获取DOM中的msg   此时this.msg还没有更新到DOM中
      this.msg1 = this.$refs.msgDiv.innerHTML //Hello Vue
      this.$nextTick(() => {
        //当this.msg渲染到DOM中后,再获取Dom中的this.msg
        this.msg2 = this.$refs.msgDiv.innerHTML  //Hello world
      })
      //获取DOM中的msg  此时this.msg还没有更新到DOM中
      this.msg3 = this.$refs.msgDiv.innerHTML  // Hello Vue
    }
  }
}
</script>

<style scoped>

</style>

实例:

  • 点击按钮后,显示输入框并且自动聚焦到输入框
<template>
  <div>
    <button @click="showInput">显示Input</button>
    <input type="text" v-if="isShow" ref="inputText">
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      isShow:false
    }
  },
  methods:{
    showInput() {
      this.isShow=true
      //若直接以这种方式写,则会报错,因为此时Dom的input输入框还未渲染
      // this.$refs.inputText.focus()
      this.$nextTick(()=>{
        //显示input输入框后,自动聚焦到输入框
        this.$refs.inputText.focus()
      })
    }
  }
}
</script>

<style scoped>

</style>

7.6 获取子组件或者元素实例ref/$refs

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

$refs 只会在组件渲染完成之后生效,并且它们不是响应式的

<template>
  <div>
    <button @click="changeRef">获取ref</button>
    <input type="text" ref="r_input" value="11111">
    <testComponent ref="c_component" id="c1"></testComponent>
  </div>
</template>
<script>
import testComponent from "@/components/C";
export default {
  name: "Home",
  components:{testComponent},
  data() {
    return {
    }
  },
  methods:{
    changeRef() {
      //获取子组件data中定义的name属性值
      console.log(this.$refs.c_component.$data.name);
      //获取input的的value值
      console.log(this.$refs.r_input.value)
      //聚焦input框
      this.$refs.r_input.focus()
    }
  }

}
</script>

8.Vue-Router的使用

https://router.vuejs.org/zh/guide/#htmlopen in new window

8.1 安装并使用

  • 添加vue-router

    npm install vue-router@3.0.0

  • 新建index.js路由文件,并添加路由规则

    import Vue from 'vue'
    import Router from "vue-router";
    //新建并导入页面组件
    import hello from "@/components/Hello";
    
    Vue.use(Router)
    
    export default new Router({
        routes:[
            {
                path:'/hello',
                name:"Hello",
                component:hello
            }
        ]
    })
    
  • main.js文件中引入路由文件

    import Vue from 'vue'
    import App from './App.vue'
    
    import router from './router/index.js'
    
    //设置为 false 以阻止 vue 在启动时生成生产提示。
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      //导入router
      router
    }).$mount('#app')
    
    
  • App.vue文件中添加路由出口

    <template>
      <div id="app">
    		<!-- 路由匹配到的组件将渲染在这里 -->
        <router-view/>
      </div>
    </template>
    <style>
    </style>
    

8.2 vue-router实现原理

SPA(single page application):单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。

单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式History模式;根据mode参数来决定采用哪一种方式。

8.2.1 Hash模式

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说hash 出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面

同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;

所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。hash 模式的原理是 onhashchange 事件(监测hash值变化),可以在 window 对象上监听这个事件

export default new Router({
    routes:[
        {
            path:'/',
            name:"Hello",
            component:hello
        },
        {
            path:'/hello',
            name:"Hello",
            component:hello
        }
    ]
})
8.2.2 History模式

由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用了html5 history interface 中新增的 pushState() 和 replaceState() 方法。

这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求

//main.js文件中
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

示例:

export default new Router({
    mode: 'history',
    routes:[
        {
            path:'/',
            name:"Hello",
            component:hello
        },
        {
            path:'/hello',
            name:"Hello",
            component:hello
        }
    ]
})
8.2.3 错误路由重定向

当访问路径匹配不到任何路由时,使用redirect重定向到已存在的页面或者跳转到指定组件

export default new Router({
    routes: [
        {
            path: '/hello',
            name: "Hello",
            component: hello
        },
        {
            path: '/helloworld',
            name: "HelloWorld",
            component: helloWorld
        },
        {
            path: "*",
          
          	//重定向到指定路由
            //redirect: '/hello'
          
          	//或者直接访问到某个组件
          	component: hello
        }
    ]
})
  • *上面的路由都没有匹配成功时,就会重定向到/hello路由
8.2.4 路由跳转方式
  • 方式1:直接修改地址栏
  • 方式2:this.$router.push('/home') this.$router.replae('/home')
  • 方式3:<router-link to="/home"></router-link>

注意:

  • ⚠️ 在router中,无论子路由还是父路由 /始终代表根路径且寻找路由的的时候,会从上依次查找路由,以首先匹配到的路由为准
  • 若匹配的路由不是该组件下的子路由则会重新渲染一个页面
  • 若匹配的路由为该路由下的子路由,则必须使用<router-view></router-view>来作为子路由的渲染位置

路由出口:<router-view></router-view>

  • 会将当前组件下的子路由渲染在此处

路由跳转:<router-link to="/hello">Go to Home</router-link>

​ 跳转到挂载在当前组件下的router中的/hello路由

  • <router-link> 将呈现一个带有正确 href 属性的 <a> 标签
8.3.1 父子路由跳转(子路由带/)

路由示例:

        {
            path:'/home',
            name:"Home",
            component:home,
            children:[
                //子路由
                {
                    path:'/a',
                    name:"A",
                    component:a
                },
                {
                    path:'/b',
                    name:"B",
                    component:b
                },
            ],
            //同级路由
            {
            path:'/hello',
            name:"Hello",
            component:hello
        		},
        }
  • 父组件Home
<template>
<div>
   父组件Home
  <!--子路由访问-->
  <router-link to="/a">子目录下的a下</router-link>
  <router-link to="/b">子目录下的的b</router-link>
  <button @click="toA">去a</button>
  <button @click="toB">去b</button>
  
   <!--访问hello组件-->
   <!--重新渲染整个页面-->
  <router-link to="/hello">hello目录</router-link>
  
   <!--子路由渲染位置-->
   <router-view></router-view>
</div>
</template>

<script>

export default {
  name: "Home",
  data() {
    return{
    }
  },
  methods:{
    toA() {
      this.$router.push({path: '/a'})
    },
    toB() {
      this.$router.push({path: '/b'})
    }
  }
}
</script>

<style scoped>

</style>

访问规则:

8.3.2 父子路由跳转(子路由不带/)

路由示例:

        {
            path:'/home',
            name:"Home",
            component:home,
            children:[
                {
                    path:'a',
                    name:"A",
                    component:a
                },
                {
                    path:'b',
                    name:"B",
                    component:b
                },
            ]
        },
        {
            path:'/a',
            name:"A",
            component:a
        },
        {
            path:'/hello',
            name:"Hello",
            component:hello
        }
  • 父组件Home
<div>
   父组件Home
  <!--访问根路径下的a组件-->
  <!--重新渲染并占用整个页面-->
  <router-link to="/a">访问根目录下和Home组件同级路径的组件a</router-link>

  <!--访问Home下的子路由-->
  <router-link to="/home/a">子目录下的的a</router-link>
  <router-link to="/home/b">子目录下的的b</router-link>

   <!--访问根目录下的/hello-->
  <router-link to="/hello">hello目录</router-link>
  
   <!--子路由渲染位置-->
   <router-view></router-view>
</div>

访问规则:

注意:访问Home下的b组件不能使用 /b访问作为路径,因为从上到下,都找不到为/b父路由或者子路由路径

8.4 routeroute和router

$router是用来操作路由的,$route是用来获取路由信息

⚠️需要注册路由组件后,才能可以在任意组件中以 this.$router 的形式访问它,并且以 this.$route 的形式访问当前路由使用

import Router from "vue-router";
Vue.use(Router)
8.4.1 基本属性用法例子

示例:

router.js:

import Vue from 'vue'
import Router from "vue-router";
import home from "@/components/Home";
import a from "@/components/A";
import hello from "@/components/Hello";

Vue.use(Router)

export default new Router({
    routes: [
        {
            path: '/home/:id/:city',
            name: "Home",
            hidden: true,
            component: home,
            meta: {
                "p1": "v1",
                "p2": "v2"
            },
            children: [
                {
                    path: 'a',
                    name: "A",
                    component: a
                }
            ]
        },
        {
            path: '/hello/',
            name: "Hello",
            component: hello
        }
    ]
})

组件Home:

<template>
  <div>
    父组件Home
    <!--访问Home下的子路由-->
    <router-link :to="{name:'A',params:{id:'张三',gender:''}}">子目录下的的a</router-link>

    <!--子路由渲染位置-->
    <router-view></router-view>

  </div>
</template>

<script>

export default {
  name: "Home",
  data() {
    return {}
  },
  created() {
    //{city: 'shanghai'}
    console.log("只输出Url的参数:",this.$route.query);
		//Home
    console.log("路由的name值:",this.$route.name);
    //{id: '12', city: '11'}
    console.log("所有「键值对」/「动态」参数:",this.$route.params);
    //12
    console.log("「键值对」/「动态」参数中的id键值:",this.$route.params.id);
    //  /home/12/11/?city=shanghai
    console.log("完整绝对路径(包括路径参数)",this.$route.fullPath);
    //  /home/12/11/
    console.log('完整绝对路径(不包括路径参数)',this.$route.path);
    // {p1: 'v1', p2: 'v2'}
    console.log("路由中的meta自定义参数值:",this.$route.meta);
		
    console.log("获取当前路由详细信息:",this.$router.currentRoute);
    console.log("获取所有路由信息:",this.$router.options.routes);
    //true
    console.log("获取所有路由信息第一个路由自定义的hidden值:",this.$router.options.routes[0].hidden);
  },

}
</script>

<style scoped>

</style>

A组件:

<template>
<div>A ~~~
</div>
</template>

<script>
export default {
  name: "A",
    //空(路径无值)
    console.log("Url的参数:",this.$route.query);
    //{id: '张三', gender: '男', city: '11'}
    console.log("所有「键值对」/「动态」参数:",this.$route.params);
  }

}
</script>
<style scoped>
</style>
8.4.2 用法总结

this.$route常见用法:

  • $route.params一个 key/value 对象,包含了 动态片段 和 全匹配片段, 如果没有路由参数,就是一个空对象。

  • $route.query一个 key/value 对象,表示 URL 查询参数。 例如,对于路径 /foo?user=1,则有 $route.query.user为1, 如果没有查询参数,则是个空对象。

  • $route.hash 当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。

  • $route.meta 获取路由中的备用参数对象meta,可在meta中自定义属性键值

  • $route.path 完整绝对路径(不包url参数)

  • $route.fullPath完成解析后的 URL,包含查询参数和 hash 的完整路径。

  • $route.matched 数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。

  • $route.name 当前路径名字

this.$router常见用法:

$router 是“路由实例”对象,即使用 new VueRouter创建的实例,包括了路由的跳转方法,钩子函数等。

<router-link :to="...">属性 to$router.push 接受的对象种类相同,所以两者的规则完全相同

  • $router.go(-1) 跳转到上一次浏览的页面

  • $router.go(1) 跳转到下一次浏览的页面

  • $router.options.routes 获取所有路由信息

  • $router.options.routes[0].hidden 获取所有路由信息第一个路由中自定义的hidden值

  • $router.currentRoute 获取当前路由详细信息

  • $router.replace('/menu') 跳转的到/menu路由

  • $router.replace({name:'menuLink'}) 跳转到namemenuLink的路由组件

  • $router.push('/menu') 跳转的到/menu路由

  • $router.push({name:'menuLink',params:{username:'zhangsan'}}) 跳转到namemenuLink的路由组件,并携带params参数

  • $router.push({ path: '/register',query: { plan: 'private' } }) 带查询参数,结果是 /register?plan=private

    注意:params 不能与 path 一起使用

  • router.push({ path: '/about', hash: '#team' }) 带hash,结果是 /about#team

router.pushrouter.push和router.replace区别:

  • 使用push方法的跳转会向 history 栈添加一个新的记录,当我们点击浏览器的返回按钮时可以看到之前的页面。

  • 使用replace方法不会向 history 添加新记录,而是替换掉当前的history记录,即当replace跳转到的网页后,后退按钮不能查看之前的页面

  • 💁‍♂️ 小技巧: router.push({ path: '/home', replace: true }) 相当于 router.replace({ path: '/home' })

8.5 导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。

8.5.1 全局前置守卫

语法:router.beforeEach((to,from,next)=>{})

  • to: 即将要进入的路由对象

  • from: 当前导航正要离开的路由对象

  • next:执行跳转的钩子函数(在整个beforeEach有且必须执行一次next调用,否则无法正常跳转)

    next()$router.push()的传参用法一致

import Vue from 'vue'
import Router from "vue-router";
import home from "@/components/Home";
import a from "@/components/A";
import hello from "@/components/Hello";
Vue.use(Router)
const routes=[
    {
        path: '/home',
        name: "Home",
        component: home,
        children: [
            {
                path: 'a',
                name: "A",
                component: a,
                meta:{
                    //需要验证
                    requireAuth:true
                }
            }
        ]
    },
    {
        path: '/hello',
        name: "Hello",
        component: hello
    }
]
let router=new Router({
    routes
})
//注册全局前置守卫
router.beforeEach((to, from,next)=>{
    if (to.meta.requireAuth == true) {
        //执行验证权限的逻辑...
        //.....
        //根据验证规则指定跳转的路由
        //跳转到/home路由
        // next("/home")
        //或者跳转到 name为Hello的 组件
        next({name:"Hello"})
    }else{
        //不需要验证直接跳转
        next()
    }
})
export default router

8.6 命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口

  • router.js

    一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s)

    ⚠️ 如果router-view 没有设置名字,那么默认为 default

    访问/hello/layout路由,则可以通过router-view指定name从而显示多个视图

    import Vue from 'vue'
    import Router from "vue-router";
    import hello from "@/components/Hello";
    import a from "@/components/A";
    import b from "@/components/B";
    import c from "@/components/C";
    
    Vue.use(Router)
    
    const router = new Router({
        routes:[
            {
                path: '/hello',
                name: "Hello",
                component: hello,
                children:[
                    {
                        path:'layout',
                        components: {
                            a,
                            b,
                            default:c
                        }
                    }
                ]
            }
    
        ]
    })
    export default router
    
  • 组件Hello

    同时显示组件a、b、c、d

    <template>
      <div>
        组件Hello
        <!--当默认没有router-view指定name时,那么会找路由组件的下的名字为default的-->
        <router-view></router-view>
        <!--通过指定name显示组件-->
        <router-view name="a"></router-view>
        <router-view name="b"></router-view>
        <!--直接使用组件的形式显示-->
        <d></d>
      </div>
    </template>
    
    <script>
    import d from "@/components/D";
    export default {
      name: "Hello",
      data() {
        return{
    
        }
      },
      components:{
         d
      }
    }
    </script>
    
    <style scoped>
    
    </style>
    

9.Vuex的使用

9.1 简介

https://v3.vuex.vuejs.org/zh/open in new window

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

这个状态自管理应用包含以下几个部分:

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

以下是一个表示“单向数据流”理念的简单示意:

image-20220605220952182

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。
image-20220605221336798

9.2 安装和使用

  • npm install vuex --save

在一个模块化的打包系统中,您必须显式地通过 Vue.use() 来安装 Vuex:

import Vue from 'vue'
import App from './App.vue'
import store from './store/index.js'

Vue.use(Vuex)

//设置为 false 以阻止 vue 在启动时生成生产提示。
Vue.config.productionTip = false

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

9.3 State

Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT (opens new window)open in new window)”而存在

在组件中可以使用以下几种方式来取出state中的值:

  • {{}}html中使用: $store.state.属性
  • js中使用:this.$store.state.属性
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex)

const store=new Vuex.Store({
    state:{
        count:0,
        name:"张三",
        age:12,
        city:'武汉'
    }

})
export default store
mapState辅助函数

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性

  • vuex导入mapState辅助函数
    • js中使用 this.属性 获取
    • html{{}} 直接使用 属性 获取
<template>
<div>B 组件~~~
   <!--通过在标签中使用 $store.state.属性 获取值-->
   count:{{count}} name:{{nameAlias}} age:{{age}}
</div>
</template>

<script>
import {mapState} from "vuex";
export default {
  name: "B",
  data(){
    return{
        localAge:1
    }
  },
  created() {
  },
  computed:mapState({
    
    //通过箭头函数获取
    count: state => state.count,

    //传字符串参数 'count' 等同于 `state => state.count`
    nameAlias:'name',

    //通过方法获取,并获取本组件的的localAge变量值
    age(state){
      return state.age + this.localAge
    }
  }),

}
</script>

<style scoped>

</style>
对象扩展运算符

什么是对象扩展运算符?

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

let n = { x, y, ...z };
n; // { x: 1, y: 2, a: 3, b: 4 }

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

  • 直接结合对象扩展运算符通过 ...mapState(['属性1',"属性2",'属性3']) 获取属性,并直接使用
<template>
<div>B 组件~~~
   count:{{count}} name:{{name}} age:{{age}}
</div>
</template>

<script>
import {mapState} from "vuex";
export default {
  name: "B",
  data(){
    return{
    }
  },
  created() {
  },
  computed:{
    ...mapState(['name',"age",'count'])
  }

}
</script>

<style scoped>

</style>

9.4 Getter

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

组件中调用方式:

  • 在js中调用 this.$store.getters.方法名()
    • 向getter方法中传参数:this.$store.getters.方法名(参数)
  • {{}}html中调用:$store.getters.方法名()

定义方法:

  • Getter 接受 state 作为其第一个参数

    调用方式:this.$store.getters.countState()

        getters:{
            //调用此方法,给count*2
            countState(state){
              return state.count * 2
            }
        }
    
  • Getter 接受其他getter作为第二个参数

  • 调用方式:this.$store.getters.countState()

    getters: {
      // 调用其它或者本getters中的方法
      doneTodosCount: (state, getters) => {
        return getters.doneTodos.length
      }
    }
    
  • 通过让 getter 返回一个函数,来实现给 getter 传参:

    调用方式:this.$store.getters.countState(3)num传值为3

        getters:{
            countState1(state){
                return function (num){
                    return state.count*num
                }
            },
            //或者
            countState: state => num => {
                return state.count * num
            }
    
        }
    
mapGetters 辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

  • 在js中使用this.方法名(参数) 调用
  • {{}}html直接使用 方法名(参数) 调用
<template>
  <div>A 组件~~~
    countState:{{getCountState}}
  </div>
</template>

<script>
import {mapGetters} from "vuex";
export default {
  name: "A",
  computed: {
    ...mapGetters(['countState']),
    getCountState() {
      //传入参数3
       return this.countState(3)
    }
  }
}
</script>

<style scoped>

</style>

如果你想将一个 getter 属性另取一个名字,使用对象形式:

...mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.countState`
  doneCount: 'countState'
})

9.5 Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方

组件中调用方式:

  • js中调用:this.$store.commit('方法名')
    • 有参数调用:this.$store.commit('方法名',参数)
  • {{}}html中调用:$store.commit('方法名')

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  • 最好提前在你的 store 中初始化好所有所需属性。

  • 当需要在对象上新增属性或者通过索引改变数组某个下标的值/添加数组内容 (若通过数组的pop和push等方法则会自动更新)

  • 使用 Vue.set('要改变的对象/数组', '属性名/索引下标', 改变的值)否则页面不会自动更新

定义方法:

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

Vue.use(Vuex)

const store=new Vuex.Store({
    state:{
        count:0,
        hr:{
            name:'超管',
            age:99
        },
        ids:[1,2,3,4,5]
    },
    mutations:{
      	//不需要传参数的定义
      	addCount1(state){
           //....
        },
        //传一个参数的定义
        addCount(state,param){
            state.count = state.count + param
        },
        reduceAge(state,param){
            state.age = state.age - param;
        },
        addPropertyName(state){
						//给hr对象添加hr属性并赋值
            Vue.set(state.hr,'city','武汉')
        },
        changeArrIndex(state) {
          	//给ids数组的索引坐标为1的修改值为-100
            Vue.set(state.ids,'1',-100)
        }
    }

})
export default store
mapMutations辅助函数
  • vuex中导入mapMutations函数

组件中调用方式:

  • js中调用:this.方法名()
    • 有参数调用:this.方法名(参数)
  • {{}}html中调用:方法名()
import {mapMutations} from "vuex";
export default {
  name: "Hello",
  data() {
    return{

    }
  },
  methods:{
    ...mapMutations(['reduceAge']),
    addAge() {
      this.reduceAge(1)
    },
  }

}
  • 若要为方法取别名:
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
Mutation中的方法必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数

在 Vuex 中,mutation 都是同步事务

store.commit('increment')
// 任何由 "increment" 导致的状态变更都应该在此刻完成。

9.6 Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。

  actions: {
    increment (context) {
        // context.state.属性
        // context.commit('方法名')
        // context.getters.方法名()
        // context.dispatch('方法名')
      	//若有多个module,则获取为根的State和Getters
      	// context.rootState
        // context.rootGetters
      context.commit('increment')
    }

或者通过对象解析异构来获取指定对象:

        asyncReduceAge({state,getters,commit},params) {
            setTimeout(()=>{
                commit('reduceAge',params)
            },1500)
        },

组件中调用方式:

  • js中调用:this.$store.dispatch('方法名')
    • 有参数调用:this.$store.dispatch('方法名',参数)
  • {{}}html中调用:$store.dispatch('方法名')

定义方法:

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

Vue.use(Vuex)

const store=new Vuex.Store({
    state:{
        age:12,
    },
    mutations:{
        reduceAge(state,param){
            state.age = state.age - param;
        }
    },
    actions:{
        asyncReduceAge(context,params) {
            setTimeout(()=>{
                context.commit('reduceAge',params)
            },1500)
        }
    }

})
export default store
mapActions辅助函数

组件中调用方式:

  • js中调用:this.方法名()
    • 有参数调用:this.方法名(参数)
  • {{}}html中调用:方法名()
import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}
结合Promise

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

获取异步方法返回的结果:

store.dispatch('actionA').then(() => {
  // ...
})

9.7 Module

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

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

组件中调用方式:

  • js中调用:this.$store.state.模块名.属性 获取指定模块名state状态中的属性值
  • {{}}html中调用:$store.state.模块名.属性

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

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

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

const moduleB = {
  namespaced: true,
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  },
  //根节点上的,可以直接使用,无需携带名命名空间
  state:{},
  mutations:{},
  getters:{},
  actions:{}
})

9.8 Watch监听

可监听state中的数据变化

  • 直接在实例中注册(全局监听)

    store实例.watch(function(state,getter){
      return state.要监听的属性
    },function(newValue,oldValue){
       console.log("newValue:",newValue,"oldValue:",oldValue)
    },{
       //更深层次的属性监听
        deep: true,
        //是否立即执行
        immediate:true
    })
    
  • 直接在组件中使用

    this.$watch('要监听的属性名(需要使用store引用该属性)',function(newVal,oldVal){},{deep: true,immediate:true})

10.axios的使用

详细请参考:

http://www.axios-js.com/zh-cn/docs/index.html#拦截器open in new window

基本使用案例:

import axios from 'axios'
// 单独导入messgae
import { Message } from 'element-ui';
import router from '../router'
// 使用axios的响应拦截器
axios.interceptors.response.use(success => {
    // 在发送响应之前应该做什么
    // 一般服务器处理的成功结果 可能是业务上的错误 则也可返回成功

    // 服务器请求值不为空  服务器请求状态为200   服务器的请求自定义数据中的状态为500
    if(success.status && success.status==200 && success.data.status==500){
        // console.log("---"+success.data.message)
        // 显示服务端返回的自定义msg消息
        Message.error({message:success.data.message})
        // 请求退出 什么也不返回
        return;
    }

    //其他情况 自定义服务器返回200  注销

    //若有值
    if(success.data.message){
        Message.success({message:success.data.message})
    }

    // 否则返回指定数据
    return success.data;
},error => {

    // 在响应错误信息之后该做什么
    // 一般是服务器上的错误404,500...
    if(error.response.status==504||error.response.status==404){
        Message.error({message:"服务器被炸了!"})
    }else if(error.response.status==403){
        Message.error({message:"权限不足,请联系管理员!"})
    }else if(error.response.status==401){
        //未登录的处理
        Message.error({message:"尚未登录,请登录!"})
        router.replace("/");
    }else{
        // 为其他错误
        // 如果数据中的错误信息有值
        if(error.response.data.msg){
            Message.error({message:error.response.data.message})
        }else{
            Message.error({message:'未知错误!'})
        }

    }
    // 也不返回任何信息
    return;
});

// 方便修改前缀
let base='';

// 键值对的 post 请求 需要转换
// 登录请求 springsecurity在这里只支持key value的形式传参。不支持json
export const postKeyValueRequest=(url,params)=>{
    return axios({
        method:"post",
        // 使用模板字符串 变量的使用需要在其中加 ${}
        url:`${base}${url}`,
        data:params,
        // 转换请求
        transformRequest:[
            function(data){
                let ret='';
                // 遍历键值对
                // i为键 dat[i]为值
                for(let i in data){
                    // 字符串作为 URI 组件进行编码。
                    ret+=encodeURIComponent(i)+'='+encodeURIComponent(data[i])+'&'
                }
                // console.log("---"+ret);
                return ret;
            }
        ],
        headers:{
            'Content-Type':"application/x-www-form-urlencoded"
        }
    })
}

// json形式的post请求
export const postRequest=(url,params)=>{
    return axios({
        method:"post",
        url:`${base}${url}`,
        data:params
    })
}

// key-value形式的get请求
export const getRequest=(url,params)=>{
    return axios({
        method:"get",
        url:`${base}${url}`,
        params:params
    })
}

// json形式的delete请求
export const deleteRequest=(url,params)=>{
    return axios({
        method:"delete",
        url:`${base}${url}`,
        data:params
    })
}

// json形式的put请求
export const putRequest=(url,params)=>{
    return axios({
        method:"put",
        url:`${base}${url}`,
        data:params
    })
}

11.npm的使用

11.1 镜像源

  • 查看npm版本

    npm -v

  • 查看镜像源

    npm config get registry

  • 令行永久更改使用指定镜像

    npm config set registry https://registry.npm.taobao.org

11.2 安装包和卸载包

  • 本地安装指定包项目中

    npm install/i「包名」

    没有指定版本,则默认安装最新的版本包。从npm 5.x开始,就把依赖包添加到dependencies中去。

    npm install/i 包名@版本 安装指定版本包

    npm install 把package.json的依赖重新下载,下载完之后在node_modules中

  • 全局安装包,不会安装到此项目中

    npm install -g「包名」

    全局安装一般只适用于工具模块,比如 eslint gulp

    • 要用到该包的命令执行任务的就需要全局安装
    • 要通过require引入使用的就需要本地安装-项目包
  • 以版本兼容的方式安装依赖

    npm install --force

  • 卸载指定包

    npm uninstall「包名」

  • 查看全局安装的所有包

    npm list -g

  • 以树形结构查看当前项目下安装的所有包,以及它们依赖的模块

    npm list

  • 以树形结构查看某个包的版本,以及它们依赖的模块

    npm list「包名」

11.3 packjson.json

  • 初始化packjson.json文件

    npm init --yes 直接同意依次 name, version, description 等字段的询问信息

11.3.1 dependenices与devDependenices

dependenices中的包会发布到生产环境中

  • npm install/i 「包名」 -S/--save 将依赖包加在dependenices

devDependenices中的包不会发布到生产环境中,只在开发环境中有效,用户使用这些包时即使不安装这些依赖也可以正常运行。

这些依赖照样会在你本地进行 npm install 时被安装和管理,但是不会被安装到生产环境

  • npm install/i 「包名」 -D/--save-dev 将依赖包加在devDependenices
11.3.2 npm script

在生成的 package.json 文件中,有一个 scripts 对象,在这个对象中,npm 允许使用 scripts 字段定义脚本命令。

  • 当前项目的所有 npm 脚本命令

    npm run

原理:npm run 新建的 shell,会在当前目录的 node_modules/.bin 子目录加入到 PATH 变量,执行结束后,再将 PATH 变量恢复原样。也就是说,当前项目目录 node——modules/.bin 子目录中所有的脚本,都可以直接用脚本名称调用,不需要增加路径.(简单总结:通过 npm 启动的脚本,会默认把 node_modules/.bin 加到 PATH 环境变量中。)

例如:

"test": "mocha test"

而不用写成:

"test": "./node_modules/.bin/mocha test"

常见问题

  • Error: PostCSS received undefined instead of CSS string

    卸载当前版本,安装最新版本的node-sass

    npm uninstall node-sass

    npm install node-sass --save-dev

12.nvm使用

nvm是一个node的版本管理工具,可以简单操作node版本的切换、安装、查看等等,与npm不同的是,npm是依赖包的管理工具。

常用命令:

  • 查看本机已经安装的node版本

    nvm ls/list

  • 查看当前node版本

    nvm current

    node -v

  • 卸载指定node版本

    nvm uninstall v4.6.2

  • 安装指定node版本

    nvm install v4.6.2

  • 切换使用node版本(临时)

    nvm use v4.6.2

  • 设置系统默认的node版本

    nvm alias default v16.15.1

常见问题

  • 使用WebStorm指定node启动环境:

    image-20220607005916871