02、Vue2之零基础到精通

Vue是渐进式JavaScript框架,该笔记讲解的是Vue2基础。

Part1------Vue2基础

1、安装Vue开发环境

  1. 下载依赖包vue.js,并在html文件中引入;
  2. 关闭控制台提示"打开生产模式"
  3. 关闭控制台提示"安装开发者工具"
  4. 配置页签图标
  5. 尝试打印Vue构造函数(直接复制favicon.ico到根目录)

2、Vue初体验

js 复制代码
<!-- 准备一个容器 -->
<div id='demo'>
    <!-- demo容器中的代码称为模板,Vue+HTML-->
    <!-- data中的数据发生改变,模板中用到的数据就会自动更新 -->
    <p>我是{{stuName}},今年{{age}}岁</p>
</div>

<script>
    //创建一个Vue的实例对象
    //传入一个配置对象(配置对象与普通对象的区别是:键名固定)
    new Vue({
        //键名:el(element):用于指定当前Vue实例对象服务于哪个容器
        //键值:css选择器字符串
        el:'#demo',
        //键名:data用于配置数据,data值可以直接在模板中使用
        //键值:data值暂时是一个对象,后期会写成一个函数
        data:{
            stuName: 'Evelyn',
            age: 22
        }
    })
</script>
  • 容器和Vue实例是一一对应的,(一夫一妻制),每一个实例会管控一个根容器。
  • 真实开发中,整个Vue项目中,只有一个Vue实例,会搭配组件一起使用。
  • {{xxx}}中的 xxx 要写js 表达式,且xxx 可以自动读取到data中的属性。

语句和表达式:

表达式:表达式会产生值,可以放在任何一个需要值的地方(let+变量能接收值)

如:Number类型、String类型、三元表达式、map循环、加减乘除运算

语句/代码:不会产生值,只是控制代码走向

如:if...else判断、for循环、try...catch捕获异常

【注】如果实例管控的根容器,使用了实例中data没有定义的属性,

3、模板语法

在Vue世界中,有两种模板语法:插值语法和指令语法。

  1. 插值语法:专用于操作标签体 ,也称为胡子语法 {{ ... }}
  2. 指令语法:专用于操作标签(标签体、标签属性、标签事件、循环标签)
3-1 插值语法

<div a='1+1' v-bind:b='1+1'></div> 属性值结果:a='1+1' b='2'

3-2 指令语法
(1) v-bind

v-bind:专用于操作标签属性v-bind:标签属性="变量读取的表达式"

v-bind的简写形式:<a :href="url">去百度</a>

(2) v-model

v-model:用于实现数据的双向绑定,双向绑定只能用于输入/表单类元素,如:input、CheckBox、radio这些输入类表单(可以交互)v-model:value='xxx'

