【尚硅谷】Vue.js从入门到精通笔记

【尚硅谷】Vue.js从入门到精通笔记,第1张

目录 第1章:Vue核心1-1.Vue简介1-1-1.什么是Vue1-1-2.Vue的特点1-1-3.搭建Vue开发环境1-1-4.创建Vue对象 1-2.模板语法1-3.数据绑定1-4.el和data的两种写法1-5.MVVM模型1-6.数据代理1-6-1.回顾Object.defineProperty方法1-6-2.何为数据代理1-6-3.Vue中的数据代理 1-7.事件处理1-7-1.事件的基本使用1-7-2.事件修饰符1-7-3.键盘事件1-7-4.修饰符连写 1-8.计算属性与监视1-8-1.姓名案例1-8-2.计算属性总结1-8-3.天气案例1-8-4.监视属性 1-9.class与style绑定1-10.条件渲染1-11.列表渲染1-11-1.基本列表1-11-2.key的原理1-11-3.列表过滤1-11-4.列表排序1-11-5.更新时的一个问题1-11-6.Vue检测数据改变的原理(对象)1-11-7.模拟一个数据监测1-11-8.Vue.set 和 vm.$set1-11-9.Vue监测数据改变的原理(数组)1-11-10.总结Vue数据监测 1-12.收集表单数据1-13.过滤器1-14.内置指令与自定义指令1-14-1.内置指令1-14-1-1.v-text1-14-1-2.v-html1-14-1-3.v-clock(没有值)1-14-1-4.v-once(没有值)1-14-1-5.v-pre(没有值) 1-14-2.自定义指令 1-15.生命周期1-15-1.生命周期引入1-15-2.生命周期分析与总结 第2章:Vue组件化编程2-1.模块与组件、模块化与组件化2-1-1.模块2-1-2.组件2-1-3.模块化2-1-4.组件化 2-2.非单文件组件2-2-1.基本使用2-2-2.几个注意点2-2-3.组件的嵌套2-2-4.VueComponent2-2-5.vm和vc的区别2-2-6.一个重要的内置关系 2-3.单文件组件 第3章:使用Vue脚手架3-1. 脚手架的基本使用3-1-1. 初始化脚手架3-1-2. 分析脚手架结构3-1-3. render函数3-1-4. 修改脚手架默认配置 3-2. ref属性3-3. props属性3-4. mixin属性3-5. 插件3-6. scoped样式3-7. Todolist案例3-8. props子组件通信父组件【通信方法一】3-9.浏览器本地存储(webStorage)3-10.组件自定义事件【通信方法二】3-11.全局事件总线(GlobalEventBus)【通信方法三】3-12.消息订阅与发布(pubsub)【通信方法四】3-13.nextTick3-14.Vue封装的过度与动画 第4章 :Vue中的ajax4-1. vue脚手架配置代理4-2. github案例4-3. vue-resource(了解)4-4. 插槽 第5章:Vuex5-1 vuex 简介5-2.求和案例(纯vue版)5-3.Vuex工作原理5-4.搭建vuex环境5-5.求和案例(vuex版)5-6.vuex开发者工具的使用5-7.vuex的基本使用使用5-8.getter配置项5-9.四个map方法的使用5-10.多组件数据共享5-11.模块化+命名空间 第6章:Vue-router6-1. 路由的简介6-2.路由基本使用6-3.几个注意点6-4.路由(多级)嵌套6-5.路由的query参数6-6.命名路由6-7.路由的params参数6-8.路由的props配置6-9.router_link和replace属性6-10.编程式路由导航6-11.缓存路由组件6-12.两个新的生命周期钩子6-13.路由守卫6-17.histroy模式与hash模式 第7章:Vue UI组件库7-1.element-ui基本使用7-2.element-ui按需引入

第1章:Vue核心 1-1.Vue简介 1-1-1.什么是Vue

Vue是一套用于构建用户界面的渐进式JavaScript框架

构建用户界面: 把拿到的数据通过某种办法变成用户可以看到的界面
 
渐进式: 自底向上逐层的应用,从一个轻量小巧的核心库逐渐递进到各式各样的Vue插件库

1-1-2.Vue的特点 采用组件化模式,提高代码复用率、且让代码更好维护。声明式编码,让编码人员无需直接 *** 作dom,提高开发效率。使用虚拟DOM + 优秀的Diff算法,尽量复用DOM节点。

组件化:即把一个页面由不同的功能分为不同的组件
 
声明式编码:给一个声明,它就自己去完成所有命令
 
命令式编码:一步一步得去执行命令,每一步都要去命令
 
diff算法:可以复用节点的一种算法

1-1-3.搭建Vue开发环境

vue文件引入

vue.js:开发版本(有警告和调试模式)vue.min.js:生产版本(删除了警告)

关掉Vue控制台的提示

安装Vue开发工具Vue.config.productionTip = false; 阻止 vue 在启动时生成生产提示

按shift强刷报错

在根目录放一个页签图标就可以了

1-1-4.创建Vue对象 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;root容器里的代码被称为【Vue模板】;Vue实例和容器是一一对应的;真实开发中只有一个Vue实例,并且会配合着组件一起使用;{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新

JS表达式和JS代码(语句)的区别

表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
1)a
2)a+b
3)demo(1)
4)x === y ? ‘a’ : ‘b’

js代码
1)if () { }
2)for () { }

表达式是特殊的JS语句,能控制代码能否执行的就是JS语句

    
    <div id="root">
        {}}表示,可直接访问data数据 -->
        <h1>Hello,{{name}}h1>
        <h1>我的年龄是:18h1>
    div>
    
    <script>
        // 创建Vue实例
        new Vue({
            // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
            // 也可以是节点(document.getElementById('root')
            el: '#root',
            // data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象(函数)
            data: {
                name: '尚硅谷',
                age: 18
            }
        });
    script>
1-2.模板语法

定义

容器里面的代码就是模板语法

模板语法分为两大类

差值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是 js表达式,且可以直接读取到data中的所有属性。指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件。。。)
备注:Vue中有很多的指令,且形式都是:v-???。
举例:v-bind:hred="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性。 1-3.数据绑定

Vue中有2种数据绑定的方式

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

备注

双向绑定一般都应用在表单元素上(如:input、select等)

v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值


<input type="text" v-bind:value="name">
<input type="text" :value="name">

<input type="text" v-model:value="name">
<input type="text" v-model="name">

以下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上

