Vue2

初识Vue2.0

  1. 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
  2. vue容器里的代码依然符合html规范,其中混入一些vue语法例如mustache语法的双花括号,mustache语法里面要写js表达式,且里面会自动读取到data中的所有属性。
  3. vue容器里的代码被称为Vue模板
<div id="app"></div>
<script>
    new Vue({
        el:'#app',
        data:{name:'Ming'}
    })
</script>

new Vue时传入一个配置对象,值有比如:el用于指定当前vue实例;data用于储存数据
Vue实例和容器是一一对应的,真是开发中只有一个Vue实例,并且会配合着组件一起使用

Vue模板语法

vue模板语法有两大类。

  1. 插值语法(mustache语法):写法 {{xxx}},xxx里写的是js表达式,且可以直接督导data中的所有属性。
  2. 指令语法:写法例如 v-bind:href="xxx",可以简写成 :href="xxx",xxx里写的也是js表达式,可以直接读取到data中的所有属性。
  • 插值语法通常用于标签体内容(#text的内容),指令语法可用于标签属性,标签体内容,绑定事件等

数据绑定

  1. 单向数据绑定,例如v-bind绑定表单input的value值 ,单向指data中的值绑定到视图中,但视图中修改不会影响到data
<input :value="name">
  1. 双向数据绑定,v-model,通常用于输入类元素上例如表单的input,通常用于绑定input的value值。v-model默认收集的就是value值,所以v-model:value 可以简写为 v-model,新版vue已不支持v-model:value写法,直接写v-model即可
<input v-model:value="name">

绑定属性修饰符

v-bind 还可以用于传值,此外给组件传值还有一个 sync 属性,例如 :money.sync="money" (冒号后面的跟引号里面的一定要一样),代表父组件给子组件传递 props:['money'],并且给子组件绑定一个自定义事件名叫(update:money),事件的回调是子组件 $emit('update:qian',huidiao) 里的第二个参数huidiao。

<zujian :money.sync="money"/>
data(){
    return{ money:10000 }
}
//子组件内:
$emit('update:money',money-100)
props:['money']

利用属性修饰符sync可以实现子组件修改父组件传过来的props(修改props)

el与data的两种写法

el的两种写法

  1. new Vue时候配置el属性。
  2. 先创建Vue实例vm,随后再通过vm.$mount('#root')指定el的值。
    vue3里已不支持第一种写法,用的是第二种的改进版

data的两种写法

  1. 对象式
  2. 函数式
    学习阶段可以用第一种写法,但只要涉及到组件,data就必须使用函数式。

重要原则

由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this指向就不再是Vue实例

MVVM模型

model-view-viewmodel

  • vue2文档中说因为设计时受mvvm启发,所以vue实例通常用vm来命名。但vue3已移除这句话,用
    Counter。
  • vm:
const vm = new Vue()

data中的所有属性,最后都会出现在了vm身上,vm身上所有属性以及vue原型上的所有属性,再vue模板中都可以直接使用

Vue数据代理

回顾Object.defineProperty

参数一:给谁添加属性
参数二:添加属性的属性名
参数三:配置项,是一个对象,其中包括value。
此方法定义的属性默认是不可枚举/遍历的,可在配置项里修改enumerable:true开启遍历。
定于的属性默认是不可以被修改的,可在配置项修改 writable:true 开启修改
定义的属性默认是不可以被删除的,可在配置项修改 configurable:true 开启删除。
配置项get函数(getter)当有人读取参数二的属性时,getter就会被调用,且返回值就是这个属性的值
配置项set函数(setter)当有人修改参数二的属性时,setter就会被调用,且会收到给setter传参的值

let person = {
    name:'Ming',
    sex:'male'
}
let number = 18
Object.defineProperty(person,'age',{
    get(){return number}
    set(value){number = value//此时修改person的age就会修改number}
})

数据代理

通过一个对象 代理 对另一个对象中的属性的操作

vue中的数据代理

vm._data 里面的内容,就是配置对象里面的data,只不过内部做了一些数据劫持,使之可以实现响应式更新视图。内部的传值用的就是Object.defineProperty这个api

Vue事件处理

给元素绑定事件,用 v-on:***="***" 指令,或者简写形式 @***="***" 例如

<button v-on:click="show"></button>

然后再vue实例里面的 methods 里面写上方法就可以了,最终会在vm身上,想要获取事件对象只需要在写函数的时候传入一个参数

const Counter = {
    methods:{
        show(event){
            console.log(event)
        }
    }
}
Vue.createApp(Counter).mount('#app')

如果在给元素绑定事件的时候想要传入一个参数怎么办?想要传入一个参数并且获得事件对象,就需要一个$event做占位符,$event占位符和传的参数的位置没有要求,只需要在写函数的时候对应拿取就行

<button @click="show($event,88)"></button>
<script>
    const Counter = {
        methods:{
            show(event,value){
                console.log(event)
                console.log(value)
            }
        }
    }
    Vue.createApp(Counter).mount('#app')
</script>

基本使用注意点:

  1. 使用 v-on:xxx 或 @xxx 可以绑定事件,其中xxx是事件名
  2. 事件的回调需要配置在methods对象中,vue会处理其绑到vm上
  3. methods中配置的函数,不要用箭头函数,否则this指向的就不是vm
  4. methos中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象
  5. @click="demo" 和 @click="demo($event)" 的效果一致,但后者可以传参

事件修饰符

<a href="http://www.baidu.com" @click="show">点我跳转</a>

像如上代码,如果想要a标签点击触发show事件,但不想进行a标签默认的跳转行为。可以使用 e.preventDeafault() 来阻止。
但是,vue里面有事件修饰符,用于在事件触发时候添加一些其他动作。
例如 prevent 修饰符,用法写在v-on指令后面用点接起来就可以。事件修饰符可以链式书写

<a href="http://www.baidu.com" @click.prevent.stop="show">点我跳转</a>
vue时间修饰符 意义
prevent 阻止默认事件(常用)
stop 阻止事件冒泡(常用)
once 事件只触发一次(常用)
capture 使用事件的捕获模式
self 只有event.target是当前操作的元素时才触发事件
passive 事件的默认行为立即执行,无需等待事件回调执行完毕

键盘事件

在原生js里,通常用e.keycode判断哪个键按下了。
vue里可以给使事件后面添加别名后缀方式使用。例如 @keyup.enter

vue常用案件别名 按键
回车 enter
删除 delete(删除和退格同时捕获)
退出 esc
空格 space
换行 tab(特殊:必须配合keydown使用)
up
down
left
right
  • vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-cace短横线命名法。例如 @keydown.caps-lock

  • 类似tab的系统用法特殊的修饰键:ctrl、alt、shift、meta

    1. 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。可以链式书写 @keyup.ctrl.y
    2. 配合keydown使用:正常出发事件
  • 也可以使用keycode去指定具体的按键(mdn文档提示未来可能弃用)

  • Vue.config.keyCodes.自定义按键名 = 键码,可以去定制按键

Vue计算属性 computed

computed里面写的属性的值基本上都是一个对象。通常称为计算属性。
计算属性里面通常写get()和set()属性,类似Object.defineProperty()一样使用

const Counter = {
    data() {
        return {
            firstName: '张',
            lastName: '三'
        }
    },
    computed: {
        fullName: {
            get() {
                return this.firstName + this.lastName
            }
        }
    }
}
app = Vue.createApp(Counter).mount('#app')
  • get什么时候调用?
    1. 初次读取该值时
    2. 所依赖的数据发生变化时
  • set什么时候调用?
    当该值被修改时

计算属性小结

  1. 定义:要用的属性不存在,当可以通过已有属性计算得来
  2. 原理:底层借助了Object.defineProperty()的getter和setter
  3. 优势:与methods实现相比,内部有缓存机制,效率更高,调试方便
  4. 备注:计算属性最终会出现在vm上,直接读取使用即可。
    如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起所依赖的数据改动。例如上面的案例
    computed: {
        fullName: {
            get() {
                return this.firstName + this.lastName
            }
            set(value){
                let v = value
                this.firstName = v[0]
                this.lastName = v[1]
            }
        }
    }

计算属性简写

通常,用到计算属性基本上是只用到getter的,如果只用到getter,那么可以使用简写形式

    computed: {
        fullName() {
                return this.firstName + this.lastName
            }
        }
    }

虽然看起来像个方法,但他实际上是个属性,使用的时候不要使用调用符号。
至此,我们学到的vue上的东西有:data数据、methods方法、computed计算属性。数据和计算属性都是直接读即可读到

监视属性变化watch

  • 配置属性watch可以监视vue实例里一个值的变化,可以是普通data值,也可以是计算属性值。监听的值发生变化就执行handler。
  • watch的属性作为键,值是一个对象形式的配置对象,里面有基础配置属性handler函数,该函数自动接收两个参数,一个是新值一个是旧值。
  • handler什么时候调用?当监视的那个键的值发生变化的时候
const Counter = {
    data() {
        return {
            isHot:true
        }
    },
    watch:{
        isHot:{
            handler(newValue,oldValue){
                console.log('isHot被修改了')
            }
        }
    }
}
app = Vue.createApp(Counter).mount('#app')
  • 还有其他配置项,immediate:false,该配置项用于是否开启 初始化时让handler调用一下。
  • 监视的属性必须存在才能被监视
  • 监视的两种写法:
    1. 创建vue实例时候传入watch配置
    2. 通过vue实例的watch方法监视。`vm.watch('isHot',{})` ,参数一是监视那个值,参数2是配置项

深度监视

监视多级结构中所有属性的变化,使用配置项 deep:true

  • vue中的watch默认不监测对象内部值的改变(一层)(当监测的值是对象时)
  • 配置 deep:true 可以监测对象内部值改变(多层)(当监测的值是对象时)
    其他知识点:
    1. vue自身可以监测到对象内部值的改变,但vue提供的watch默认不可以
    2. 使用watch时根据数据的具体结构,来决定是否开启deep配置项。像上面提到的当监测的值是对象时

监视的简写

当不需要其他配置项只需要handler函数的时候,可以使用简写

const Counter = {
    data() {
        return {
            isHot:true
        }
    },
    watch:{
        isHot(newValue,oldValue){
                console.log('isHot被修改了')
        }
    }
}
app = Vue.createApp(Counter).mount('#app')

watch和computed区别

  1. computed能完成的功能,watch都可以完成。
  2. watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作

绑定class样式

  1. 字符串写法:适用于:样式的类名不确定,需要动态指定
<div class="basic" :class="mood" @click="changeMood"></div>
<script>
    const Counter = {
        data(){
            return {
                mood = 'style1'
            }
        }
        methods:{
            changeMood(){
                this.mood = 'style2'
            }
        }
    }
app = Vue.createApp(Counter).mount('#app')
</script>
  1. 数组写法,适用于:要绑定的样式个数不确定、名字也不确定。因为可以使用数组的方法删除或添加
<div class="basic" :class="classArr" @click="changeMood"></div>
<script>
    const Counter = {
        data(){
            return {
                classArr:['style1','style2','style3']
            }
        }
        methods:{
            changeMood(){
                this.classArr.shift()
            }
        }
    }
app = Vue.createApp(Counter).mount('#app')
</script>
  1. 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用
<div class="basic" :class="classObj" @click="changeMood"></div>
<script>
    const Counter = {
        data(){
            return {
                classObj:{
                    style1:true,
                    style2:true
                }
            }
        }
        methods:{
            changeMood(){
                this.classObj.style1 = false
            }
        }
    }
app = Vue.createApp(Counter).mount('#app')
</script>

绑定style样式

注意:使用style绑定样式,key值要用小驼峰写法

  1. 对象写法
<div class="basic" :style="styleObj" @click="changeMood"></div>
<script>
    const Counter = {
        data(){
            return {
                styleObj:{
                    backgroundColor:'green',
                    fontSize:'40px'
                }
            }
        }
    }
app = Vue.createApp(Counter).mount('#app')
</script>
  1. 数组写法
    :style="[a,b]"其中a、b是样式对象

scoped样式

多个组件写style样式,如果不携带scoped属性,那么会全部由app.vue组合到一起,如果命名冲突后面的就会覆盖前面的

<style scoped>

</style>

作用:让样式只在局部组件中生效,防止冲突。一般不会在app组件使用

条件渲染 v-show和v-if

v-show的原理是display:none,接的值是一个布尔值,也可以是一个表达式。v-show="3===1"
v-if的原理是把整个元素节点移除掉,dom节点资源开销币v-show大
v-else-if的原理与普通if else一样,v-if判断成功后,v-else-if就不判断了
v-else就是v-if和v-else-if都不成立的时候,v-else就生效
v-else和v-if和v-else-if需要成组使用,也就是使用这三个命令的节点需要连在一起,否则只有v-if生效,然后后续的报错
其他知识点:template标签和v-if搭配使用,template元素在渲染的时候会脱掉这个标签

循环渲染/列表渲染 v-for

类似原生的 for-in 循环,可用于遍历可迭代对象和对象,还可以遍历指定次数。
语法:v-for="(item,index) in xxx" :key="yyy",当只需要遍历值不需要索引的时候可以只写一个参数

<ul>
    <li v-for="value in arr" :key="value.id"></li>
    <li v-for="(objItem,index) in carObj" :key="item.id"></li>
    <li v-for="(number,index) in 5" :key="value.id"></li>
    //v-for循环对象时有三个参数,item,key和index
    <li v-for="(item,key,index) in obj" :key="index"></li>
</ul>

key有什么作用

  1. 虚拟DOM中key的作用
    key是虚拟DOM对象的标识!!!当数据发生变化时,Vur会根据新数据生成新的虚拟DOM!!!
    随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,也就是根据diff算法去进行页面渲染。
  2. 如果不写key,则Vue默认根据index来对key进行赋值。如果用index作为key会引发一下问题
    1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新,影响效率
    2. 如果结构中还包含输入类DOM,会产生错误DOM更新进而影响界面。
  3. 所以,开发中使用每条数据的唯一标识作为key,如id、手机号、身份证号等唯一值。

Vue2监测数据改变的原理

Vue.set() 和 vm.set() 用于修改堆数据(数组、对象等)里的数据 参数一:要修改(添加)进的目标 参数二:要修改的属性key 参数三:要修改的值value 除了set 还有 $delete,参数是一样的

原理总结:

  1. vue会监视data中所有层次的数据
  2. 如何监测对象中的数据?通过setter实现监视,且要在new Vue时旧传入要监测的数据。
    1. 对象中后追加的属性,Vue默认不做响应式处理
    2. 如需给后添加的属性做响应式,请使用如下API: Vue.set(TARGET,PropertyName/INDEX,VALUE)vm.$set(TARGET,PropertyName/INDEX,VALUE)
  3. 如何监测数组中的数据?
    1. 通过包裹数组更新元素的方法(vue重写的常用原生数组方法):push、pop、shift、unshift、splice、sort、reverse。本质是调用原生对应的方法对数组进行更新后重新解析模板,调用reactiveSetter等进行界面更新。
    2. 使用 Vue.set(TARGET,PropertyName/INDEX,VALUE)vm.$set(TARGET,PropertyName/INDEX,VALUE)
  4. 特别注意:Vue3已移除那两个api,Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性

收集表单数据

vue页面收集表单数据,通常使用v-model指令,以及给form表单用v-on绑定事件并阻止默认跳转提交 <form @submit.prevent="demo">

  1. <input type="text"/>,则v-model手机的是value值,用户输入的就是value值
  2. <input type="radio"/>,则v-model收集的是value值,且要给标签配置value属性
  3. <input type="checkbox">
    1. 没有配置input的value属性,那么收集的就是checked的布尔值
    2. 配置input的value属性:
      • v-model的初始值是非数组,那么收集的就是checked的布尔值(不推荐这样用)
      • v-model的初始值是数组,那么收集的就是value组成的数组

v-model的三个修饰符

修饰符 作用
number 输入字符串转为有效的数字
lazy 失去焦点再收集数据
trim 输入首尾空格过滤

Vue2过滤器filters

<div id="app">
    <h1>现在的时间是:{{time | timeFomater}}</h1>
    <h1>现在的时间是:{{time | timeFomater()}}</h1>
</div>
<script>
    const Counter = {
        data() {
            return {
                time:Date.now()
            }
        },
        filters:{
            timeFomater(value){
                return dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
            }
        }
    }
    let app = Vue.createApp(Counter).mount('#app')
</script>
  • vue会把,需要过滤器处理的那个参数,当作函数的参数传入过滤器函数里。
  • 如果像第二个h1那样写,传入小括号的第一个参数依然是需要过滤器处理的那个参数,第二个则是自定义传的参数.
  • filters过滤器可以进行多层过滤 <h1>现在的时间是:{{time | timeFomater() | myslice}}</h1>
  • 过滤器可以搭配v-bind使用,但不能搭配v-model 现在的时间是:<input :x="time | timeFomater"></input>

全局过滤器 Vue.filter

Vue.filter('mySlice',function(value){return value.slice(0,4)})

Vue其他内置指令

整理之前学的指令

指令 说明
v-bind 单项绑定解析表达式,可简写为 :xxx
v-model 双向数据绑定
v-for 遍历数组、对象、字符串
v-on 绑定事件监听,可简写为@xxx
v-show 条件渲染是否展示
v-if v-else-if v-else 条件渲染

v-text

类似innerText,v-text里的内容会覆盖标签内所有内容,且不会编译里面的标签括号

<div id="app">
    <h1 v-text="time"></h1>
</div>
<script>
    const Counter = {
        data() {
            return {
                time: '<h2>'+Date.now()+'</h2>'
            }
        }
    }
    let app = Vue.createApp(Counter).mount('#app')
</script>

v-html

类似innerHtml,可以解析结构。所以v-html对比v-text有安全性问题。一定要在可信的内容上使用v-html,永远不要在用户提交的内容上使用

v-cloak

v-cloak指令没有值。

  1. 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉元素上的v-cloak属性。
  2. 使用css的display:none 可以解决网速慢时展示出 {{xxx}} 的问题
<div v-clock>{{name}}</div>
<style>
    [v-cloak]{
        display: none;
    }
</style>

v-once

v-once指令没有值。事件修饰符也有个once别搞混了,用法不一样

  1. 使用该指令的元素内容,只会进行初次传值渲染。v-once所在节点在初次动态渲染后,就视为静态内容了。
  2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

v-pre

  1. 跳过其所在节点的vue编译过程
  2. 可利用他跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译

自定义指令 directives

  • vue里使用directives来自定义指令,指令是写成一个函数形式或对象形式。
  • 每个函数接收两个参数,参数一:该指令绑定的对象;参数二:该指令书写时后面引号接的东西(信息对象),通常用他的value
<div id="app">
    <h1>当前值是{{num}}</h1>
    <h2 v-big="num"></h2>
    <button @click="num++">点击增加原始值</button>
</div>
<script>
    const Counter = {
        data() {
            return {
                num: 1
            }
        },
        directives: {
            big(element, binding) {
                element.innerText = `放大十倍后的值是${binding.value * 10}`
            }
        }
    }
    let app = Vue.createApp(Counter).mount('#app')
</script>
  • 书写的函数型指令何时会被执行?
  1. 指令与元素成功绑定时(一上来)
  2. 指令所在的模板被重新解析时。(例如使用到的data里的值被修改了)

对象形式自定义指令

  • 函数形式是简写形式,实际上对象形式才是自定义指令的本体。
  • 其中包括三个阶段:bind、inserted、update。这三个东西被称为自定义指令的配置。
  • bind是指令与元素成功绑定时。inserted是所在元素被插入页面时。update是指令所在的模板被重新解析时。参数都是element和binding。有很多操作时只有在inserted之后才有效的,这点要记住
directives: {
    fbind: {
        bind(element,binding){
            
        },
        inserted(element,binding){
            element.focus()
        },
        update(element,binding){
        
        }
    }
}

自定义指令注意点

  1. 指令定义时不加v-,但使用时要加 v-
  2. 指令名不能使用驼峰命名法,vue会默认全部转成小写。真要多个单词写用斜杠接起来。例如 v-focus-bind。在写指令的时候指令对象名用引号包起来。'v-focus-bind'(element,binding){}
  3. 全局指令书写形式 Vue.directive(指令名,配置对象)Vue.directive(指令名,回调函数)

Vue生命周期

  1. 是什么:vue在关键时刻帮我们调用的一些特殊名称的函数
  2. 生命周期函数的名字不可更改,但函数的具体内容时程序员根据需求编写的
  3. 生命周期函数中的this指向是vm或组件实例对象

完整生命周期,8个(4对)

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted(常用;发送ajax请求,启动定时器,绑定自定义事件,订阅消息等初始化操作)
  5. beforeUpdate
  6. updated
  7. beforeDestroy(常用;清除定时器,解绑自定义事件,取消订阅等收尾工作)
  8. destroyed

[图片上传失败...(image-db9fe8-1660811061785)]

Vue2组件

<body>
    <div id="app">
        <school></school>
        <student></student>
        <student></student>
    </div>
    <script>
        const school = Vue.extend({
            template: `
                <div>
                    <h2>学校的名字是{{name}}</h2>    
                    <h2>学校的地址是{{address}}</h2>    
                </div>
            `,
            data() {
                return {
                    name: '家里蹲大学',
                    address: '家里'
                }
            },
        })

        const student = Vue.extend({
            template: `
                <div>
                    <h2>学生的名字是{{name}}</h2>    
                    <h2>学生的年龄是{{age}}</h2>    
                    <button @click="addage">点我增加年龄</button>
                </div>
            `,
            data() {
                return {
                    name: 'zhangsan',
                    age: 18
                }
            },
            methods: {
                addage() {
                    this.age++
                }
            },
        })

        const vm = new Vue({
            el: '#app',
            components: {
                school,
                student
            }
        })
    </script>
</body>
  • 使用组件三大步骤1.创建/定义组件、2.注册组件、3.使用组件(写组件标签)
  • 如何定义一个组件?
    使用Vue.extend(OPTIONS)创建,其中OPTIONS和new Vue(OPTIONS)时传入那个配置对象几乎一样。但区别如下
    1. el不要写,因为最终所有的组件都要经过一个vm管理,由vm中的el决定服务那个容器
    2. data必须写成函数,避免组件复用时候,多个组件互相篡改数据
  • 如何注册组件?
    1. 局部注册:靠 new Vue 时候写的 components 配置项
    2. 全局注册: 靠 Vue.components('组件名',组件)
  • 组件书写结构时候用 template 项来写,template里面必须要有且只有一个父标签
  • 使用组件: <zujian></zujian>

关于组件的注意点

  1. 关于组件名:
    1. 一个单词组成:
      • 第一种写法(首字母小写):zujian
      • 第二种写法(首字母大写):Zujian
    2. 多个单词组成:
      • 第一种写法(kebab-case命名):my-school
      • 第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
    3. 备注:
      • 组件名要回避HTML已有的元素标签名
      • 可以使用name配置项指定组件在开发者工具中显示的名字
  2. 关于组件标签:
    • 第一种写法:<zujian></zujian>
    • 第二种写法:<zujian/>
    • 备注:不使用脚手架时,第二种写法会导致后续组件不能渲染
  3. 一个简写方式:
    const zujian = Vue.extend(OPTIONS) 可简写为 const zujian = OPTIONS

关于VueComponent

  1. Vue组件本质是一个名为VueComponent的构造函数,是由Vue.extend生成的
  2. 我们在使用组件的时候(写上组件标签时)Vue解析时会帮我们创建组件的实例对象(即执行new VueComponent)
  3. new调用构造函数,每次调用返回的都是一个全新的VueComponent
  4. 关于组件的this指向
    1. 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是vc
    2. new Vue中的data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是vm,注意区别

vue组件与vue的重要关系

Vue组件实例.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象可以访问到Vue原型上的属性和方法

Vue脚手架

安装

npm i -g @vue/cli
# OR
yarn global add @vue/cli

创建一个项目:

vue create my-project
# OR
vue ui

脚手架文件结构:

|-- undefined
    |-- .gitignore:git忽略文件配置
    |-- babel.config.js:babel配置文件
    |-- jsconfig.json
    |-- package-lock.json:包版本控制文件
    |-- package.json:应用包配置文件
    |-- README.md:应用描述文件
    |-- vue.config.js:vue配置文件
    |-- dist:打包后文件
    |-- public:静态资源文件夹,webpack会原封不动的打包文件
    |   |-- favicon.ico:页标签图标
    |   |-- index.html:主页面
    |-- src
        |-- App.vue:汇总所有组件
        |-- main.js:入口文件
        |-- assets:静态资源文件夹,webpack会模块化打包
        |   |-- logo.png
        |-- components:组件文件夹
            |-- HelloWorld.vue

关于不同版本的Vue

  • vue.js 与 vue.runtime.xxx.js 的区别
    1. vue.js 是完整版的vue,包含核心功能和模板解析器
    2. vue.runtime.xxx.js 是运行版的vue,只包含核心功能,没有模板解析器,所以需要render函数
  • 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。

vue.config.js配置文件

  • 使用 vue inspect > output.js 可以查看到vue脚手架的默认配置
  • 使用 vue.config.js 可以对脚手架进行个性化定制
  • 新手通常把eslint关闭 lintOnSave:false

ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者),然后用 this.$refs.*** 获取
<template>
    <div>
        <h1 ref="biaoti"></h1>
        <School ref="sch"></School>
    </div>
