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应用,整个项目只有一个页面,根据路由匹配加载不同的组件。

相关推荐
hongkid7 分钟前
React Native 如何打包正式apk
javascript·react native·react.js
李少兄9 分钟前
简单讲讲 SVG:前端开发中的矢量图形
前端·svg
前端小万11 分钟前
告别 CJS 库加载兼容坑
前端·前端工程化
恋猫de小郭11 分钟前
Flutter 3.38.1 之后,因为某些框架低级错误导致提交 Store 被拒
android·前端·flutter
JarvanMo15 分钟前
Flutter 需要 Hooks 吗?
前端
光影少年25 分钟前
前端如何虚拟列表优化?
前端·react native·react.js
Moment27 分钟前
一杯茶时间带你基于 Yjs 和 reactflow 构建协同流程图编辑器 😍😍😍
前端·后端·面试
菩提祖师_41 分钟前
量子机器学习在时间序列预测中的应用
开发语言·javascript·爬虫·flutter
invicinble44 分钟前
对于前端数据的生命周期的认识
前端
PieroPc1 小时前
用FastAPI 后端 和 HTML/CSS/JavaScript 前端写一个博客系统 例
前端·html·fastapi