<h2 v-model:x="name">helloh2> 
1-4.el和data的两种写法 el有2种写法:
1)new Vue的时候指定el属性;
2)先创建Vue实例,随后再通过 vm.$mount(‘#root’) 指定el的值(mount—挂载)data有2种写法:
1)对象式
2)函数式
如何选择:目前哪种方法都可以,以后学习到组件时,data必须使用函数式,否则会报错。一个重要的原则:
只要是Vue所管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了,而是window
    const v = new Vue({
        // el: '#root', //el第一种写法
        // data: { //data第一种写法
        //     name: '尚硅谷' 
        // },
        data() { //data第二种写法
            return {
                name: '尚硅谷'
            }
        }
    })

    v.$mount('#root') //el第二种写法
1-5.MVVM模型

m就是data数据,v是模板渲染出来的页面,vm就是vue实例,用于绑定数据和dom监听,让页面与数据之间建立起一个连接

MVVM模型

M:模型(Model):data中的数据V:视图(View):Vue模板VM:视图模型(ViewModel):Vue实例对象(绑定数据,dom监听)

观察发现

data中所有的属性,最后都出现在了vm身上vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接访问 1-6.数据代理 1-6-1.回顾Object.defineProperty方法

定义

给对象定义属性,并对该属性进行一些高级的设置

参数

需要添加属性的对象 / 添加属性名称 / 配置项(配置属性值或一些其它的参数)

常用配置属性作用
value设置属性值
enumerable控制属性是否可以枚举,默认值为false
writable控制属性是否可以被修改,默认值为false
configurable控制属性是否可以被删除,默认值为false
get函数当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
set函数当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值

为什么无法直接修改person.age的值,怎么修改?

let number = 18;
let person = {
        name: '张三',
        sex: '男'
    }
Object.defineProperty(person, 'age', {
	// value:18,
	// enumerable:true,
	// writable:true,
	// configurable:true,
    get: function() {
        return number;
    },
    set: function(value) {
    number = value;
	}
})

解决方法

因为age是不可写的,可以设置writable属性或直接修改与age关联的number通过set函数把修改的值赋值给number 1-6-2.何为数据代理

通过一个对象代理对另一个对象中属性的 *** 作叫做数据代理

// 通过obj2访问并且修改obj的x
let obj = {x: 100}
let obj2 = {y: 200}
Object.defineProperty(obj2, 'x', {
    get() {
        return obj.x;
    },
    set(value) {
        obj.x = value;
    }
})
1-6-3.Vue中的数据代理 Vue中的数据代理:通过 vm对象 代理 vm._data对象 中的属性 *** 作(读/写)Vue中数据代理的好处:更加方便的 *** 作data中的数据基本原理:
通过Object.defineProperty把data对象中所有属性都添加到vm上,并为其指定一个getter/setter,通过getter/setter内部去 *** 作(读/写)data中对应的属性。

vm._data展开后你会发现它和data并不一致,这是因为Vue底层做了数据劫持而非数据代理,其目的就是为实现响应式的 *** 作。

1-7.事件处理 1-7-1.事件的基本使用

定义

使用 v-on:xxx@xxx 绑定事件,其中xxx是事件名;事件的回调需要配置在methods对象中,最终会再vm上;methods中配置的函数,不要用箭头函数!否则this就不是vm了而是window;methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;@click="demo"@click="demo($event)" 效果一致,前者默认参数就是事件对象,后者要用$event才能生成事件对象,并且可以传多个参数;

<button @click="demo">点我提示信息button>

<button @click="showInfo(66)">点我提示信息button> 

<button @click="showInfo(66,$event)">点我提示信息button>

备注

method上面的方法最终也会出现在vm上面,但它并没有作数据代理,因为没有必要,函数只会用来调,而不会改。

1-7-2.事件修饰符

prevent用法

阻止默认事件(常用)

<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息a> 

stop用法

阻止事件冒泡(常用)

<div class="demo1" @click="showInfo">
	<button @click.stop="showInfo">点我提示信息button> 
div>

once用法

事件只触发一次(常用)

<button @click.once="showInfo">点我提示信息button> 

capture用法

使用事件的捕获模式

<div class="box1" @click.capture="showMsg(1)">  
    div1
    <div class="box2" @click="showMsg(2)"> 
        div2
    div>
div>

当点击div2的时候先经过两个阶段:事件捕获=>事件冒泡,默认是事件冒泡处理事件;

捕获阶段由外往内,冒泡阶段由内往外;

self用法

只有event.target是当前 *** 作的元素时才触发事件

<div class="demo1" @click.self="showInfo">
	
	
    <button @click="showInfo">点我提示信息button>
div>

event.target在元素被 *** 作时便确定,随着冒泡往后触发的事件中的事件源对象也会是它;

self要求event.target是自身元素时才会被触发;

passive用法

先执行默认行为,后执行回调函数

<ul @wheel.passive="demo" class="list"> 
    <li>1li> 
    <li>2li>
    <li>3li>
    <li>4li>
ul>

好处:可以一定程度上进行优化,常用在移动端的项目(手机、平板),但用的比较少。

拓展

滚动事件分为scroll和wheel:

scroll:鼠标、键盘、滚轮都可以触发,但滚到底部就不会在触发(默认=>回调)wheel:只有滚轮可以触发,滚到底部依然可以触发(回调=>默认) 1-7-3.键盘事件

Vue中常用的按键别名

         回车 => enter
         删除 => delete(捕获“删除(delete)”和“退格(BackSpace)”键)
         退出 => esc
         空格 => space
         换行 => tab(特殊,必须配合 keydown 去使用,因为keyup时会切位)
         上 => up
         下 => down
         左 => left
         右 => right

Vue未提供别名的案件,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

<input type="text" placeholder="按下回车提示输入" @keyup.caps-lock="showInfo">

系统修饰键(用法特殊)

ctrl、alt、shift、meta

配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其它键,事件才被触发。(此时的e.key值是其它键,而不是系统修饰键)配合keydown使用:正常触发事件。