</template>
<script>
    export default {
        mounted(){
            console.log(this.$refs.biaoti)
            console.log(this.$refs.sch)
        }
    }
</script>
  1. 应用在html标签上获取的是真实DOM元素,应用在逐渐标签上是组件实例对象(vc)
  2. 使用方式:
    • 打标识:<h1 ref="xxx"></h1><School ref="xxx"></School>
    • 获取:this.$refs.xxx

组件传值接值 和 props配置项

传值

  • 方式:在使用组件的时候携带标签属性,为一个个名值对形式
<Student name="张三" sex="女" age="18"></Student>
  • 用props接收过来的值会在组件的vc身上存着,在开发者工具里的props可以看到

props配置项

  • 功能:让组件接收外部传过来的数据,默认类型是string类型,如要修改例如可用v-bind改为number。
  1. 方式一:数组形式(简写形式),顺序不重要,对上号就行
<script>
    export default{
        props:['name','sex','age']
    }
</script>
  1. 方式二:接收时只对数据进行类型限制
<script>
    export default{
        props:{
            name:String,
            age:Number,
            sex:String
        }
    }
</script>
  1. 方式三:接收时对每个数据进行严格设置,可以配置type、default、required。通常default、required不会同时使用,逻辑问题
<script>
    export default{
        props:{
            name:{
                type:String,
                required:true
            },
            age:{
                type:String,
                default:99
            },
            sex:{
                type:String,
                required:false,
                default:'male'
            }
        }
    }