v-model:简写形式:`v-model='xxx'

4、数据绑定

Vue中有两种绑定数据的方式:

  • 单向数据绑定(v-bind):数据只能从data流向页面。
  • 双向数据绑定(v-model):数据不仅能从data流向页面,也能从页面流向data

【注】:插值语法、v-bind都是单向绑定,v_model是双向绑定。

html 复制代码
<div id="root">
    单向绑定:<p>我是{{stuName}}</p>
    单向绑定:<a v-bind:href="url">去百度</a>
    双向绑定:<input type="text" v-model:value="slogan">
    单向绑定的简写:<a :href="url">去百度</a>
    双向绑定的简写:<input type="text" v-model="address">
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            stuName: 'Evelyn',
            url: 'https://www.baidu.com',
            slogan: '巴啦啦能量',
            address: '魔仙堡'
        }
    })
</script>

5、el的三种写法:

js 复制代码
//1. 值为css选择器字符串
//2. 值为DOM元素(了解)
//3. 如下:
new Vue({
    data:{ ... }
}).$mount('#demo')

6、data的两种写法

js 复制代码
//1、data值为对象
//2、data值为函数
new Vue({
    el:'#demo',
    //对象中方法的简写(不要写成箭头函数)
    data(){
        //返回一个对象
        return {
            stuName:'Evelyn'
        }
    }
})

7、软件设计模式------MVVM

M:Model(模型) V:View(视图) VM:ViewModel(视图模型)

【注】M其实就是数据,V是页面,VM是桥梁

【注】因此常用view model的缩写vm这个变量,表示Vue的实例

8、vm对象

vm是Vue的实例对象,是通过new Vue({...})得到的!

VM身上的属性:

  1. $... 给程序员使用
  2. _... 底层使用
  3. data中的数据(键名)

在模板里面可以写什么?VM身上以及沿着原型链身上的(vm------>Vue------>Object)

9、vue2响应式原理 ------ Object.defineProperty

vue2之所以能够实现响应式(数据变化影响页面变化,数据驱动视图渲染),是由Object.defineProperty实现的。

Object.defineProperty用于给一个对象追加属性,且可以进行高级定制。

  • get方法,也称为getter,要有返回值;

getter何时调用? ------ 要读取某一个值的时候,调用getter(getter中的this指向当前操作的对象)

  • set方法,也称为setter;

setter何时调用?------ 修改/设置对象的;

js 复制代码
let p1 = {}
//'属性名称',要加引号
Object.defineProperty(p1 , ' age ' , {
    value : 22 , 
    enumerable : true , //控制属性是否可以被枚举
    writable : true , //控制属性是否可以被修改
    configurable : true , //控制属性是否可以被删除
    
    // get函数(getter)中的this是谁? ------ 当前对象(person)
    get : function get(){
        console.log('getter执行了' , this)
        return number
    },
          
    // set函数(setter)何时会被调用? ------ 有人修改person对象的age属性时执行 
    set : function set(value){
        console.log('有人修改了person的age属性,值为:' , value , this)
        number = value
    }
})
delete p1.age;
console.log(p1.age);

10、Vue中的数据代理⭐

实例对象vm身上的_data属性{name:''},原本访问vm._data.name,通过数据代理,可以直接访问Vue实例对象,进而访问其身上的属性。

11、Vue中的事件绑定 v-on

Vue中使用v-on:click=事件回调名称,可简写:@事件回调名称=clickHandler

【注】:methods 中的方法:一定要写成普通函数,只有这样this才指向vm,实现事件中能使用data 由vue管理的函数,都要写成普通函数!!!

12、Vue中的事件传参⭐

  • 1、不传参(回调之后不加括号) @click="clickHandler"

事件回调中会收到一个事件对象参数event。

  • 2、传1个/多个参数@click="clickHandler(100 , 200 , ...)"

事件回调中会收到一个/多个参数(100,200, ...)

  • 3、传参+事件对象参数@click="clickHandler(100,$event)"

事件回调中,可以收到事件对象参数,也可以收到传递过来的其他参数。

  • 4、传递的参数也可以是data中的数据@click="clickHandler(name)"

注意:在Vue中模板数据,不需要写this。

13、事件修饰符

  • 1、prevent 阻止默认行为(如:阻止表单中按钮的默认提交);
  • 2、stop 阻止事件冒泡;
  • 3、once 事件只能触发一次;

【注】:修饰符可以串联 @click.prevent.stop='事件'

14、键盘事件

@keyup.enter='事件名' 【注】:(容器)模板中的内容已经在读取vm了,所以不需要再添加this。但是script标签中不能直接找,否则会以为是变量,因此要借助于this!

15、计算属性

计算属性是指:要用的数据/属性不存在,要根据已有数据/属性计算得来。

【注】计算属性底层是通过 Objcet.defineProperty实现的(getter和setter)

【注】计算属性的灵魂是"return"

15-1 前言案例------获取全名

不使用计算属性,在methods中实现获取全名

js 复制代码
<div id="demo">
    姓:<input type="text" v-model="firstName">
    名:<input type="text" v-model="lastName">
    全名:<span>{{getFullName()}}</span><br>
    全名:<span>{{getFullName()}}</span><br>
    全名:<span>{{getFullName()}}</span><br>
    全名:<span>{{getFullName()}}</span><br>
    全名:<span>{{getFullName()}}</span><br>
    全名:<span>{{getFullName()}}</span><br>
    全名:<span>{{getFullName()}}</span><br>
    <p>计数器 <span>{{num}}</span></p>
    <button @click="add">+10</button>
</div>

<script type="text/javascript">
    new Vue({
        el: '#demo',
        data: {
            firstName: 'song',
            lastName: 'Eveyln',
            num: 100
        },
        methods: {
            getFullName() {
                console.log('@')//测试方法调用
                return this.firstName + this.lastName
            },
            add() {
                this.num += 10;
            }
        }
    })
</script>

【存在问题】:

  • 没有缓存,多次使用同样的值,函数会执行多次,效率不高。
  • 无关数据若发生变化,也会导致方法执行。(如案例中的num属性变化)
15-2 获取全名案例------计算属性实现
js 复制代码
<div id="demo">
    姓:<input type="text" v-model="firstName">
    名:<input type="text" v-model="lastName">
    全名:<span>{{fullName}}</span>
    <button @click="changeFullName">修改fullName</button>
</div>

<script type="text/javascript">
    new Vue({
        el: '#demo',
        data: {
            firstName: 'song',
            lastName: 'Evelyn'
        },
        methods: {
            changeFullName() {
                this.fullName = '宋威龙'
            }
        },
        computed: {
            fullName: {
                // getter中的this是谁?vm
                // getter何时调用?
                // 1.初次读取 
                // 2.计算fullName属性时所依赖的数据发生变化时
                get() {
                    console.log('getter被调用...')
                    return this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + '-' + this.lastName
                },
                
                // setter中的this是谁?vm
                // setter何时调用?
                // 当且仅当fullName被修改时(直接给fullName赋值,而不是他所依赖的)
                set(value) {
                    console.log(value)//宋威龙
                    console.log('setter被调用...')
                }
            }
        }
    })
</script>

【注】:通过在输入框修改firstName和lastName的方式,从而间接修改fullName的方式,不会调用setter,必须是将fullName=...,直接重新的赋值才算是修改fullName,进而可触发setter。

15-3 计算属性的简写

只有当计算属性不会被修改,(不需要 set函数时),才能用简写形式。

【注】:如果在简写形式中,计算属性使用了v-model双向数据绑定,如果在表单中修改内容,会报错,因为没有setter,如姓名案例中直接修改fullName=...

js 复制代码
computed: {
    // 计算属性fullName是一个属性,fullName不是一个函数,只是简写形式,而是指返回值
    fullName() {
        console.log('getter被调用...')
        return this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + '-' + this.lastName
    },
}

16、监视属性

监视属性又称侦听器 ,其作用是:当被监视的属性变化时, 回调函数handler将会自动调用。

  1. Vue底层本身就有监视数据的功能,数据变,页面上用到数据的地方就会自动更新(数据驱动视图);
  2. Vue给程序员也提供了监视配置,用于让程序员在数据变化时,执行更多的逻辑。
js 复制代码
new Vue({
    watch:{
        //要监视的属性名
        isHot:{
            //1.handler何时执行?当被监视的属性变化时, 回调函数handler自动调用
            //2.handler中this指向?vm
            handler(newValue , oldValue){
                //第一个形参:newValue,现在的值
                //第二个形参:oldValue
            }
        }
    }
})
  • 被监视的可以是data数据/属性,也可以是计算属性/数据;
  • 监视的属性必须存在,才能进行监视,若监视了不存在的属性,也不会报错!
16-1.立即监视

Vue初次渲染时,数据还没有发生变化,就调用一下监视的回调函数handler

配置:immediate:true

16-2.深度监视(不常用)

配置深度监视可以监测对象内部属性的改变(对象嵌套很深时要用到)

配置: deep:true

html 复制代码
<div id="demo">
    <ul>
        <li>{{Person.name}}</li>
        <li>{{Person.age}}</li>
        <li>{{Person.mood}}</li>
    </ul>
    <button @click="changeName">修改姓名</button>
    <button @click="changePerson">修改Person整体</button>
</div>

<script type="text/javascript">
    new Vue({
        el: '#demo',
        data: {
            Person: {
                name: 'Evelyn',
                age: 22,
                mood: 'nice'
            },

        },
        methods: {
            changeName() {
                this.Person.name = 'Evelynnnnn'
            },
            changePerson() {
                this.Person = {
                    name: '宋威龙',
                    age: 23,
                    mood: 'just so so'
                }
            }
        },
        watch: {
            Person: {
                deep: true,
                handler(newValue, oldValue) {
                    // Vue中存在的问题,如果监视的是对象整体,那么新值旧值打印出来就是一样的
                    // 对象,引用数据类型,地址值不变(不是因为浏览器的惰性加载)
                    // oldValue,该参数值基本不用
                    console.log('新值', newValue)
                    console.log('旧值', oldValue)
                    console.log('handler执行...')
                }
            }
        }
    })
</script>

如果监视的是基本数据类型, 如果监视的是引用数据类型,要的是对象地址值变化,而不是对象中的某个属性发生变化

16-3 监视的简写(用得多)

当不需要【立即监视】、【深度监视】的时候,才可以用简写形式。

html 复制代码
<!-- 准备好一个容器-->
<div id="demo">
    姓:<input type="text" v-model="firstName">
    名:<input type="text" v-model="lastName">
    全名:<span>{{fullName}}</span>
</div>

<script type="text/javascript">
    new Vue({
        el: '#demo',
        data: {
            firstName: 'Evelyn',
            lastName: 'Song',
            fullName: '',
        },
        watch: {
            //需要深度监视,因此不能采用简写形式
            firstName: {
                immediate: true,
                handler(value) {
                    this.fullName = value.slice(0, 1).toUpperCase() + value.slice(1) + this.lastName
                }
            },
            lastName(value) {
                this.fullName = this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + value
            }
        }
    })
</script>

17、总结:computed 和 watch⭐

computed

  • 侧重在【算】,核心是:计算出来的值。
  • return来输出计算的结果。
  • 不能异步计算。

watch

  • 侧重在【监视】,核心是:xxx变了,我要做 ???事。
  • 无需 return,核心是内部的逻辑。
  • 能开启异步任务。
  • computed能完成的功能,watch都能完成,但computed会更简便,更语义化;

  • watch能完成的功能,computed不一定能完成(如异步功能的实现);

【注】:watch能实现异步,computed不能实现异步。因为计算属性中最关键的是return(异步任务中通常有回调函数,比如定时器、网络请求)(return是同步代码,异步代码的运行结果还没执行完毕,已经return完毕了)

js 复制代码
// 监视属性,实现异步
watch: {
    firstName: {
        // immediate: true,
        handler(value) {
            setTimeout(() => {
                this.fullName = value.slice(0, 1).toUpperCase() + value.slice(1) + this.lastName
            }, 1000)
        }
    },
    lastName(value) {
        setTimeout(() => {
            this.fullName = this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + value
        }, 1000)
    }
}

// 尝试使用计算属性,实现异步
computed: {
    fullName() {
        // 达咩!
        // 计算属性最核心的是return
        let str = ''
        setTimeout(() => {
            // 定时器函数,返回值是timerID
            str = this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + this.lastName
        }, 1000)
        return str;
        // 同步任务优先执行,模板中读取计算属性,str读取都是'',计算属性才返回
    }
}

18、Vue中函数的使用原则

【原则】:由Vue管理的函数写普通,不由Vue管理的函数写箭头

【目的】:遵循以上原则的目的是:让this指向vm!!!

【注】:因为Vue管理的普通函数,写在Vue实例中,this指向Vue实例对象------vm;而箭头函数没有自己的this,往外找,在其声明环境中(函数/块级作用域中,对象不能算作作用域),正好是vm管理的普通函数的this指向。

【注】:Vue管理的函数是指:data中的函数、methods中的函数、computed中的函数、watch中的函数...

js 复制代码
new Vue({
    el: '#demo',
    data: {
        num: 100
    },
    methods: {
        asyncAdd() {
            console.log(this) // Vue的实例对象,Vue{}
            setTimeout(() => {
                //箭头函数没有自己的this指向
                //往外看,就是asyncAdd函数所在的环境
                console.log(this)// Vue的实例对象,Vue{}
                this.num += 100;
            }, 1000)
        }
    }
})

19、条件渲染

(1)v-show

语法:v-show="表达式"

如何隐藏掉不展示的元素:style="display: none;"

(2)v-if

v-if条件判断,如果不满足条件,展示了一个空的注释<!-- -->(开发版本中) v-if v-else-if v-else 不允许被打断

【区别】:v-show处理的是display样式,v-if处理的是创建DOM元素,所以,切换频繁,真实开发中,往往使用v-if解决问题,使用v-show解决某个DOM元素时有时无。

  • v-show:设置隐藏是给绑定的DOM元素添加CSS样式:display:none,但是DOM元素仍然存在;
  • v-if:设置隐藏是将DOM元素整个删除,此时DOM元素不再存在。

20、列表渲染v-for

想生成多个谁,就在谁的身上写 v-for="p in persons"

v-for所在标签,及其所有子标签

【人员搜索案例】------watch实现

html 复制代码
<div id="demo">
    <input type="text" v-model="keyword">
    <ul>
        <li v-for="p in filPersons">{{p.name}}</li>
    </ul>
</div>

<script type="text/javascript">
    new Vue({
        el: '#demo',
        data: {
            persons: [{ id: 1, name: '宋威龙' }, { id: 2, name: '袁小宇' }, { id: 3, name: '袁美丽' }, { id: 4, name: '宋二丽' }],
            keyword: '',
            filPersons: []
        },
        //监视属性
        watch: {
            //要监视的是:页面中 keyword 的变化
            keyword: {
                //开启立即监视,
                //因为每个字符串中都包含有空字符
                immediate: true,
                handler(value) {
                    console.log(value)
                    //数组的filter方法,返回一个新数组
                    this.filPersons = this.persons.filter(item => {
                        // 字符串的includes方法,返回值是一个布尔值
                        return item.name.includes(value)
                    })
                }
            }
        }
    })
</script>

【人员搜索案例】------computed实现(easier!)

js 复制代码
new Vue({
    el: '#demo',
    data: {
        persons: [{ id: 1, name: '宋威龙' }, { id: 2, name: '袁小宇' }, { id: 3, name: '袁美丽' }, { id: 4, name: '宋二丽' }],
        keyword: ''
    },
    computed: {
        // 要计算的是展示在页面上的人员数组------filPersons
        filPersons() {
            //计算属性一定要有return
            //他返回的是经过keyword过滤后的数组
            return this.persons.filter(item => {
                return item.name.includes(this.keyword)
            })
        }
    }
})

【注】:任何一个字符串中都包含空字符串(includes和indexOf)

21、其他指令(很少用)

(1)v-text

作用:向其所在的节点中渲染文本内容。

与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

(2)v-html

作用:向指定节点中渲染包含 html结构的文本。

与插值语法的区别:

  • v-html会替换掉节点中所有的内容,{{xx}}则不会。
  • v-html可以识别 html结构。 【注】v-html存在一些安全性问题,因为结构中很有可能包含恶意脚本。
(3)v-once
  • v-once所在节点在初次动态渲染后,就视为静态内容了。
  • 以后数据的改变不会引起 v-once所在结构的更新,可以用于优化性能。
(4)v-pre

跳过这个元素和它的子元素的编译过程,一般用在大量不使用 Vue语法的结构中。

(5)v-cloak

本质是一个特殊属性,Vue接管容器后,会删掉 v-cloak属性。

使用 css配合 v-cloak可以解决网速慢时,页面展示出 {{xxx}}的问题。

22、数据劫持⭐

概念:前端的响应式框架(Vue、React)通常都会对数据进行劫持,这样当数据发生变化时,才能自动更新相关的视图,或执行其他逻辑。(响应式)

  • 读取数据:proxyGetter------>reactiveGetter

  • 修改数据:proxySetter------>reactiveSetter(改数据、更新页面)

23、总结数据代理和数据劫持(概念层面)

  • 数据代理(简单,vm身上的那点事):

    • 目的:让程序员更加方便的读取、修改到 _data中属性。

    • 原理:Object.defineProperty

    • 体现:vm身上有 _data里的所有属性,且有每一个属性,都有自己的 proxyGetterproxySetter

    • 当修改 vm上的属性时,该属性对应的 proxySetter就会调用,去修改 _data中对应的属性。
    • 当读取 vm上的属性时,该属性对应的 proxyGetter就会调用,去读取 _data中对应的属性。
  • 数据劫持(_data里的那点事):

    • 目的:为了实现响应式(什么是响应式?------ 数据变页面自动更新),有了数据劫持,就可以捕获到数据的改变,进而重新解析模板,更新界面。

    • 原理:Object.defineProperty

    • 体现:_data身上的每一个属性不直接给值,都变为:reactiveSetterreactiveGetter形式。

    • 当修改 _data上的属性时,该属性对应的 reactiveSetter就会调用。且在 reactiveSetter中Vue会:维护数据、更新页面。
    • 当读取 _data上的属性时,该属性对应的 reactiveGetter就会调用,返回对应的值。

24、Vue中操作数组(Vue3解决了)⭐

在Vue2中,永远不要通过操作数组的下标来操作元素,要使用数组的7个方法(对原数组产生影响),因为Vue底层重写了这7个原生方法。

Vue中如果要更改数组中某一个元素对象,由于没有对应的getter和setter,所以不支持修改, 在Vue中修改数组,不能通过数组下标,只能通过数组中的7个原生方法。

在Vue中,调用数组原生方法,已经不是Array.prototype上的原生方法了,而是经过了Vue封装的(验证:this.arr.splice === Array.prototype.splice,结果是false)

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

25、Vue生命周期

生命周期函数,又称生命周期钩子,又称生命周期 (所有生命周期函数中的this均指向vm)

1)初始化阶段
  • beforeCreate:数据代理、数据劫持之前

(该阶段无法访问到vm中的data、methods,也没有_data)

  • created:完成数据代理、数据劫持

(该阶段可以访问到vm中的data、methods,以及_data)

2)挂载阶段
  • beforeMount(挂载之前)

将虚拟DOM转化为真实DOM,插入页面。

  • mounted(挂载完毕)⭐

该阶段:对DOM操作、开启定时器、发送ajax、订阅消息、绑定自定义事件

3)更新阶段
  • beforeUpdate(更新之前)

数据更新,页面没有更新

  • updated(更新完毕)

数据更新,页面随之更新

4)销毁阶段
  • beforeDestroy(vm销毁之前)⭐

关闭定时器、取消订阅、解绑自定义事件

  • destroyed(已销毁)

vm还在,还能打印输出,但是失去了响应式的能力(数据驱动视图)

  1. 哪个钩子中不能访问data中的数据、methods中的方法? ------ beforeCreate
  2. 想给vm上追加一些属性,最早可以在哪个钩子中操作? ------ beforeCreate
  3. data中的数据、methods中的方法,最早可以在哪个钩子中获取? ------ created
  4. 哪个钩子中数据和页面其实是不同步的?------ beforeUpdate
  5. 常用的钩子有哪些?
  • mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
  • beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

Part2------Vue2脚手架

  • Step1:全局安装webpack和webpack-cli两个包。npm i webapck -g,npm i webpack-cli -g
  • Step2:在你想要放置项目的目录下,使用vue create 项目名称,创建一个基于vue-cli脚手架的Vue项目;
  • Step3:记得安装less-loader,npm i less less-loader 【注】:安装此插件,可以识别Vue2的语法(.vue文件有高亮)

1、组件

1-1 对组件的理解

组件化是指每一个组件中包含html、css、js这三部分构成的一个.vue文件,可复用。

html结构------css样式------js交互

  1. template------结构
  2. script------脚本
  3. style------样式
1-2 组件的基本使用
  • Step1:创建/定义一个组件Vue.extend({配置对象})

【注意】:组件中配置项data:要写成函数的形式(return),这是因为每次调用data函数,都会在堆内存形成不同的地址空间,既是修改属性值,也不会影响其他人读取。如某一组件调用多次,它们之间互不干扰(数据不会被污染)。

js 复制代码
const 组件名 = Vue.extend({
    //html结构
    template:``,
    //数据
    data(){
        return {...}
    },
    //交互
    methods:{
        show
    }
})
  • Step2:注册组件 增加配置项 components:{}
js 复制代码
new Vue({
    el : '#demo' ,
    // 传入一个对象,,每一个组件都是键值对
    // 键名:自定义,在调用组件时使用,一般要和定义组件同名
    // 方便ES6中,键名和键值重复,对象的简写
    components : { 组件名1 , 组件名2 }
})
  • Step3:调用组件

【注】:最终容器/模板中什么都不写,在vm中的template配置项中调用组件。

【注】:data中以后都只写函数形式+return!!!

【注】:注册组件时用什么,调用组件时就用什么

【注】:定义组件时,强烈建议:定义组件的name配置项,这样不管怎么注册,怎么调用,在开发者工具中都是你定义好的name值,一般情况下,三者保持一致!

1-3 组件命名
  1. 一个单词组成:
  • 第一种写法:(首字母小写):user
  • 第二种写法:(首字母大写):User ------ 推荐
  1. 多个单词组成:
  • 第一种写法:kebab-case命名,例如:evelyn-song
  • 第二种写法:CamelCase命名,例如:evelynSong

【注】:如果要在容器 / 模板中调用组件,组件调用应该是双标签,不然有bug,但是一般我们要保持容器为空,将所有结构写在vm的template配置项

1-4 App组件的使用

为了减轻vm的负担,注册组件以及调用组件,都由App组件完成,boss只负责和App组件对接。

1-5 全局注册组件的使用

如果很多组件都要用到某一组件,全局注册组件,这样每次只需调用

Vue.component('组件名',组件名)

【注】:全局注册组件,要写在创建组件之后。不然会报错;

【注】:

  • Vue哪来的?------ 引入来的
  • VueComponent哪来的?------ Vue.extend(),方法调用时的返回值 在调用组件时,,底层会找到VueComponent构造函数,通过new VueComponent,传入我们在创建组件(Vue.extend())时的配置对象,VueCompomnent的实例对象------组件实例对象(vc),vm和vc很相似,只不过vm中有el配置项,他可以指定服务于哪个容器。
  • 每次调用Vue.extend() ===> 生成一个VueComponent构造函数
  • 每次调用组件<组件名/> ===> new VueComponent ===> 生成一个组件实例对象(vm)
1-6 Vue.extend的简写形式

省略Vue.extend(),只写里面的配置对象。

因为底层会帮我们进行判断,如果我们没有写Vue.extend(),他会先帮我们生成一个VueComponent,然后new VueComponent生成vc

2、Vue脚手架------webpack&vite(主要针对vue3)

  • babel.configu.js: jsx------>js tsx------>ts,语法转换文件;
  • jsconfig.json:(paths:@/配置文件路径,指的是src文件夹);
  • vue.config.js:脚手架最为重要的配置文件;
  • public/index.html:主入口文件;

【注1】:关闭语法检查lintOnSave:false(在vue.config.js配置文件中);

// eslint-disbale-next-line临时关闭下一行的语法检查;

/*eslint-disable*/在该文件内关闭语法检查

【注2】:若修改src以外的文件,如配置文件,要重启脚手架npm run serve

3、局部样式 scoped

<style scoped> <style/>只能控制该组件的样式。

【原理】:css选择器:加了一个属性选择器[data-v-..],App组件一定不加scoped属性。

【区别】:在Vue中通过scoped关键字实现样式的模块化,而在React中需要手动创建xx.module.css文件来实现样式模块化。

4、ref属性

标识一个标签,获取该标签DOM元素,是id的代替者。

获取该DOM元素时,采用this.$refs.xxx

5、动态样式绑定

5-1 class类样式

class类样式有三种写法,分别是:字符串、对象、数组。

js 复制代码
<!-- 动态绑定class样式一 ------ 字符串写法:适用于类名不确定-->
<div class="basic" :class="str"> Evelyn Song </div>
<button @click="str='happy'"> Evelyn Song </button>

// css
.basic { ... }
.happy { ... }



<!-- 动态绑定class样式二 ------ 对象写法:适用于 个数确定,类名也确定,但不确定是否要使用-->
<div class="basic" :class=" obj "> Evelyn Song </div>
<div class="basic" :class="{ hasBg:true }"> Evelyn Song </div>
<!-- 通过点击事件,控制是否使用该样式 -->
<button @click="obj.hasBg = !obj.hasBg"> Evelyn Song </button>

// css
.hasBg { ... }



<!-- 动态绑定class样式三 ------ 数组写法:适用于个数、类名都不确定 -->
<!-- 在动态绑定class样式时,还可以使用三元表达式,动态地计算。 -->
<div :class="isActive ? '' : ''"> Evelyn Song </div>
5-2 style样式

style样式有三种写法,分别是:字符串、对象、数组。

js 复制代码
<template>
  <div>
    <!-- 字符串写法 -->
    <h2 :style="str">你好啊</h2>

    <!-- 对象写法,用得巨多!!! -->
    <h2 :style="obj">你好啊</h2>

    <!-- 对象写法 -->
    <h2 :style="arr">你好啊</h2>
  </div>
</template>

<script>
  export default {
    name:'App',
    data() {
      return {
        str:'color:red',
        obj:{
          color:'purple',
          border:'1px solid black',
          fontSize:'80px',
          backgroundColor:'orange'
        },
        arr:[
          {color:'purple',border:'1px solid black'},
          {fontSize:'80px',backgroundColor:'orange'}
        ]
      }
    },
  }
</script>

Part3------Vue2组件通信方式

1、props ⭐

props用于父传子,通过标签属性的方式,<组件名 :key="value"/>

Vue中,prop不能修改,但如果修改的是对象中的某;一个属性,控制台不会报错,但是不推荐!

1-1 接收props方式一 ------ 数组

props:['属性1' , '属性2' , '属性3']

1-2 接收props方式二 ------ 限制数据类型
js 复制代码
props:{
    name:String,
    age:Number
}
1-3 接收props方式三 ------ 限制数据类型 + 配置必要性 + 指定默认值
js 复制代码
props:{
    name:{
        type:String,
        default:'Evelyn',
        required:true
    }
}
1-4 prop实现子传父

父给子传递一个函数,在子组件中调用函数,并传递参数。(因为不能在子组件中修改props数据,所以在子组件中调用函数,通过传参的方式,然后在父组件中真正去修改。)

2、v-model ⭐

2-1. v-model 绑定在表单元素上

为什么在表单元素上,要对v-model进行拆分?

因为双向绑定的数据,有可能是来自props,而props中的数据不能直接修改,所以要进行一个拆分,针对三种表单元素的类型:

  • (1)普通输入框 ------ 拆分为 value属性input事件;

  • (2)radio单选框 ------ 拆分为 checked属性input/change/click事件

  • (3)checkbox复选框 ------ 拆分为checked属性input/change/click事件

  • 【注】:区分input事件和change事件触发条件的区别?

    • change:输入框内容发生变化 + 输入框失去焦点 ==> 触发
    • input :输入框内容发生变化 ==> 触发
2-2. v-model 绑定在自定义组件上

3、自定义事件

自定义事件是实现子组件给父组件传递数据。 触发:this.emit ,解绑:this.off

【注1】:自定义事件和props子传父是不一样的!!!

  • 自定义事件------在父组件中,给子组件身上绑定一个自定义事件<Child @send-toy="getToy"/>,然后在子组件中触发事件,并传递数据,在父组件中设置回调,接收数据。
  • props------ 是通过标签属性传递一个函数,这个函数要在子组件中调用。

【注2】: 写在标签上的是原生DOM事件,写在组件上的就是自定义事件; 而内置事件是靠鼠标键盘等触发的,自定义事件是靠$emit触发的。

【注3】:如果非要在组件上绑定原生DOM事件(点击事件),需要使用native修饰符,并且会把原生事件加在组件最外侧元素上。

【注4】:官方推荐,自定义事件命名方式,要写成串式,比如:@send-data形式。

4、全局事件总线

总线适用于任意组件间通信,但是总线一般用于两个组件之间的临时通信。

  • Step1:安装总线
js 复制代码
new Vue({
    beforeCreate() {
      Vue.prototype.$bus = this
    }
}) 
  • Step2:使用总线

    • 需要数据的一方,绑定自定义事件,留下回调。
    js 复制代码
    methods: {
      test(value){
        // 根据需求,编写收到数据后的逻辑。。。
      }
    },
    mounted() {
      // 给$bus绑定事件并指定事件的回调,只要$bus的xxx事件被触发,test方法就会调用。
      this.$bus.$on('xxxx',this.test) 
    }
    • 提供 数据的一方,触发自定义事件this.$bus.$emit('xxx',数据)
  • Step3:解绑自定义事件(最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件,否则会导致$bus很"重"!)

5、自定义指令

自定义指令,目前项目应用是按钮级别的权限,后续补充。。。

  • 局部自定义指令

新增配置项,directives:{ 指令名 : 回调名 }

  • 全局自定义指令
js 复制代码
// 在main.js中全局注册自定义指令v-permissionbtn
app.directive('permissionbtn', (el, binding) => {
      // el表示是当前绑定该自定义指令的元素,可以直接操作DOM元素
      // binding是一个对象
      if ( !userInfoStore.userInfo.buttons.includes(binding.value) ) {
        el.remove()
      }
})

6、插槽

以前,在父组件中调用子组件,直接调用即可<Child></Child>,子组件开始标签和结束标签之间的内容,不会被显示出来。但是使用插槽之后,是由父组件决定子组件中的模板结构,<Child> ...HTML结构... </Child>,在子组件中需要使用<slot></slot>标签开一个槽位(占位作用)。插槽分为默认插槽、具名插槽、作用域插槽。

6-1 默认插槽
js 复制代码
// 父组件中
<Child>
    <!-- 默认插槽 -->
    <div>
        <h2>todos列表</h2>
        <ul>
          <li v-for="item in 父组件data数据" :key="item.id">{{ item.title }}</li>
        </ul>
    </div>
</Child>

// 子组件中
<slot></slot>
6-2 具名插槽

当存在多个插槽时,为了区分,所以采用具名插槽加以区分。

【注】:子组件的slot标签上新增一个name属性name="xxx",父祖家中的组件标签中新增一个slot属性slot="xxx"

6-3 作用域插槽

作用域插槽,是因为子组件想要给父组件传递数据,或者说,插槽模板中的数据要由子组件决定。(需要注意的是,在作用域插槽中,最外层需要有一层template标签包裹)

js 复制代码
// 父组件中,直接使用v-slot,可以接收子组件通过标签属性传递过来的数据。
<Child>
    <template v-slot="{ todos }">
        <ol>
              <li v-for="n in todos" :key="n.id">{{n.title}}</li>
        </ol>
    </template>
</Child>

// 子组件中(子组件中data数据)
<slot :todos="todos"></slot>

7、vuex ⭐

在Vue中实现集中式状态(数据)管理的一个Vue插件,专门管理(读/写)多个组件的共享状态(数据),一种重要的组件通信方式,且适用于任意组件间通信。

Vuex中主要是由actions、mutations、state三大部分组成,actions中主要执行一些异步任务,只有在mutationa中才可以修改数据。

7-1 搭建Vuex环境
  • Step1:安装Vuex npm i vuex@3(vue2中使用3版本,Vue3中使用4版本)
  • Step2:创建文件 /src/store/index.js
js 复制代码
import Vuex from 'vuex'
import Vue from 'vue'

// 使用vuex插件
Vue.use(Vuex)

export default new Vuex.Store({
    actions:{ ... },
    mutations:{ ... },
    state:{ ... }
})
  • Step3:在main.js文件中导入store对象
js 复制代码
import Vue from 'vue'
import App from './App.vue'
//导入仓库对象
import store from './store/index'

Vue.config.productionTip = false;

let vm = new Vue({
    el: "#app",
    render: h => h(App),
    store
})
// 如何检查vuex安装是否成功?
// 在Vue实例对象vm、组件实例对象vc身上看是否有$store,开发者工具是否有vuex的选项卡
console.log(vm);

【注1】:npm view vuex versions 可以查看vuex包的所有版本

【注2】:关于Vuex安装版本错误

【注3】:不要在main.js文件中,配置Vue.use(Vuex),因为在脚手架中会有变量提升,会提升所有的import语句。

7-2 Vuex案例练习(state------actions------mutations)

/src/components/Count.vue文件:

js 复制代码
<template>
  <div id="child">
    <h2>当前sum的值是:{{ this.$store.state.sum }}</h2>
    <select v-model="num">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increase">+</button>
    <button @click="decrease">-</button>
    <button @click="increaseOdd">sum为奇数时再加</button>
    <button @click="increaseWait">等两秒再加</button>
  </div>
</template>

<script>
export default {
  name: "Count",
  data() {
    return {
      num: 1,
    };
  },
  methods: {
    // 增加
    increase() {
      this.$store.dispatch("add", this.num);
    },
    // 减少
    // 当actions中代码业务逻辑很简单时,可以直接在$store对象身上调用commit方法
    decrease() {
      this.$store.commit("DEC", this.num);
    },
    // sum是奇数时再加
    increaseOdd() {
      this.$store.dispatch("addOdd", this.num);
    },
    // 异步,等一秒再加
    increaseWait() {
      this.$store.dispatch("addWait", this.num);
    },
  },
};
</script>

/src/store/main.js文件:

js 复制代码
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)

export default new Vuex.Store({
    actions: {
        add(context, value) {
            // context:集装箱对象,属性值有commit,dispatch,getters,rootGetters,rootState,state
            // value:表示从组件传递过来的数据,接着还来传递给mutations
            context.commit('ADD', value)

        },
        addOdd({ state, commit }, value) {
            if (state.sum % 2) {
                commit('ADD', value);
            }
        },
        addWait({ commit }, value) {
            setTimeout(() => {
                commit('ADD', value)
            }, 1000);
        }
    },
    mutations: {
        ADD(state, value) {
            state.sum += value
        },
        DEC(state, value) {
            state.sum -= value
        }
    },
    state: {
        sum: 1
    }
})

【注1】:读取vuex中的数据:this.$store.state.sum(在模板中也要写this)

【注2】:Vue Component相当于是顾客,dispatch是点单,commit是服务员给后厨提交订单,Mutations是后厨,只有"后厨"才可以真正地修改state,state相当于是做菜的原材料;

【注3】:在mutations中,不能写任何的判断,不能写网络请求,单纯写修改数据,且只有在mutations修改数据,开发者工具才能捕捉到,所有的业务逻辑(判断、异步),都写在actions中。

【注4】:如果在actions中不涉及判断逻辑/异步任务,则可以在组件中直接this.$store.commit(...)。

7-3 getters

当state中的数据要经过处理之后再使用时,可以利用getters,但是要依赖于state(所以getters很像是Vuex层面的计算属性)

js 复制代码
// 在new Vuex.Store({})配置项中多加一项:getters:{ return ... }
// state表示最新的状态数据
getters(state){
    //getters也有返回值
    return state.name.toUpperCase()
}

使用时:this.$store.getters.xxx

7-4 mapState和mapGetters

代码风格指南表明,模板中尽量简单,所以设计了mapStatemapGetters

  • mapState:把Vuex中的state中的数据读出来,生成对应的计算属性
  • mapGetters:把Vuex中的getters中的数据读出来,生成对应的计算属性

【注】:mapState属性写在计算属性中,使用展开运算符computed:{ ... },需要注意的是!使用之前要引入,import { mapState } from 'vuex',使用之前,在组件中读取state中的数据this.$store.state.xxx,使用了mapState之后,直接xxx

js 复制代码
computed: {
    // mapState------写法一------对象写法
    ...mapState({ sum: "sum", name: "name" }),

    // mapState------写法二------数组写法(常用!!)
    ...mapState(["sum", "name"]),

    // mapGetters------写法一------对象写法
    ...mapGetters({ upperName: "upperName", bigSum: "bigSum" }),
    
    // mapGetters------写法二------数组写法
    ...mapGetters(["upperName", "bigSum"]),
  },
7-5 命名空间(Vuex的模块化)

目的:将Vuex中的state,actions,mutatins进行分类,且每个分类都对应一个.js文件。

  • Step1:创建vuex中的user模块:src/store/modules/user.js,用于保存用户相关数据。
js 复制代码
const actions = {
  addAge({state,commit},value){
      commit('ADD_AGE',value)
  }
}
const mutations = {
  ADD_AGE(state,value){
    state.age += value
  }
}
const state = {
  age:18
}
export default {
  namespaced:true, // 开启命名空间
  actions,
  mutations,
  state
}
  • Step2:创建vuex中的home模块:src/store/modules/home.js,用于保存主页相关的数据;
js 复制代码
const actions = {
  addA({state,commit},value){
    commit('ADD_A',value)
  }
}

const mutations = {
  ADD_A(state,value){
    state.a += value
  }
}

const state = {
  a:100,
  b:200
}

export default {
  namespaced:true, // 开启命名空间
  actions,
  mutations,
  state
}
  • Step3:在src/store/index.js中合并:home模块、user模块
js 复制代码
import Vue from 'vue'
import Vuex,{Store} from 'vuex'
import user from './modules/user'
import home from './modules/home'

Vue.use(Vuex)
const actions = {}
const mutations = {}
const state = {}

export default new Store({
  actions,
  mutations,
  state,
  modules:{
    user,
    home
  }
})
  • Step4:在组件中使用Vuex中的数据

    • ...mapState(所属命名空间名称,[数据1,数据2])
    js 复制代码
    ...mapState('user',['name','age']),
    ...mapState('home',['a','b'])
    • this.$store.dispatch('所属命名空间名称/action名',数据)
    js 复制代码
    this.$store.dispatch('user/addAge',1)
    this.$store.dispatch('home/addA',1)

【注】:Vue3就没有命名空间的概念了,因为pinia本身就有模块化。

Part4------Vue中的路由⭐

前端路由不同于后端路由,后端路由每次都要带着URL向服务器发请求,

前端路由,是基于SPA应用,整个项目只有一个页面,根据路由匹配加载不同的组件。

相关推荐
秦jh_13 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21326 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy27 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun2 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法