也可以使用keyCode去指定具体的按键(不推荐,因为不同的键盘编码可能会不统一,尽量用键名

<input type="text" placeholder="按下回车提示输入" @keyup.13="showInfo">

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


<input type="text" placeholder="按下回车提示输入" @keyup.huiche="showInfo">
1-7-4.修饰符连写

<div class="demo1" @click="showInfo">
	<a href="http://www.atguigu.com" @click.stop.prevent="showInfo">点我提示信息a>
div>

<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo">
1-8.计算属性与监视 1-8-1.姓名案例

差值语法实现:

<div id="root">
	姓:<input type="text" v-model="firstName">
	名:<input type="text" v-model="lastName">
	姓名:<span>{{firstName.slice(0,3)}}-{{lastName}}span>
div>

<script>
    const vm = new Vue({
        el: '#root',
        data: { firstName: '张', lastName: '三' }
    })
script>

methods实现:

<div id="root">
    姓:<input type="text" v-model="firstName">
    名:<input type="text" v-model="lastName"> 
    姓名:<span>{{fullName()}}span>
    <button @click="fullName">点我button>
div>

<script>
    const vm = new Vue({
        el: '#root',
        data: { firstName: '张', lastName: '三' },
        methods: {
            fullName() { return this.firstName + '-' + this.lastName } // this指向vm
        }
    })
script>

注意:methods方法在绑定事件的时候才可以省略小括号,在差值里不能

computed实现:

<div id="root">
    姓:<input type="text" v-model="firstName">  
    名:<input type="text" v-model="lastName"> 
    姓名:<span>{{fullName}}span>
div>

<script>
    const vm = new Vue({
        el: '#root',
        data: { firstName: '张', lastName: '三' },
        computed: {
            fullName: {
                get() {
                    // 此处的this是vm
                    return this.firstName + '-' + this.lastName
                }
            }
        }
    })
script>
1-8-2.计算属性总结

定义

计算属性,就是拿着写完的属性去加工计算,生成一个全新的属性。计算属性直接被挂载到vm上,直接读取使用即可(_data里面没有计算属性)。
属性描述
get(){}获取计算属性的时候执行
set(){}修改计算属性的时候执行

get有什么作用?

当有人读取fullName时,get就会被调用,且返回值就作为fullName的值。
(底层就是用Object.defineProperty的getter/setter实现的)

get什么时候被调用?

初次读取时会执行一次(往后就会取缓存里的数据)所依赖的数据发生变化时会被再次调用(所以不用担心修改了值还会从缓存里获取)

set什么时候被调用?

当fullName被修改时。如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化。

computed对比methods

computed有缓存机制(复用),效率更高、调试方便(devtools上会有一个computed的分类)。methods函数出现几次就调用几次,效率比较低。

计算属性的简写

一般来说计算属性是不修改的,更多是读取出来展示。并不是什么时候都能用简写,只有考虑读取,不考虑修改 的时候才能用简写形式。

注意

fullName后面不能加小括号,因为它只是计算属性后的结果,并不是函数。

1-8-3.天气案例

<div id="root">
    <h2>今天天气很{{info}}</h2>
    <!-- <button @click="isHot = !isHot">切换天气</button> -->
    <button @click="changeWeather">切换天气</button>
</div>
<script>
    Vue.config.productionTip = false;

    new Vue({
        el: '#root',
        data: {
            isHot: true
        },
        computed: {
            info() {
                return this.isHot ? '炎热' : '凉爽';
            }
        },
        methods: {
            changeWeather() {
                this.isHot = !this.isHot
            }
        },
    })
</script>

1-8-4.监视属性

定义

当被监视的属性变化时,回调函数自动调用,进行相关 *** 作;监视的属性必须存在,才能进行监视!!(除了data属性,computed属性也能监视)

监视的两种写法

new Vue时传入watch配置;通过vm.$watch监视;
(明确要监视哪些属性的时候用第一个。创建实例的时候不知道要监视谁,后续根据用户的一些行为然后才知道要监视哪个属性就用第二个)
属性描述
immediate (立即的)初始化时让handlder调用一下
handler(newVal, oldVal)当监视的属性发生改变的时候调用,参数可以拿到改变前后的值
deep深度监听,可以监测多层级数据的改变
// 写法一
const vm = new Vue({
    el: '#root',
    data: {
        isHot: true
    },
    computed: {
    methods: {
    watch: {
        isHot: {
            immediate: true,
            handler(newValue, oldValue) {
                console.log('isHot被修改了', newValue, oldValue);
            }
        }
    }
})

// 方法二
vm.$watch('isHot', { // 注意这里监视的属性要写字符串,配置对象的写法和上一种完全一致
    immediate: true,
    handler(newValue, oldValue) {
        console.log('isHot被修改了', newValue, oldValue);
    }
})

深度监视

Vue中的watch默认不监测对象内部值的变化(监测一层)配置deep:true可以监测对象内部值变化(监测多层)
const vm = new Vue({
    el: '#root',
    data: {
        isHot: true,
        numbers: {
            a: 1,
            b: 1
        }
    },
    computed: {
    methods: {
    watch: {
        isHot: {
        // 监视多级结构中某个属性的变化————加上引号(原始写法)   
        // 'numbers.a': {
        //     handler() {
        //         console.log('a被改变了');
        //     }
        // }
        // 监视多级结构中所有属性的变化————开启深度模式
        'numbers': {
            deep: true,
            handler() {
                console.log('numbers被改变了');
            }
        }
    }
})

监视属性的简写:配置项只有handler的时候才可以简写。

const vm = new Vue({
    el: '#root',
    data: {
        isHot: true
    },
    computed: {
        info() {
            return this.isHot ? '炎热' : '凉爽';
        }
    },
    methods: {
        changeWeather() {
            this.isHot = !this.isHot
        }
    },
    watch: {
        // 正常写法:
        /* isHot: {
            // immediate: true,
            // deep:true,
            handler(newValue, oldValue) {
                console.log('isHot被修改了', newValue, oldValue);
            }
        }, */
        // 简写:
        isHot(newValue, oldValue) {
            console.log('isHot被修改了', newValue, oldValue);
        }
    }
});

// 正常写法:
vm.$watch('isHot', {
    // immediate: true,
    // deep:true,
    handler(newValue, oldValue) {
        console.log('isHot被修改了', newValue, oldValue);
    }
});

// 简写:
vm.$watch('isHot', function(newValue, oldValue) { //注意这里不能用箭头函数,this指向会指向window(vue所管理的函数一律不用箭头)
    console.log('isHot被修改了', newValue, oldValue);
});

watch对比computed

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

还是拿上一个天气案例作对比:

const vm = new Vue({
    el: '#root',
    data: {
        firstName: '张',
        lastName: '三',
        fullName: '张-三'
    },
    watch: {
        firstName(val) {
            this.fullName = val + '-' + this.lastName;
        },
        lastName(val) {
            this.fullName = this.firstName + '-' + val;
        }
    }
})

上面代码是命令式且重复的。将他们与计算属性的版本进行比较:

const vm = new Vue({
    el: '#root',
    data: {
        firstName: '张',
        lastName: '三'
    },
    computed: {
        fullName() {
            return this.firstName + '-' + this.lastName
        }

好得多了,不是吗?

延迟一秒钟后再响应:

const vm = new Vue({
    el: '#root',
    data: {
        firstName: '张',
        lastName: '三',
        fullName: '张-三'
    },
    watch: {
        firstName(val) {
            // 注意这里用普通函数自带this 默认指向window,箭头函数没有this 会往上继承
            setTimeout(() => {
                this.fullName = val + '-' + this.lastName;
            }, 1000)
        },
        lastName(val) {
            setTimeout(() => {
                this.fullName = this.firstName + '-' + val;
            }, 1000)
        }
    }
})

watch轻松完成。

const vm = new Vue({
    el: '#root',
    data: {
        firstName: '张',
        lastName: '三'
    },
    computed: {
        fullName() {
        	// fullName值等于空,因为fullName没有返回值,返回值给了定时器的回调
        	setTimeout(() => {
    			return this.firstName + '-' + this.lastName;
			}, 1000)
        }

相比较之下,computed却无法实现,因为它无法执行异步任务。

1-9.class与style绑定

class样式

写法: xxx可以是字符串、对象、数组。字符串写法适用于:类名不确定,要动态获取。对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

style样式

:其中xxx是动态值。(注意样式名得是小驼峰):其中a、b是样式对象。
<div id="root">
    
    <div class="basic" :class="mood" @click="changeMood">{{name}}div> <br/>
    
    <div class="basic" :class="classArr">{{name}}div> <br/>
    
    <div class="basic" :class="classObj">{{name}}div> <br/>
    
    
    <div class="basic" :style="styleObj">{{name}}div> <br/>
    
    <div class="basic" :style="styleArr">{{name}}div>
div>

<script>
    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            mood: 'normal',
            classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
            classObj: {
                atguigu1: false,
                atguigu2: false
            },
            styleObj: {
                fontSize: '40px',
                color: 'red'
            },
            styleObj2: {
                backgroundColor: 'orange'
            },
            styleArr: [{
                fontSize: '40px',
                color: 'red'
            }, {
                backgroundColor: 'gray'
            }]

        },
        methods: {
            changeMood() {
                const arr = ['normal', 'happy', 'sad'];
                const index = Math.floor(Math.random() * 3);
                this.mood = arr[index];

            }
        },
    })
script>
1-10.条件渲染

两种方式

v-if
写法:
(1)v-if=“表达式”
(2)v-else-if=“表达式”
(3)v-else=“表达式”

适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。v-show
写法:v-show=“表达式”
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

备注

使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。template只能配合 v-if 使用,不能用 v-show,后者会直接显示。 1-11.列表渲染 1-11-1.基本列表

v-for指令

用于展示列表数据语法:v-for=“(item, index) in xxx” :key=“yyy”可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

备注

in可用of代替(2.6.12)(item,index)要打上小括号,否则在一些老的脚手架上就会报错
<div id="root">
	
	<h2>人员列表(遍历数组)h2>
	<ul>    
		<li v-for="(p,index) of persons" :key="index">
			{{p.name}}-{{p.age}}
		li>
	ul>
	
	<h2>汽车信息(遍历对象)h2>
	<ul>     
		<li v-for="(value,k) of car" :key="k">
			{{k}}-{{value}}
		li>
	ul>
	
	<h2>测试遍历字符串(用得少)h2>
	<ul>     
		<li v-for="(char,index) of str" :key="index">
			{{char}}-{{index}}
		li>
	ul>
	
	
	<h2>测试遍历指定次数(用得少,多用于测试)h2>
	<ul>	 
		<li v-for="(number,index) of 5" :key="index">
			{{index}}-{{number}}
		li>
	ul>
div>

<script type="text/javascript">
	new Vue({
		el:'#root',
		data:{
			persons:[
				{id:'001',name:'张三',age:18},
				{id:'002',name:'李四',age:19},
				{id:'003',name:'王五',age:20}
			],
			car:{
				name:'奥迪A8',
				price:'70万',
				color:'黑色'
			},
			str:'hello'
		}
	})
script>
1-11-2.key的原理

面试题:react、vue中的key有什么作用?

使用key的原因是vue在模板解析的时候会有一个diff算法,新旧虚拟dom会根据key值进行对比,如果key所对应的内容相同,就直接复用,不同则会新生成一个真实dom,这样是比较影响效率的,给key一个唯一的id就是为了避免出现这种问题。

虚拟DOM中key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1)若虚拟DOM中内容没变, 直接使用之前的真实DOM!
(2)若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到到页面。*

用index作为key可能会引发的问题

若对数据进行:逆序添加、逆序删除等破坏顺序 *** 作:会产生没有必要的真实DOM更新 => 界面效果没问题, 但效率低。如果结构中还包含输入类的DOM:会产生错误DOM更新 => 界面有问题。*

开发中如何选择key?

最好使用每条数据的唯一标识作为key, 比如id、手机号、身份z号、学号等唯一值。如果不存在对数据的逆序添加、逆序删除等破坏顺序 *** 作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。


key=0~2,对应的name值不同所以填充新的值,input相同所以直接复用
key=3,没有所对应的key,所以生成一个新的虚拟dom

key=001~002,所对应的内容相同直接复用,key=004,生成新的虚拟dom

<div id="root">
	
	<h2>人员列表(遍历数组)h2>
	<button @click.once="add">添加一个老刘button>
	<ul>
		<li v-for="(p,index) of persons" :key="index"> 
			{{p.name}}-{{p.age}}
			<input type="text">
		li>
	ul>
div>

<script type="text/javascript">
	new Vue({
		el:'#root',
		data:{
			persons:[
				{id:'001',name:'张三',age:18},
				{id:'002',name:'李四',age:19},
				{id:'003',name:'王五',age:20}
			]
		},
		methods: {
			add(){
				const p = {id:'004',name:'老刘',age:40}
				this.persons.unshift(p)
			}
		},
	})
script>
1-11-3.列表过滤

<div id="root">
    <input type="text" placeholder="请输入名字" v-model="keyWord">
    <ul>
        <li v-for="(p,index) of filPersons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        li>
    ul>
div>

<script type="text/javascript">
    // watch实现:
    /* const vm = new Vue({
        el: '#root',
        data: {
            keyWord: '',
            persons: [{
                id: '001',
                name: '马冬梅',
                age: 18,
                sex: '女'
            }, {
                id: '002',
                name: '周冬雨',
                age: 19,
                sex: '女'
            }, {
                id: '003',
                name: '周杰伦',
                age: 20,
                sex: '男'
            }, {
                id: '004',
                name: '温兆伦',
                age: 21,
                sex: '男'
            }],
            filPersons: []
        },
        watch: {
            keyWord: {
                immediate: true,
                handler(val) {
                    this.filPersons = this.persons.filter((p) => {
                        return p.name.indexOf(val) !== -1;
                    })
                }
            }
        }
    }) */

    // computed实现:
    const vm = new Vue({
        el: '#root',
        data: {
            keyWord: '',
            persons: [{
                id: '001',
                name: '马冬梅',
                age: 18,
                sex: '女'
            }, {
                id: '002',
                name: '周冬雨',
                age: 19,
                sex: '女'
            }, {
                id: '003',
                name: '周杰伦',
                age: 20,
                sex: '男'
            }, {
                id: '004',
                name: '温兆伦',
                age: 21,
                sex: '男'
            }]
        },
        computed: {
            filPersons() {
                return this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1;
                })

            }
        }
    })
script>

这里computed明显要比watch更简洁,computed一开始就会执行,而watch还得配置immediate

所以,当computed和watch都能实现的时候,优先使用computed

1-11-4.列表排序

这个案例充分体现了computed功能的强大,只要所依赖的属性发生了变化,整个计算属性都会重新办进行计算。

<div id="root">
    <input type="text" placeholder="请输入名字" v-model="keyWord">
    <button @click="sortType = 1">年龄升序button>
    <button @click="sortType = 2">年龄降序button>
    <button @click="sortType = 0">原顺序button>
    <ul>
        <li v-for="(p,index) of filPersons" :key="index">
            {{p.name}}-{{p.age}}-{{p.sex}}
        li>
    ul>
div>

<script type="text/javascript">
    Vue.config.productionTip = false

    // computed实现:
    const vm = new Vue({
        el: '#root',
        data: {
            keyWord: '',
            persons: [{
                id: '001',
                name: '马冬梅',
                age: 30,
                sex: '女'
            }, {
                id: '002',
                name: '周冬雨',
                age: 31,
                sex: '女'
            }, {
                id: '003',
                name: '周杰伦',
                age: 18,
                sex: '男'
            }, {
                id: '004',
                name: '温兆伦',
                age: 21,
                sex: '男'
            }],
            sortType: 0 //0原顺序   @click="sortType = 0"1降序  2升序
        },
        computed: {
            filPersons() {
                const arr = this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1;
                });
                // 判断一下是否需要排序
                if (this.sortType) {
                    arr.sort((p1, p2) => {
                        return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
                    })
                }
                return arr;
            }
        }
    })
script>

sort(a,b)数组方法,a-b升序,b-a降序,更改原数组

1-11-5.更新时的一个问题

数组元素赋值Vue无法监测,元素中对象属性赋值Vue可以监测

const vm = new Vue({
    el: '#root',
    data: {
        persons: [{id: '001',name: '马冬梅',age: 30,sex: '女'}, 
        {id: '002',name: '周冬雨',age: 31,sex: '女'}, 
        {id: '003',name: '周杰伦',age: 18,sex: '男'}, 
        {id: '004',name: '温兆伦',age: 19,sex: '男'}]
    },
    methods: {
        updateMei() {
            // this.persons[0].name = '马老师' //
            // this.persons[0].age = 50 //奏效
            // this.persons[0].sex = '男' //奏效
            this.persons[0] = {id: '001',name: '马老师',age: 50,sex: '男'} //不奏效
        }
    }
})
1-11-6.Vue检测数据改变的原理(对象)

Vue是通过getter和setter进行数据监测的,这个getter和setter和普通的getter和setter不一样,只要它们监测到数据修改,就会重新解析模板,所以这个getter和setter也称之为响应式的getter和setter。

1-11-7.模拟一个数据监测

如何模拟和_data里面一样的结构?

通过Observe的实例对象代理data,这也是Vue底层所使用的方式。

PS:这只是一个模拟,和正真底层实现还是有所差异的,比如只能通过vm._data去获取和修改,这里的监测也只做了一层。

let data = {
    name: '尚硅谷',
    address: '北京',
}
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm = {}
vm._data = data = obs
function Observer(obj) {
    //汇总对象中所有的属性形成一个数组
    const keys = Object.keys(obj)
        //遍历
    keys.forEach((k) => {
        Object.defineProperty(this, k, {
            get() {
                return obj[k] //这里的obj和data是同一个引用
            },
            set(val) {
                console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
                obj[k] = val // 这里改的其实就是data的原数据
            }
        })
    })
}

Vue底层是通过递归解决这种问题,只要属性值是对象或数组就会一直往下找,直到属性值不再是对象或数组。

数组元素是没有响应式的getter和setter服务的,所以你给数组元素赋值Vue是监测不到的,页面不会发生变化

如果数组的元素是一个对象,那么这个对象是一定有响应式的getter和setter服务的

1-11-8.Vue.set 和 vm.$set

普通方法添加的属性是没有getter和setter的,而 Vue.set 或 vm.$set 添加的属性是有getter和setter的,也就是说它们是响应式的。

参数描述
target要添加属性的对象/数组(注:不能是vue实例或data)
key要添加属性名/索引
val要添加属性值
Vue.set(vm.student,'sex','男')
// 或
vm.$set(vm.student,'sex','男')
1-11-9.Vue监测数据改变的原理(数组)

数组的元素是没有getter和setter的,因为数组本身就提供了许多 *** 作数组的API,所以vue的设计者给这些API进行了包装,也就是当发现你调用了这些API,就会重新解析模板,从而实现数据的响应式。

这些API主要是一些变更方法,具体有以下七种:

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

filter/concat/slice不包含在内是因为它不变更原数组,而是返回新数组,当使用非变更方法时,可以用新数组替换旧数组。

另外,Vue.set()vm.$set()也可以响应式修改数组

Vue.set(vm.array, index, 'xxx');
// 或
vm.$set(vm.array, index, 'xxx');
1-11-10.总结Vue数据监测

Vue监视数据的原理

vue会监视data中所有层次的数据。

如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1)对象中后追加的属性,Vue默认不做响应式处理
(2)如需给后添加的属性做响应式,请使用如下API:
Vue.set(target, propertyName/index, value)
vm.$set(target, propertyName/index, value)

如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。
(2)重新解析模板,进而更新页面。

在Vue修改数组中的某个元素一定要用如下方法:
(1)使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2)Vue.set() 或 vm.$set()

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

vue把data里所有属性都变成getter、setter形式的这种行为,叫做数据劫持。

1-12.收集表单数据

收集表单数据

若:,则v-model收集的是value值,用户输入的就是value值。
若:,则v-model收集的是value值,且要给标签配置value值。
若:,在select身上绑定v-model,且v-model的初始值就是select的默认选项
若:

没有配置value的初始值,默认收集的就是checked(勾选 or 未勾选,是布尔值)配置了value的初始值:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组

关于 v-model 的修饰符

lazy:失去焦点再收集数据number:输入字符串转为有效的数字trim:输入首尾空格过滤
<div id="root">
	<form @submit.prevent="demo">
		账号:<input type="text" v-model.trim="userInfo.account">
		密码:<input type="password" v-model="userInfo.password">
		年龄:<input type="number" v-model.number="userInfo.age">
		性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female">
		爱好:学习<input type="checkbox" v-model="userInfo.hobby" value="study">
		      打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
		      吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
		所属校区:
		<select v-model="userInfo.city">
			<option value="">请选择校区option>
			<option value="beijing">北京option>
			<option value="shanghai">上海option>
			<option value="shenzhen">深圳option>
			<option value="wuhan">武汉option>
		select>
		其他信息:
		<textarea v-model.lazy="userInfo.other">textarea>
		<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">
		<button>提交button>
	form>
div>

<script type="text/javascript">
	new Vue({
		el:'#root',
		data:{
			userInfo:{
				account:'',
				password:'',
				age:18,
				sex:'female',
				hobby:[],
				city:'beijing',
				other:'',
				agree:''
			}
		},
		methods: {
			demo(){
				console.log(JSON.stringify(this.userInfo))
			}
		}
	})
script>
1-13.过滤器

定义

对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理,复杂还得用computed、methods)。

语法

注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”

备注

过滤器也可以接收额外参数、多个过滤器也可以串联并没有改变原本的数据, 是产生新的对应的数据过滤器的默认参数是 | 前面的值,手动传入的参数从第二个参数开始接收
<div id="root">
	<h2>显示格式化后的时间h2>
	
	<h3>现在是:{{fmtTime}}h3>
	
	<h3>现在是:{{getFmtTime()}}h3>
	
	<h3>现在是:{{time | timeFormater}}h3>
	
	<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}h3>
	<h3 :x="msg | mySlice">尚硅谷h3>