</script>

props注意点

  1. vue不允许修改props接收过来的值,否则发出警告。如要修改,先把props的值存在data之后再修改
<Student name="张三" sex="女" age="18"></Student>
<script>
    export default{
        props:[name,age,sex],
        data(){
            return{
                uname:this.name
            }
        }
    }
</script>
  1. 如果使用的组件上的data有与props同名的,优先使用props的,即父组件传过来的那个值。
  2. 传值时候不要使用vue预留的指令当作传值的名
  3. 数组方式传入的时候注意变量要用引号包起来

mixin混入

new Vue({
    mixin:[xxx,xxx]
})
  • 功能:可以把多个组件公用的配置提取成一个混入对象,提取的文件是一个js格式的文件
  1. 第一步:定义混合,写一个js文件,然后里面写入配置项
const hun1 = {
    data(){...},
    methods:{...}
    ...
}
  1. 第二步,使用局部混入或全局混入。局部混入写在局部组件的vue配置项里
new Vue({
    mixins:[xxx,xxx]
})

全局混入一般写在main.js里

Vue.mixin(xxx)
  1. 注意点:混入项和组件自带项冲突时,数据以组件自带的data为准。如果两者都有生命周期钩子,则先执行混入项的生命周期钩子。

vue插件

vue插件是一个包含install方法的一个对象,install的第一个参数是Vue构造函数,第二个第三个第n个是插件使用者传递的数据。
定义完Vue插件之后在main.js使用 Vue.use(插件) 即可。

