Vue2

作者: Zindex | 来源:发表于2022-08-18 16:26 被阅读0次

    初识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文件过大的问题,可以参考官方文档使用按需引入

    相关文章

      网友评论

          本文标题:Vue2

          本文链接:https://www.haomeiwen.com/subject/bybegrtx.html