div>
<div id="root2">
	<h2>{{msg | mySlice}}h2>
div>

<script type="text/javascript">
	Vue.config.productionTip = false
	//全局过滤器
	Vue.filter('mySlice',function(value){
		return value.slice(0,4)
	})
	
	new Vue({
		el:'#root',
		data:{
			time:1621561377603, //时间戳
			msg:'你好,尚硅谷'
		},
		computed: {
			fmtTime(){
				return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
			}
		},
		methods: {
			getFmtTime(){
				return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
			}
		},
		//局部过滤器
		filters:{
			timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
				// console.log('@',value)
				return dayjs(value).format(str)
			}
		}
	})
	new Vue({
		el:'#root2',
		data:{
			msg:'hello,atguigu!'
		}
	})
script>

day.min.js:https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.0/dayjs.min.js

1-14.内置指令与自定义指令 1-14-1.内置指令

我们学过的指令

v-bind : 单向绑定解析表达式, 可简写为 :xxxv-model : 双向数据绑定v-for : 遍历数组/对象/字符串v-on : 绑定事件监听, 可简写为@v-if : 条件渲染(动态控制节点是否存存在)v-else : 条件渲染(动态控制节点是否存存在)v-show : 条件渲染 (动态控制节点是否展示) 1-14-1-1.v-text