//定义一个plugins.js

export default{
    install(Vue){
        Vue.directive(...)
        Vue.mixin({...})
        Vue.prototype.hello=...
    }
}

在main.js里使用

import plg1 from './plugins.js'
Vue.use(plg1)

给组件触发自定义事件 this.$emit

this.$emit(eventName,params) 触发自定义事件的两个参数,参数1是触发的事件名,参数2是传过去的参数。
this.$on('eventName',params)或者v-on:eventName="fn" fn(params) 类似这样接收参数

<School v-on:zidingyi="demo"></School>
methods:{
    demo(value){console.log("demo被调用了,得到的值是"+value)}
}

以上案例,在使用组件的时候,绑定了一个自定义事件“zidingyi”,接下来,去那个组件里面使用this.$emit('zidingyi',this.name)触发该事件,就可以在子组件把数据传输给父组件。

<template>
  <div>
    <h2>学校名字是{{ name }}</h2>
    <h2>学校地址{{ address }}</h2>
    <button @click="sendSchName">点我发送学校名字给app</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: "家里蹲大学",
      address: "家里",
    };
  },
  methods: {
    sendSchName() {
      this.$emit("zidingyi", this.name);
    },
  },
};
</script>

这个时候在页面使用button就会在控制台输出 demo被调用了,得到的值是家里蹲大学

通过ref给组件绑定自定义事件

<Student ref="student"></Student>
methods:{
    demo(value){console.log("demo被调用了,得到的值是"+value)}
}
mounted(){
    this.$refs.student.$on('zidingyi',this.demo)
}

通过ref给逐渐绑定自定义事件灵活性更高,可以在异步给组件绑定自定义事件

this.$off() 解绑自定义事件