作用

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

与插值语法的区别

v-text会替换掉节点中的内容,{{xx}}则不会v-text不能识别html结构。
<div v-text="name"></div>
1-14-1-2.v-html

作用

向指定节点中渲染包含html结构的内容。

与插值语法的区别

v-html会替换掉节点中所有的内容,{{xx}}则不会。v-html可以识别html结构。

严重注意:v-html有安全性问题!!!!

在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
<div v-html="str"></div> 
// str:'兄弟我找到你想要的资源了,快来!'
1-14-1-3.v-clock(没有值)

作用

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

<body>
	<div id="root">
		<h2 v-cloak>{{name}}h2> 
	div>
	<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js">script>
body>
1-14-1-4.v-once(没有值)

作用

v-once所在节点在初次动态渲染后,就视为静态内容了。以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

备注

注意和修饰符的once进行区分,一个是事件只触发一次,一个是只进行一次动态渲染。

<div id="root">
	<h2 v-once>初始化的n值是:{{n}}h2>  // 输出:1
	<h2>当前的n值是:{{n}}h2> // 输出:1,2,3,4,5...
	<button @click="n++">点我n+1button>
div>
<script type="text/javascript">
	new Vue({
		el:'#root',
		data:{ n:1 }
	})
script>
1-14-1-5.v-pre(没有值)

作用

跳过其所在节点的编译过程。可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
<h2 v-pre>Vue其实很简单h2>
1-14-2.自定义指令

定义语法

局部指令:
new Vue({
	directives{指令名:回调函数}
})	
// 或
new Vue({						
	directives:{指令名:配置对象}
}) 							
全局指令:
Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)

配置对象中常用的3个回调

bind:指令与元素成功绑定时调用。inserted:指令所在元素被插入页面时调用。update:指令所在模板结构被重新解析时调用。

备注

指令命名时不加v-,短横杠命名用字符串,使用时要加v-;指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
// 局部:
directives:{
	//big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
	/* 'big-number'(element,binding){
		// console.log('big')
		element.innerText = binding.value * 10
	}, */
	//  绑定元素 绑定对象(信息)
	big(element,binding){
		console.log('big',this) //注意此处的this是window
		// console.log('big')
		element.innerText = binding.value * 10
	},
	fbind:{
		//指令与元素成功绑定时(一上来)
		bind(element,binding){
			element.value = binding.value
		},
		//指令所在元素被插入页面时
		inserted(element,binding){
			element.focus()
		},
		//指令所在的模板被重新解析时
		update(element,binding){
			element.value = binding.value
		}
	}
}
// 全局:
// Vue.directive('big',function(element,binding){
// 	element.innerText = binding.value * 10
// })
Vue.directive('fbind',{
	bind(element,binding){
		element.value = binding.value
	},
	inserted(element,binding){
		element.focus()
	},
	update(element,binding){
		element.value = binding.value
	}
})
1-15.生命周期 1-15-1.生命周期引入

又名

生命周期回调函数、生命周期函数、生命周期钩子。

是什么

Vue在关键时刻帮我们调用的一些特殊名称的函数。

生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。

生命周期函数中的this指向是vm 或 组件实例对象。

1-15-2.生命周期分析与总结

常用的生命周期钩子

mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化 *** 作】。beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例

销毁后借助Vue开发者工具看不到任何信息。销毁后自定义事件会失效,但原生DOM事件依然有效。一般不会在beforeDestroy *** 作数据,因为即便 *** 作数据,也不会再触发更新流程了。

除了上图的6个声明周期钩子,还有另外3个:activeted、deactiveted、nextTick