<template>
  <div>
    <h2>学校名字是{{ name }}</h2>
    <h2>学校地址{{ address }}</h2>
    <button @click="sendSchName">点我发送学校名字给app</button>
    <button @click="unset">点我解绑自定义事件</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: "家里蹲大学",
      address: "家里",
    };
  },
  methods: {
    sendSchName() {
      this.$emit("zidingyi", this.name);
    },
    unset(){
        this.$off("zidingyi")
    }
  },
};
</script>
  • this.$off()可以传递一个字符串为参数,如果需要解绑多个自定义事件,则需要写成一个数组形式 this.$off(["zidingyi","chufa"])
  • 如果 this.$off() 不传递任何参数,则解绑该组件身上所有的自定义事件
  • 如果组件进行了destroyed 生命周期,那么身上绑定的自定义事件也会解绑,这是生命周期自身的特性

自定义事件总结

  • 作用:子组件给父组件传内容,使用场景:子组件给父组件传数据,在父组件中给B绑定自定义事件。
  • 通过this.$refs.xxx.$on('atguigu',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向的是绑定者。
  • 组件上也可以绑定原生DOM事件,需要使用native修饰符,本质是给组件的根标签绑定原生DOM事件,点任何子标签都会冒泡到根标签。
  • 给原生DOM绑定自定义事件是没有意义的,因为没有办法触发emit函数,但可以借由原生DOM事件触发自定义事件@click="emit('zidingyi',参数)"

全局事件总线(GlobalEventBus)

是一种组件间通信的方式,适用于任意组件间通信

安装全局事件总线

在创建vue实例的时候在beforeCreate钩子里面给vue原型挂上bus这个量,使之拥有vm上的on$off等方法

new Vue({
    ......
    beforeCreate(){
        Vue.prototype.$bus = this
    },
    ......
})

使用事件总线

谁需要数据的,就先往谁身上先写一个方法,然后把这个方法,挂在一个自定义事件上

methods(){
    reqData(data){
        //data为事件触发者传过来的参数
    }
}
mounted(){
    this.$bus.$on('xxxreqData',this.reqData)
}

谁发送数据的,就在这个组件通过这个自定义事件把数据发送过去

this.$bus.$emit('xxxreqData',DATA)

就是根据这个挂在$bus上的自定义事件来实现通信

消息订阅与发布,pubsub-js库

也是一种组件间同行的方式,可以适用于任意组件间通信。

使用方法

  1. 安装pubsubjs npm i pubsub-js
  2. 在使用的组件里面引入pubsub import pubsub from 'pubsub-js'
  3. 使用pubsub的api subscribe 去订阅一个自定义消息名的消息,第一个参数是消息名,第二个参数是这个消息里的数据。注意这个api里面的this是undefined。并且注意,作为subscribe的回调的形参一默认是消息名字,形参二后面的才是传输的数据。
mounted(){
    this.pid = pubsub.subscribe('MSGNAME',(MSGNAME,data)=>{data...})
}
  1. 在提供数据的组件使用 publish 这个api去发布消息,第一个参数是自定义消息名,第二个参数是数据。
pubsub.publish('MSGNAME',data)
  1. 最好在beforeDestroy钩子中使用 unsubscribe(pid) 这个api去取消订阅,参数是使用订阅时声明的id变量
pubsub.unsubscribe(pid)

$nextTick

this.$nextTick(回调函数)
this.$nextTick(()=>{
    //...更新dom后要进行的操作
})
  • 作用:在这一轮DOM更新结束后的时刻执行参数里面的回调函数
  • 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
    nextTick,在Vue里,我们在更新数据之后,DOM会在这一轮宏任务之后微任务里进行更新,倘若这一次宏任务中进行了一些读取DOM的操作,则会发现读取的是宏任务后的DOM信息,因为还没到vue更新DOM的微任务。如果想要在更新DOM的微任务以后对DOM进行一些操作,则把这些操作写在$nextTick的回调函数里面

Vue与CSS3

Vue动画

CSS3的keyframe动画,搭配Vue的 <transition></transition> 标签,以及v-enter-activev-leave-active 类名

<template>
  <div>
    <button @click="a++"></button>
    <transition name="anima">
      <h1 class="biaoti" v-show="a % 2 == 0">你好啊</h1>
    </transition>
  </div>
</template>
<script>
export default {
  data() {
    return {
      a: 2,
    };
  },
};
</script>
<style scoped>
.anima-enter-active {
  animation: animaName 1s;
}
.anima-leave-active {
  animation: animaName 1s reverse;
}
@keyframes animaName {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0px);
  }
}
</style>

Vue过渡

实际上vue给一套transition标签内置了六个class,分别是以下六个,如果只是动画,只使用active那两个就可以了

.v-enter .v-enter-active .v-enter-to .v-leave .v-leave-active .v-leave-to
<template>
  <div>
    <button @click="a++"></button>
    <transition name="anima">
      <h1 class="biaoti" v-show="a % 2 == 0">你好啊</h1>
    </transition>
  </div>
</template>
<script>
export default {
  data() {
    return {
      a: 2,
    };
  },
};
</script>
<style scoped>
.anima-enter,
.anima-leave-to {
  transform: translateX(-100%);
}
.anima-leave-active,
.anima-enter-active {
  transition: 1s;
}
.anima-leave,
.anima-enter-to {
  transform: translateX(0);
}
</style>

transition-group 多个过渡、动画标签

一个transition标签里面只能套一个子元素,让一个子元素给vue自动形成六个标签。如果需要套多个子元素,则需要用到<transition-group></transition-group>,注意,如果使用transition-group,那么每一个子元素都要给key值

<transition-group name="anima">
      <h1 v-show="a % 2 == 0" key="1">你好啊</h1>
      <h1 v-show="a % 2 == 0" key="2">我不好</h1>
</transition-group>

transition和transition-group标签上的属性

属性名 功能
name="xxx" 自动生成动画的类名的前缀,第三方动画库也会用到
appear 一开始就执行一次动画
enter-active-class 第三方动画库用到
leave-active-class 第三方动画库用到

Vue配置代理devServer.proxy 解决跨域

module.exports = defineConfig({
  devServer: {
    proxy: 'http://192.168.1.10:5000'
  }
})

在vue.config.js文件中写入以上内容即可实现简单代理解决跨域问题,请求的时候请求本机地址即可。
但如果本地public资源有同名请求则冲突。且只能配置一台代理

module.exports = defineConfig({
  devServer: {
    proxy: {
        ''/student': {
            target: 'http://192.168.1.10:5000',
            pathRewrite: { '^/student': '' },
        },
        '/car': {
            target: 'http://192.168.1.10:5001',
            pathRewrite: { '^/car': '' },
        },
    }
  }
})

以上方式可以开启多个代理服务器,proxy写成一个对象形式,里面的值也写成一个对象的形式。
对象的键名是路由,为该代理请求的后缀,如果不写pathRewrite配置项,请求资源时也会带上此路由。
ws:true 用于支持websocket,changeOrigin:true 用于控制请求头中的host值,这两如果不写,默认值也是true

slot插槽

默认插槽 slot标签

在组件里面使用slot标签进行插槽占位

<template>
  <div>
    <h2>学校名字是{{ name }}</h2>
    <slot></slot>
  </div>
</template>

在使用组件的地方在双组件标签里面写上其他内容,那么这些内容就会使用插槽占的位置,比如这个例子,img标签就会使用slot的位置

<School>
      <img src="https://xxx.jpg" />
</School>

具名插槽 带有name属性的slot标签

在组件里面使用slot标签进行插槽占位,并带上name属性

<template>
  <div>
    <h2>学校名字是{{ name }}</h2>
    <slot name="center"></slot>
    <slot name="footer"></slot>
  </div>
</template>

在vue2里用带有slot属性的标签即可为插槽传入内容(已弃用),请使用 v-slot,但注意,v-slot只能用在template标签上,这点与slot不一样,只有一种特殊情况就是只使用默认插槽,就可以把v-slot直接用在组件上

<div slot="center">
    ...旧写法已弃用
<div>
<template v-slot:center>
    ...新标准
<template>

vue3里需要使用v-slot指令和template标签,甚至使用v-slot的简写形式井号#

<template v-slot:footer>

</template>
或
<template #footer>

</template>

默认插槽即不具名插槽也是有name的,name的值为default

作用域插槽

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(数据在子组件,父组件写结构)

//子组件中,使用slot传输data数据
<template>
    <div>
        <slot :info="info"></slot>
    </div>
</template>
<script>
    export default{
        data(){return{
            info:['name:Ming','age:18']
        }}
    }
</script>

在父组件中,使用template标签搭配scope(或slot-scope)属性拿到组件传过来的数据,注意拿过来的是一个对象,可用解构赋值

<Student>
    <template scope="data">
        <ul>
            <li v-for="d in data.info" :key="d">{{d}}<li>
        </ul>
    </template>
<Student>

在vue3中,又要起name,又要使用scope,可使用v-slot:NAME="SCOPENAME",vue3中只能使用v-slot指令

<template v-slot:default = "slotProps">
    {{slotProps}}
</template>

Vuex

全局事件总线可以很好完成读的功能,但多个组件间如果要修改数据就很麻烦。
Vuex是专门在Vue中实现集中式状态(数据)管理的一个Vue插件,在一个vue应用中进行各组件的数据操作很有用。
Vuex业务逻辑图:[图片上传失败...(image-a2f5f6-1660811061785)]

什么时候用Vuex

  1. 多个组件依赖于同一状态
  2. 来自不同组件的行为需要变更同一状态

搭建Vuex环境

先在项目里安装Vuex,npm i vuex@3,注意:Vue2只能用Vuex3,Vuex4只能用于Vue3,默认安装的是最高版本。

  • 创建目录及文件 src/store/index.js
//引入Vue核心库,因为使用Vuex需要在new Vuex.Store之前执行Vue.use(Vuex)否则报错
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//准备三个对象接收api的赋值,state保存具体数据,actions相应组件中用户的动作,mutations修改state中的数据
const state = {}
const actions = {}
const mutations = {}
//创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state
})
  • 在main.js中引入store,并在创建vm的时候放入
import store from './store'
new Vue({
    el:'#app',
    render:h=>h(App),
    store
})

state

需要Vuex管理的状态(数据)都放在store里面的state对象里,比如说需要Vuex管理一个sum

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
    sum:0       //此处放置需要vuex管理的数据
}
const actions = {}
const mutations = {}
export default new Vuex.Store({
    actions,
    mutations,
    state
})

如何读取store里面的数据?在需要的组件里面使用 this.$store.state.*** 即可

dispatch申请修改store里的state,或直接commit

如果在组件中想要修改store里的state,需在组件中调用 this.$store.dispatch() 这个api,参数一是要执行的action,参数二是传递过去的实参。

export default {
    ...
    this.$store.dispatch('提交的行为名称',需要传递的实参)
    ...
}

如果不需要一些复杂的处理,想直接修改state的,也可以在组件中直接使用commit api

export default {
    ...
    this.$store.commit('要执行的mutations名称',需要传递的实参)
    ...
}

action和commit

一般修改state里的东西的行为,都会写在action对象里,书写格式是键值对格式 const action={jia:fuction(){}},一般用es6简写模式

const actions = {
    jia(context,value){
        context.commit('mutations里的方法名',要传递的参数)
    }
}
  • 里面的函数会接收到两个以上参数,
    第一个参数很重要,是一个mini版的store,官方文档称为context。
    第二个及后面的参数,就是调用了dispatch的人传递过来的参数。
  • 根据vuex流程图可知action里面的行为最终一定会commit到mutation里,用的就是context里的commit

关于context

action里的函数写的第一个形参context里面除了有commit,还有dispatch和state。

  • 为什么有dispatch?方便多个action链式调用。
  • 为什么有state?直接修改行不行?如果在action里修改,低版本开发者工具监测不到,action里用state主要用于逻辑判断

mutations

在一般开发中,mutations里的方法名通常是actions中对应提交的方法的全大写。mutations里的函数会接收到两个参数。
第一个参数是经过数据代理后的state,可以拿到经过getter和setter包装的数据。
第二个参数是commit传递过来的数据。不管是组件直接commit过来的还是actions commit过来的

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
    sum:0       //此处放置需要vuex管理的数据
}
const actions = {
    jia(context,value){
        context.commit('JIA',value)
    }
}
const mutations = {
    JIA(state,value){
    state.sum += value
    }
}
export default new Vuex.Store({
    actions,
    mutations,
    state
})

actions和mutations

mutations不要进行异步操作,一般异步操作都在actions里进行。
或者说,一般在actions里面进行数据修改前的逻辑行为和异步行为,mutations里直接进行数据处理

const actions = {
    jiaWait(context,value){
        setTimeout(()=>{
            context.commit('JIAWAIT',value)
        },500)  
    }
}
const mutations = {
    JIAWAIT(state,value){
        state.sum += value
    }
}

$store.getters

但state中的数据需要经过加工后再使用时,可以使用getters加工,类似于computed。
使用 getters 需要在 store 中最佳 getters配置,getters中的函数会收到一个参数,它就是state

...
const getters = {
    bigSum(state){
        return state.sum * 10
    }
}
export default new Vuex.Store({
    state,
    actions,
    mutations,
    getters
})

组件中读取getters的东西 $store.getters.***

mapState和mapGetters

如果组件内想要读取state和getters的数据,要写一大串 $store.state.***this.$store.getters.***,如果不想写一大串,则需要自己在计算属性一个个写返回值。

...
computed:{
    he(){
        return this.$store.state.sum
    }
}
...

这个时候就需要vuex里面自带的 mapState 和 mapGetters 了,需要用到的组件需要从vuex中引入这两个方法

import {mapState,mapGetters} from vuex

这两个函数可以接收两种形式的参数,一种是对象形式,对象的里键值对的键是自己要起的名字,对象里键值对的值是state里面的名字,这个值需要用引号包住,否则会读成变量

mapState({he:'sum'})
mapGetters({dahe:'bigSum'})

但如果不需要自己起名,想在组件里使用直接与state里面同名,则可以写成数组形式。

mapState(['sum'])

这两个方法的返回值都是一个对象,里面是 {VALUE:function mappedState(){}} 形式,为的是方便放入computed属性,放入时因为是一个对象,所以需要使用拓展运算符...mapState()

...
computed(){
    ...mapState(['sum'])
}

mapMutations和mapAction

这两个方法用于帮助生成与mutations和action对话的方法。即包含$store.commit(xxx)$store.dispatch(xxx) 的函数。在使用的时候,也要先从vuex模块中引入这两个方法。

import {mapMutations,mapAction} from vuex

因为与mutations和action对话一般都是在method里,我们在组件的method里用拓展运算符展开,传递的参数也是对象和数组两种写法。

method:{
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    //或数组形式 ...mapActions(['jiaOdd','jiaWait'])
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    //或数组形式 ...mapMutations(['JIA','JIAN']),
}

那么问题来了,使用mapMutations和mapAction怎么传递参数呢?我们需要在模板绑定事件时传递好参数,否则参数是事件对象

<button @click="jiaOdd(参数)"></button>

vuex模块化 + namespace

store模块化。使用模块化必须要在每个模块建立时加上 namespaced:true 配置项

const countAbout = {
    namespaced:true,
    state:{...},
    mutations:{...},
    actions:{...},
    getters:{....}
}
const personAbout = {
    namespaced:true,
    state:{...},
    mutations:{...},
    actions:{...},
}
//或者以上的模块使用es6模块化引入
const store = new Vuex.Store({
    modules:{
        countAbout,     //相当于 countAbout:countAbout
        personAbout
    }
})

mapXXX新的传值方式:'...mapXXX(NAMESPACED',[])...mapXXX('NAMESPACED',{}),即第一个参数是哪个模块里的数据

...mapState('countAbout',['sum','school','subject'])
...mapGetters('countAbout',['bigsum'])
...mapActions('personAbout',{addPersonServ:'addfromServer'})
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'})

如果不使用mapXXX读取store里面的东西,除了state可以直接用点xxx.xxx 读到以外,其他都要使用一些特殊手法

//读取state里的数据
this.$store.state.personAbout.list
//读取getters里的数据
this.$store.getters['personAbout/firstPersonName']
//调用dispatch
this.$store.dispatch('personAbout/addfromServer',value)
//调用commit
this.$store.commit('countAbout/JIA',value)

VueRouter 路由

  • vue路由用于构建单页面应用。要使用先安装vue-router。并且注意,vue2只能用router3版所以在安装的时候要声明版本npm i vue-router@3,否则默认安装的是用于vue3的vuerouter4。
  • 配置好router文件后然后在main.js里use这个插件,创建路径文件src/router/index.js
//npm i vue-router@3
import VueRouter from 'vue-router'
//引入需要在路由中配置的组件
import About from '../components/About'
import Home from '../components/Home'
//在新建VueRouter实例时传入基本配置项 routes,形式是数组,数组项是由path和component组成的对象。
const router = new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {
            path:'/home',
            component:Home
        }
    ]
})
//暴露以上配置对象
export default router

去到main.js中引入并使用

//先引入这个模块并使用。
import VueRouter from './vue-router`
Vue.use(VueRouter)
//然后再引入上面的配置文件并在new Vue的时候传入router配置项
import router from './router'
new Vue({
    ...
    router
    ...
})

或者在 router/index.js 里面先引入vue,并在里面use VueRouter之后导出,在main.js里面直接引入router就可以了


import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

import routes from './routes'
const router = new VueRouter({
    routes,
    scrollBehavior(to, from, savedPosition) {
        return { y: 0 }
    }
})
export default router

记得在mainjs里new vue实例的时候配置router属性

new Vue({
    ...
    router
})

router-link 和 router-view 标签

  • router-link标签本质是一个经过vue包装后的a标签,可以通过tag属性改为其他标签,可以书写一些正常标签属性以及自身拥有的特别属性例如 active-class 和 to
    • to是去router文件里寻找匹配规则,
    • active-class为活跃的路由添加class样式,不活跃的自动移去(或者自己写.router-kubj-exact-active的样式)或者去router配置项加上linkActiveClass属性,值为要加上的class名。
    • 还有一个replace属性,会把本次路由跳转的地址放在浏览器历史记录上的push方式改为replace方式。
<router-link to="/about" active-class="active">点击渲染About组件</router-link>
  • router-view标签为把router文件匹配到的组件渲染出来的组件,在哪展现就在哪使用
<router-view></router-view>

路由的注意点

  1. 普通组件我们都放在 component 组件文件夹中,但路由使用的组件,我们一般新建一个 views 文件夹放在里面,称为路由组件。
  2. 通过切换隐藏的路由组件,实际上是被销毁掉的,会触发生命周期的 beforeDestroy 和 destroyed ,切换的则是挂载mounted
  3. 使用vue router后,我们输出这个组件vc,会发现他们身上有两个属性 route 和router。每个组件都有自己的 router 属性,里面存储着自己的路由信息,但整个应用只有一个router 。
  4. 路由懒加载:component的属性值使用回调函数形式。
const router = new VueRouter({
    routes: [         
        {             
            path: '/home',
            component: function foo(){return import("@/views/home")}
            //或者简写:component: ()=>import("@/views/home")
        }     
    ] 
})

路由嵌套/多级路由

routes数组项里的新配置项children用于写一个路由下的子级路由,path的方法有所不同,不用带斜杠/

const router = new VueRouter({
    routes: [
        {
            path: '/home',
            component: Home,
            children:[
                {
                    path:'news',
                    component:News
                },
                {
                    path:'message',
                    component:Message
                }
            ]
        }
    ]
})

然后在跳转到子路由的router-link标签里的to属性值要写完整路径

<router-link to="/home/news">News</router-link>

路由传递query参数

类似于请求url携带query参数一样,www.xxx.com?a=1&b=2,路由的to属性也可以携带query参数。可以使用字符串形式,也可以使用配置对象形式。

//字符串形式,因为通常要携带变量,所以要使用v-bind把传的值变成js表达式,并且通常使用拼串或es6模板字符串
<router-link :to="'/home/message/detail?title=' + value.title"></router-link>
//to属性传入配置对象
<router-link :to="{
    path:'/home/message/detail',
    query:{
        title:value.title
    }
}">

如何读取query参数?在被传递的路由组件上使用 $route.query.xxx 读取

$route.query.title

路由命名

  • 路由命名的目的是为了简化to属性跳转路径的书写长度,只能用对象方式写。
  • 在使用之前先要给路由路径配置好name属性。通常与组件和路径名对应,当然也可以乱取。
//像下面detail那种三级children路由,起一个name属性,会让router-link的to属性方便很多
{
    path: '/home',
    component: Home,
    children: [
        {
            path: 'news',
            component: News
        },
        {
            path: 'message',
            component: Message,
            children: [
                {
                    name:'detail'
                    path: 'detail',
                    component: Detail
                }
            ]
        }
    ]
}

在router-link里的to属性,使用对象方式传入name属性,就可以不用path属性了

<router-link :to="{
    name:'detail'
}"></router-link>

路由传递 params 参数 (动态路由、带有动态参数的路径)

  • router-link的to属性也可以传递params参数,但是想要发送params参数,先要在router配置文件的数组项的配置对象里的path属性使用占位符。
  • 书写方式是 path:xxx/:VALUE/:VALUE,如果要同时传递params和query,防止出现空串还会使用问号占位。path:xxx/:VALUE?/:VALUE?,问号的原理是正则表达式出现0次或任意次数的意思
{
    path: '/home',
    component: Home,
    children: [
        {
            path: 'news',
            component: News
        },
        {
            path: 'message',
            component: Message,
            children: [
                {
                    name:'detail'
                    path: 'detail/:id/:title',
                    component: Detail
                }
            ]
        }
    ]
}

router-link的to属性传递params参数,可以使用字符串形式和对象形式,特别注意使用对象写法不能使用path配置项,只能用name。

//字符串写法
<router-link :to="`/home/message/detail/${value.id}/${value.title}`"></router-link>
//to属性传入配置对象
<router-link :to="{
    name:'detail'
    params:{
        id:value.id,
        title:value.title
    }
}">

旧版本中防止传空串在传的时候用或运算符借个undefined params:{id:value.id||undefined}

路由的props配置

  • 如果不使用props,在使用 $route上的数据的时候都要写上前面一大串的$route.query.xxx$route.params.xxx
  • 普通组件可以使用props传递参数,路由组件也可以使用props收到参数。路由组件想要收到props参数,就需要在路由配置文件里的数组项的配置对象里写上props配置参数。
  • 而且props配置参数有三种写法。为对象式,布尔值式和函数式,通常用函数式,因为没有局限性
{
    path: 'message',
    component: Message,
    children: [
        {
            name: 'detail',
            path: 'detail',
            component: Detail
            //props:{a:100}
            //props:true
            props($route){
                return{
                    id:$route.query.id,
                    title:$route.params.value
                }
            }
        }
    ]
}
  1. 第一种为对象,该对象中所有的key-value的组合最终都会通过props传递给传过去的那个路由组件。所以数据是静态的。
  2. 第二种是布尔值,布尔值为true,则把路由收到的所有params参数通过props传给那个路由组件。只能传递params参数。
  3. 第三种是函数式,这个回调函数会接收到一个参数,这个参数就是props所在组件的 $route 对象

编程式路由导航router.push 和router.replace 和其他

  • 使用编程时路由导航,目的是为了不借助 <router-link> 实现路由跳转,让路由跳转更加灵活。
  • 主要使用到的是use vueRouter后挂载到vue实例上的那个 $router,存在于vueRouter构造函数上。
  • 接收的参数是类似to属性里面接受的那个配置对象,有path、name、query、params等属性
//利用 $router.push 和 $router.replace 实现路由跳转。
this.$router.push({
    path:'/home',
    query:{
        id:xxx
    }
})
this.$router.replace({
    name:'detail'
    params:{
        id:xxx,
        title:xxx
    }
})
//利用 $router.back 和 $router.forward 和 $router.go 实现类似 window.history.xxx 的功能

编程式路由导航的小bug

在vue-router@3的高版本中引入了promise,会导致多次点击跳转报warning。
解决方法1是在写完第一个配置对象后再接两个回调函数。

this.$router.push({
    path:'/home',
},()=>{},()=>{})

解决方法2是在router文件夹的index.js重写push和replace这个方法

const originPush = VueRouter.prototype.push
const originReplace = VueRouter.prototype.replace
VueRouter.prototype.push = function (location) { return originPush.call(this, location).catch(err => err) }
VueRouter.prototype.replace = function (location) { return originReplace.call(this, location).catch(err => err) }

路由组件缓存 keep-alive 标签

  • 由“路由的注意点”可知路由切换会销毁组件。路由组件缓存的目的是为了切换路由时候,被切换的那个页面缓存不被销毁。
  • 使用 <keep-alive></keep-alive> 标签包裹住 <router-view></router-view> 标签即可
<keep-alive include="biaodan">
    <router-view to="xxx"></router-view>
</keep-alive>
  • 注意在哪个路由组件需要keep-alive的就在使用它的router-view外面包,注意不是router-link
  • 注意如果不写include属性,则 keep-alive 包裹所有展示过的router-view 都会缓存。include接收的值是那个路由组件起的 name 属性,是组件里的name属性,是组件里的name属性。如果不单止一个,就传入数组参数 :inclue="['xxx','yyy']"

activated和deactivated 配合 keep-alive 独有的两个生命周期

activated() {
    console.log("activated被调用了");
  },
  deactivated() {
    console.log("deactivated被调用了");
  },

注意这两个生命周期只能用作在被 keep-alive 缓存的组件中使用,组件一旦被 router-view 展示,就会触发activated。
组件激活时触发activated,组件失活时触发deactivated

全局路由守卫 router.beforeEach() 和 router.afterEach()

  • 我们在暴露路由对象之前,先往router身上的beforeEach里面扔进一个函数,这个函数包含三个参数。
  • 这个 beforeEach() 什么时候被调用?初始化路由和每次启动路由规则时,例如点击跳转
  • afterEach() 初始化路由 和 每次路由跳转执行后会执行
const router = new VueRouter({ 
    routes:[
        { path:'/about', 
        component:About }, 
        { path:'/home', 
        component:Home } 
    ] 
}) 
//暴露之前使用beforeEach api并传入函数
router.beforeEach((to,from,next)=>{
    console.log(to,from,next)
})
export default router

首先 to 和 from 都是类似的是一个对象,里面包含了本次路由操作的一些信息,例如
path 所在或要跳转的路径,query和params 参数

路由的 meta 配置项

路由配置文件的数组项里的配置项可以写 meta属性,该属性一般用于携带一些自己写的数据给 beforeEach 和 afterEach 用作判断。
里面的值是一个对象,读取时从meta对象里读取数据 meta.xxx

const router = new VueRouter({ 
    routes:[
        { 
            path:'/about', 
            component:About,
            meta:{
                Auth:true,
                call:666
            }
        }, 
    ] 
}) 
router.beforeEach((to,from,next)=>{
    console.log(to.meta.call)
})

独享路由守卫,路由的beforeEnter配置项

  • 全局路由守卫挂在router上,单独某个路由需要路由守卫的话,可以单独写入配置项 beforeEnter。
  • 值是一个函数。写法跟全局路由守卫一样,是一个有 to,from,next三个参数的函数。
  • 独享路由守卫只有前置没有后置,可以和全局搭配使用,先使用全局后使用局部
const router = new VueRouter({ 
    routes:[
        { 
            path:'/about', 
            component:About,
            beforeEnter:function(to,from,next){     //也可以es6简写模式
                xxx
            }
        }, 
    ] 
}) 
router.beforeEach((to,from,next)=>{
    console.log(to.meta.call)
})

组件内路由

这个路由是写在vue组件里的一个配置项,通常写在路由组件里面。
beforeRouteEnter是通过路由规则进入守卫时被调用,beforeRouteLeave时通过路由规则离开守卫时被调用,注意都是通过路由规则,直接使用组件时这两个配置项是不生效的。还有一个beforeRouteUpdate

export default {
    ...
    beforeRouteEnter(to,from,next){
        //通过路由规则进入守卫时被调用
    }
    beforeRouteLeave:function(to,from,next){
        //通过路由规则离开守卫时被调用
    }
}

路由 hash模式与 history模式

  • 我们前面的路由知识有两个点,一是路径都带有 /#/ ,而是router配置对象只有一个routes。
  • 带有 /#/ 是我们使用的是默认的hash模式, /#/ 后面的东西是不包含在http请求中带给服务器的。
  • 想要改变就需要往router配置对象传入mode属性,值为history。
  • hash模式:
    1. 地址中永远带着 # 号,不美观。
    2. 若以后降低至通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好
  • history模式:
    1. 地址干净,美观。
    2. 但页面部署上线时需要后端人员支持,解决刷新页面服务端404的问题。例如nodejs的connect-history-api-fallback

重定向redirect 掩盖路由/别名alias

重定向通过routes配置来完成,例如从 /a 重定向到 /b

const router = new VueRouter({
    routes:[
        {
            path:'/a',
            redirect:'/b'
            //重定向也可以是一个命名的路由
            //redirect:{name:'foo'}
            //甚至可以是一个方法
            //redirect:function(to){
                //方法接收 目标路由 作为参数,return 重定向的字符串路径/路径对象
            }
        }
    ]
})

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]})

Vue UI组件库(以ElementUI为例)

想要使用UI组件库,先需要安装

npm i element-ui

然后参照官方文档引入及使用

import Vue from 'vue';
import App from './App.vue'

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

按需引入

上面代码 Vue.use(ElementUI) 一旦编译,就会把整个组件库引入,造成js文件过大的问题,可以参考官方文档使用按需引入

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • #vue2笔记 ##脚手架文件结构 ├──node_modules ├──public │├──favicon.i...
    Daydream_许多阅读 424评论 0 0
  • 笔记 脚手架文件结构 关于不同版本的Vue vue.js与vue.runtime.xxx.js的区别:vue.js...
    sskingfly阅读 139评论 0 0
  • 什么是Vue 是一套用于构建用户界面的渐进式 javascript 框架(渐进式:想用什么就用什么不必全都用) 在...
    王果果阅读 4,743评论 0 14
  • _________________________________________________________...
    fastwe阅读 1,365评论 0 0
  • 源码相关的文章确实不好写,一个是每个人基础功不一样,我觉得说的清楚的东西可能对到别人依旧含糊,一个是对一些逻辑的理...
    羽晞yose阅读 447评论 1 2