第2章:Vue组件化编程 2-1.模块与组件、模块化与组件化 2-1-1.模块

什么是模块?

向外提供特定功能的js程序,一般就是一个js文件

为什么要用模块?

因为js文件很多很复杂

有什么作用?

复用js,简化js的便携,提高js运行效率

2-1-2.组件


什么是组件?

实现界面中局部功能代码和资源的集合(代码指html、css、js,资源指mp3、mp4、ttf、zip)

为什么要用组件?

一个界面的功能很复杂

有什么作用?

复用编码,简化项目编码,提高运行效率

2-1-3.模块化

当应用中的 js 都以模块来编写,那这个应用就是一个模块化的应用

2-1-4.组件化

当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

2-2.非单文件组件 2-2-1.基本使用

Vue中使用组件的三大步骤

定义组件(创建组件)注册组件使用组件(写组件标签)

如何定义一个组件?

使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别,区别如下:

el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。

如何注册组件?

局部注册:靠new Vue的时候传入components选项全局注册:靠Vue.component(‘组件名’,组件)

编写组件标签

<school>school>

局部组件

// 第一步:创建school组件
const school = Vue.extend({
	template:`
		
			学校名称:{{schoolName}}
			学校地址:{{address}}
				
		
	`,
	// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
	data(){
		return {
			schoolName:'尚硅谷',
			address:'北京昌平'
		}
	},
	methods: {
		showName(){
			alert(this.schoolName)
		}
	},
})

new Vue({
	el:'#root',
	data:{
		msg:'你好啊!'
	},
	//第二步:注册组件(局部注册)
	components:{
		school,
		student
	}
})

全局组件

// 第一步:创建hello组件
const hello = Vue.extend({
	template:`
			
			你好啊!{{name}}
		
	`,
	data(){
		return {
			name:'Tom'
		}
	}
})

// 第二步:全局注册组件
Vue.component('hello',hello)
2-2-2.几个注意点

关于组件名

一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
可以使用name配置项指定组件在开发者工具中呈现的名字。

关于组件标签

第一种写法:
第二种写法:

备注:不用使用脚手架时,会导致后续组件不能渲染。

一个简写方式

const school = Vue.extend(options) 可简写为:const school = options

备注:Vue会帮你判断是否有写extend方法,没有会帮你调一个

关于template

Component template should contain exactly one root element,组件模板应该只包含一个根元素

2-2-3.组件的嵌套

app用来管理所有组件(这是规范写法)

组件里也可以注册组件

<body>
	
	<div id="root">
		
	div>
body>

<script type="text/javascript">
	//定义student组件
	const student = Vue.extend({
		name:'student',
		template:`
			
				学生姓名:{{name}}	
				学生年龄:{{age}}	
			
		`,
		data(){
			return {
				name:'尚硅谷',
				age:18
			}
		}
	})
	
	//定义school组件
	const school = Vue.extend({
		name:'school',
		template:`
			
				学校名称:{{name}}	
				学校地址:{{address}}	
				
			
		`,
		data(){
			return {
				name:'尚硅谷',
				address:'北京'
			}
		},
		//注册组件(局部)
		components:{
			student
		}
	})

	//定义hello组件
	const hello = Vue.extend({
		template:`{{msg}}`,
		data(){
			return {
				msg:'欢迎来到尚硅谷学习!'
			}
		}
	})
	
	//定义app组件
	const app = Vue.extend({
		template:`
				
				
				
			
		`,
		components:{
			school,
			hello
		}
	})

	//创建vm
	new Vue({
		template:'',
		el:'#root',
		//注册组件(局部)
		components:{app}
	})
script>
2-2-4.VueComponent

关于VueComponent

school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,
即Vue帮我们执行的:new VueComponent(options)。特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!关于this指向:
(1)组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2)new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。

备注

每一个组件都是一个构造函数VueComponent,它们的结构 相同,但是指针不同

首先要知道,两个变量进行比较,它们比较的是指针,有以下两种情况:

原始数据类型的比较:
如果声明了两个相同的变量,后声明的变量会覆盖掉先声明的变量,所以它们始终是相等的。

引用数据类型的比较:
如果声明了两个相同的变量,会在内存开辟一个空间存储新的引用,所以它们始终是不相等的。

在extend方法里面,其实就是进行这样的一步 *** 作,最后把VueComponent返回出来

这样很明显就可以看出,每个返回的指针都是不一样的,所以每个VueComponent必然是不同的


怎么看出vm管理着所有的组件?

2-2-5.vm和vc的区别

组件实例对象(vc)是一个小型的vm,有几点不同之处:

vm可以el,vc不能写,会报错(vc最终被vm管理,所以它不需要el)vm的data可以写成函数或对象,vc只能写函数(避免引用错乱) 2-2-6.一个重要的内置关系 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype(组件的原型的原型是Vue的原型)为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

const school = Vue.extend({}); // 返回的是vueComponent构造函数

console.log(school.prototype.__proto__ === Vue.prototype); // true
2-3.单文件组件

命名方式

school.vue 或 my-schoolSchool.vue 或 MySchool

配合开发者工具,一般选用第二种

组件的基本结构

<template>
	
template>

<script>
	// 组件交互相关的代码(数据、方法等等)
script>

<style>
	/* 组件的样式 */
style>

组件导出

export default Vue.extend({...}) 
// 或
export default {...} 

引入到app.js

<template>
	<div> 
		<Component>Component>
	div>
template>

<script>
	//引入组件
	import Component from './component.vue'

	export default {
		name:'App',
		components:{
			Component
		}
	}
script>

注意template里面必须放一个div根元素包裹所有组件
 
给组件name是为了避免非组件开发者乱改名,vue开发工具显示最终以name为准

引入到main.js

import App from './App.vue'

new Vue({
	el:'#root',
	template:``,
	components:{App},
})

引入到index.html

DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>练习一下单文件组件的语法title>
head>

<body>
    
    <div id="root">div>
    <script type="text/javascript" src="../js/vue.js">script>
    <script type="text/javascript" src="./main.js">script>
body>

html>

打开后发现无法显示,这因为浏览器不支持es6的模块化语法,此时就需要搭建脚手架环境

第3章:使用Vue脚手架 3-1. 脚手架的基本使用 3-1-1. 初始化脚手架

说明

Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)最新版本 4.x文档:https://cli.vuejs.org/zh/

如果vue文件没有脚手架作转换和翻译,浏览器就不认识
 
CLI(脚手架)- command line interface(命令行接口工具)

具体步骤

第一步(仅第一次执行):全局安装@vue/cli

npm install -g @vue/cli

第二步:切换到你要创建项目的目录,然后使用命令创建目录

vue create xxxx

第三步:启动项目(对项目进行编译初始化)

npm run serve

备注

如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.orgVue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的webpakc 配置,请执行:vue inspect > output.js

在脚手架里引入文件不用加后缀名,且组件可以用闭合标签


编译完成:

3-1-2. 分析脚手架结构

模板项目的结构

├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git 版本管制忽略的配置
├── babel.config.js: babel 的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

main.js

/* 
	该文件是整个项目的入口文件
*/
//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭vue的生产提示
Vue.config.productionTip = false

//创建Vue实例对象---vm
new Vue({
	el:'#app',
	//render函数完成了这个功能:将App组件放入容器中
  render: h => h(App),
	// render:q=> q('h1','你好啊')

	// template:`你好啊`,
	// components:{App},
})  //.$mount('#app'); // 与 el:'#app' 等效

执行顺序:
执行main.js > 引入vue文件(vue.runtime.esm.js) > 引入app组件(解析所有组件) > 创建Vue对象(绑定el,通过render方法将app组件放入容器)

public/index.html

DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
		
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
		
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
		
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
		
		<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
		
    <title>硅谷系统title>
  head>
  <body>
		
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.strong>
    noscript>
		
    <div id="app">div>
    
  body>
html>
3-1-3. render函数

关于不同版本的Vue

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

运行时版的Vue 即:删减了模板解析器的vue

new Vue({
	el:'#app',
 	render: h => h(App)
})

render方法完整写法:

new Vue({
	el:'#app',
 	render(createElement){
 		return createElement('h1','你好啊')
 	}
})

render参数方法说明

内置创建元素的方法,第一个参数是创建元素,第二个参数是元素内容

render除了渲染html元素,还可以渲染组件(即实例化组件返回的vueComponent构造函数),以render的方式注册组件并渲染然后直接放到index.html的容器里

为什么要用这个方法注册组件,而不用传统的方法?

因为官方默认为我们引入的是精简版的vue,它不能解析Vue里面的template

为什么不引入完整的Vue?

完整Vue包含了核心功能+模板解析器,但最终我们项目打包的时候,webpack会帮我们解析模板,为了减轻容量大小,所以选择了阉割掉模板解析器的Vue版本

为什么组件里的模板就可以解析呢?

脚手架里提供了模板编译器的库,专门解析标签里面的模板组件,完美取代了Vue的编译器

3-1-4. 修改脚手架默认配置 使用vue inspect > output.js可以查看到Vue脚手架的默认配置(output.json)。使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

直接修改output.json是不行的,因为它只是webpack配置文件输出的一个文件

脚手架的哪些默认配置不能改?

红色标记的都不能改

怎样修改脚手架的默认配置?

脚手架底层其实是通过webpack的webpack.config.js修改默认配置的,要想修改webpack.config.js就得借助Vue提供的API

所有左侧的配置属性都可以改,没有出现的就不能改

用法

第一步:在根目录下创建一个vue.config.js文件

Vue.config.xxx 与 vue.config.js 有啥区别?
 
前者是给vue构造函数身上的某个属性配置属性,后者是配置脚手架工作模式的

第二步:在官网找到响应的配置代码,复制到 vue.config.js 里面,修改相应的属性就可以了

配置文件写好后会输送给webpack的配置文件进行合并,webpack是基于Node.js的,node.js采用的模块化是common.js,所以这里暴露的方式是common

关闭语法检查

避免不必要的报错

module.exports = {
    pages: {...},
    lintOnSave:false // 关闭语法检查()
}

如果你想要在生产构建时禁用语法检查可以配置 lintOnSave: process.env.NODE_ENV !== ‘production’

3-2. ref属性 被用来给元素或子组件注册引用信息(id的替代者)应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)使用方式: 打标识:.....获取:this.$refs.xxx
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>

methods: {
	showDOM(){
		console.log(this.$refs.title) //真实DOM元素
		console.log(this.$refs.btn) //真实DOM元素
		console.log(this.$refs.sch) //School组件的实例对象(vc)
	}
}
3-3. props属性

功能:让组件接收外部传过来的数据

传递数据:(前者"“中是字符串,后者”"中是表达式)

接收数据:

第一种方式(只接收):props:['name']

第二种方式(限制类型):props:{name:String}

第三种方式(限制类型、限制必要性、指定默认值):

props:{
	name:{
	type:String, //类型
	required:true, //必要性
	default:'老王' //默认值
	}
}

备注:props与data是有区别的,props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

<Student name="李四" sex="女" :age="18"/>

props:{
	name:{
		type:String, //name的类型是字符串
		required:true, //name是必要的
	},
	age:{
		type:Number,
		default:99 //默认值
	},
	sex:{
		type:String,
		required:true
	}
}
3-4. mixin属性

功能:可以把多个组件共用的配置提取成一个混入对象

使用方式:

第一步定义混合:

{
    data(){....},
    methods:{....}
    ....
}

第二步使用混入:

​ 全局混入:Vue.mixin(xxx)
​ 局部混入:mixins:['xxx']

定义混入文件

// mixin.js
export const hunhe = {
	data() {
		return {
			x:100,
			y:200
		}
	},
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}

全局混入

// main.js
import Vue from 'vue'
import App from './App.vue'
import {hunhe,hunhe2} from './mixin'

Vue.mixin(hunhe)

new Vue({
	el:'#app',
	render: h => h(App)
})

局部混入

// Component.js
import {hunhe} from '../mixin'

export default {
	name:'Student',
	data() {
		return {
			name:'张三',
			sex:'男'
		}
	},
	mixins:[hunhe]
}
3-5. 插件

功能:用于增强Vue

本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

定义插件:

对象.install = function (Vue, options) {
    // 1. 添加全局过滤器
    Vue.filter(....)

    // 2. 添加全局指令
    Vue.directive(....)

    // 3. 配置全局混入(合)
    Vue.mixin(....)

    // 4. 添加实例方法
    Vue.prototype.$myMethod = function () {...}
    Vue.prototype.$myProperty = xxxx
}

使用插件:Vue.use()

定义插件文件

// plugin.js
export default {
    install(Vue, x, y, z) { 
        console.log(Vue, x, y, z) // Vue构造器 1 2 3
        //全局过滤器
        Vue.filter(...)

        //定义全局指令
        Vue.directive(...)
        
        //定义混入
        Vue.mixin(...)
}

应用插件

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

Vue.use(plugins,1,2,3)

new Vue({
	el:'#app',
	render: h => h(App)
})

应用完所有组件就可以直接使用了

3-6. scoped样式 作用:让样式在局部生效,防止冲突。写法: