Vue.js安装

这里我们使用方式二:

去官网,下载vue.js(开发版本)

使用webstorm新建项目,然后新建js文件夹,把刚才下载的vue.js放到js文件夹下

修改数据

第一个vue代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app">{{message}}</div>
<!--引入vue.js-->
<script src="../js/vue.js"></script>
<script>//let(变量) const(常量)//new Vue,说明有一个function Vue()//声明式编程const app = new Vue({//用于挂载要管理的元素el:'#app',//定义数据data:{message:'你好呀,李银河!'}})//以上代码使用js实现(命令式编程)//1、创建div元素//2、定义一个变量叫message//3.将message变量放在前面的div元素中//4.修改message数据://5.将修改后的数据再次替换到div元素
</script>
</body>
</html>

vue列表展示

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h1>{{movies[0]}}</h1><ul><li v-for="item in movies">{{item}}</li></ul>
</div><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',movies: ['龙门飞甲','芳华','剑雨','盗梦空间']}})
</script>
</body>
</html>

响应式添加数据

vue计数器实列

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>当前计数:{{counter}}</h2><!--v-on:监听事件,事件简单时直接在等号后写事件动作--><button v-on:click="counter++">+</button><button v-on:click="counter--">-</button><!--事件复杂时,使用以上方式,代码就会变得不易读,宜采用函数方式--><button v-on:click="add">+</button><button v-on:click="sub">-</button>
</div><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {counter: 0},//定义方法,函数methods: {add: function(){console.log("add被执行了!")//注意,在函数里使用data中变量时,不能直接使用变量名,如果使用变量名它就会默认去找全局变量//使用data中的变量时,因为这些变量都是在Vue对象中,可以使用Vue的实列app调用变量,但最推荐的方式是使用this(当前对象)this.counter++},sub: function(){console.log("sub被执行了!")this.counter--}}})
</script>
</body>
</html>

语法糖:v-on:click等价于@click

Vue中的MVVM

计数器中的MVVM

另外的写法:结构更加清晰(使用了代理)

vue中的参数:options

el:

data:组件的时候必须传Function

function:函数:全局的function就是函数

方法:与类挂钩,类中的function是方法,即方法与类的实列挂钩

Vue的声明周期:

回调函数:

Vue自动回调函数

一般在created中做一些网络请求

代码规范:缩进两个空格

cli->.editconfig

webstorm中设置

编写自己的模板

<div id="app">{{message}}
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>

复制以上代码,点击设置->Editor->Live Templates->Vue

点击+号

点击Define

点击HTML,点击apply,点击应用,以后在html中输入vue后tab键即可弹出我们刚才定义的模板。、

tab键可以自定义为自己喜欢的方式

基本语法

模板语法

插值操作:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{message}}</h2><h2>{{message}},李银河</h2><!--表达式--><h2>{{firstName + lastName}}</h2><h2>{{firstName + ' ' + lastName}}</h2><h2>{{firstName}} {{lastName}}</h2><h2>{{counter * 2}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',firstName: 'kobe',lastName: 'bryant',counter: 100}})
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{message}}</h2><!--v-once:使用该指令,data中的数据改变了,页面上不会跟着刷新,使用的还是原来的数据,即破坏了响应式--><h2 v-once>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

v-once

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{url}}</h2><!--v-html解析html标签--><h2 v-html="url"></h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',url: '<a href="http://www.baidu.com">百度一下</a>'}})
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2> {{message}}</h2><!--使用v-text展示message--><h2 v-text="message"></h2><!--使用v-text不灵活,例如,要拼接的时候,v-text会直接覆盖掉html标签中的数据--><h2 v-text="message">,李银河</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

第三个v-text后的,李银河被覆盖了

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{message}}</h2><!--不想解析message时,仅仅只是显示{{message}}可以使用v-pre指令--><h2 v-pre>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

v-cloak:cloak(斗篷)

v-cloak加上css代码可以防止网络抖动时js代码执行缓慢导致的显示{{message}},而不是输出内容的情况,如模拟网络延时时

这是,就会出现上面的情况,1s后js解析完成,才能正常显示,使用v-cloak指令,让网络延时时什么也不显示

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>/*设置v-cloak的样式*/[v-cloak]{display: none}</style>
</head>
<body>
<div id="app"><h2 v-cloak>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>//设置网络延时1秒后执行function函数//v-cloak:在vue解析之前,div中有一个属性,v-cloak//在vue解析完成后,div中没有属性v-cloak(即vue解析完成后会自动删除v-cloak-->setTimeout(function(){const app = new Vue({el: '#app',data: {message: '你好呀'}})},1000)
</script>
</body>
</html>

这时,vue解析延时时,会展示空白(即什么也不展示)

v-bind:绑定属性,即把数据绑定到属性中

使用mustache语法,直接在src中显示{{imgUrl}}

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><!--错误的写法:这里不可以使用mustache语法--><!--<img src="{{imgUrl}}">--><!--正确的写法:使用v-bind--><img v-bind:src="imgUrl" alt="">
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',//图片地址,网上随便找的imgUrl: 'https://scpic.chinaz.net/files/pic/pic9/202106/apic33584.jpg'}})
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><!--错误的写法:这里不可以使用mustache语法--><!--<img src="{{imgUrl}}">--><!--正确的写法:使用v-bind--><img v-bind:src="imgUrl" alt=""><a v-bind:href="aHref">百度一下</a>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',//图片地址,网上随便找的imgUrl: 'https://scpic.chinaz.net/files/pic/pic9/202106/apic33584.jpg',aHref: 'http:www.baidu.com'}})
</script>
</body>
</html>

v-bind的语法糖:v-bind等价于:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>.actives{color: red;}</style>
</head>
<body>
<div id="app"><h2 :class="active">{{message}}</h2><!--<h2 v-bind:class="{类名1:boolean,类名2:boolean}">{{message}}</h2>--><!--当boolean为true时,会将类名加到class中,当boolean为false时会从class中移除类名,这里的类名一般为css的类名--><!--v-bind,对象语法:{}表示一个对象,里面是键值对,对于不需要改变的class,可以使用普通的class加进去,其不用改变,title,line是随便写的class,其并没有实际意义(因为没有写相应的css)--><h2 class="title" v-bind:class="{actives: isActive, line: isLine}">{{message}}</h2><button @click="btnClick">改变颜色</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',active: 'actives',isActive: true,isLine: true},methods: {btnClick: function(){this.isActive = !this.isActive}}})
</script>
</body>
</html>

{}对象使用函数返回

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>.actives{color: red;}</style>
</head>
<body>
<div id="app"><h2 class="title" :class="{actives: isActive, line: isLine}">{{message}}</h2><!--对象语法:调用函数,最好加上一对(),button上的是省略了--><h2 class="title" :class="getClass()">{{message}}</h2><button @click="btnClick">改变颜色</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',isActive: true,isLine: true},methods: {btnClick: function(){this.isActive = !this.isActive},getClass: function(){return {actives: this.isActive, line: this.isLine}}}})
</script>
</body>
</html>

数组语法:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><!--写法一:这中方式与直接使用普通class效果是一样的 ['类名1','类名2',....],类名即css样式的class名--><h2 class="title" :class="['actives','line']">{{message}}</h2><!--写法二:也是使用数组语法的原因:有时类名是从服务器请求过来的,该类名与vue data中的变量绑定--><h2 class="title" :class="[class1,class2]">{{message}}</h2><!--写法三:使用methods返回--><h2 class="title" :class="getClass()">{{message}}</h2><!--也可以把数组放到一个methods中-->
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',class1: 'actives',class2: 'line'},methods: {getClass: function(){return [this.class1, this.class2]}}})
</script>
</body>
</html>

效果是一样的

作业需求:

点击列表中的某一项,那个该项文字变成红色:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><ul><!--v-for:返回的第一个元素是movies中的元素,第二个元素是下标--><li v-for="(item,index) in movies">{{index + '-' + item}}</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {movies: ['海王','海尔兄弟','火影忍者','进击的巨人']}})
</script>
</body>
</html>

v-bind动态绑定style

对象语法:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--:style={key:value},key是css的属性名,value是属性值,属性名可以使用-连接,也可以使用小驼峰命名方式--><h2 :style="{'font-size': '50px'}">{{message}}</h2><!--使用小驼峰命名的好处:key可以不加单引号(加上单引号也可以),但是value在不引用vue中的data中的变量时必须加单引号,因为如果不加单引号,使用了v-bind时就会去vue中找同名的变量绑定的值--><h2 :style="{fontSize: '50px'}">{{message}}</h2><h2 :style="{'fontSize': '50px'}">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

效果一样:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--:style={key:value},key是css的属性名,value是属性值,属性名可以使用-连接,也可以使用小驼峰命名方式--><h2 :style="{'font-size': '50px'}">{{message}}</h2><!--使用小驼峰命名的好处:key可以不加单引号(加上单引号也可以),但是value在不引用vue中的data中的变量时必须加单引号,因为如果不加单引号,使用了v-bind时就会去vue中找同名的变量绑定的值--><h2 :style="{fontSize: '50px'}">{{message}}</h2><h2 :style="{'fontSize': '50px'}">{{message}}</h2><!--引用变量--><h2 :style="{fontSize: finalSize}">{{message}}</h2><h2 :style="{fontSize: finalSize01 + 'px'}">{{message}}</h2><h2 :style="{fontSize: finalSize01 + 'px', color: finalColor}">{{message}}</h2><h2 :style="{fontSize: finalSize01 + 'px', background: finalColor}">{{message}}</h2><!--使用methods返回--><h2 :style="getStyles()">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',finalSize: '100px',finalSize01: 100,finalColor: 'red'},methods: {getStyles: function(){return {fontSize: this.finalSize01 + 'px', background: this.finalColor};}}})
</script>
</body>
</html>

数组语法:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2 :style="[baseStyle, fontSize]">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',baseStyle: {background: 'red'},fontSize: {fontSize: '50px'}}})
</script>
</body>
</html>

一、Vue基础语法

1、计算属性

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{firstName}} {{lastName}}</h2><h2>{{firstName + ' ' + lastName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: 'Lebron',lastName: 'James'}})
</script>
</body>
</html>

上面的代码弊端:如果要写多次,就需要拼接多次,工作量大

解决办法一:使用方法

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{firstName}} {{lastName}}</h2><h2>{{firstName + ' ' + lastName}}</h2><!--使用方法:缺点:使用方法,需要加(),看着别扭,并且执行效率不高--><h2>{{getFullName()}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: 'Lebron',lastName: 'James'},methods: {getFullName(){return this.firstName + ' ' + this.lastName}}})
</script>
</body>
</html>

解决办法二:使用计算属性:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{firstName}} {{lastName}}</h2><h2>{{firstName + ' ' + lastName}}</h2><!--使用方法:缺点:使用方法,需要加(),看着别扭,并且执行效率不高--><h2>{{getFullName()}}</h2><!--使用计算属性--><h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: 'Lebron',lastName: 'James'},methods: {getFullName(){return this.firstName + ' ' + this.lastName}},computed: {//类似于函数的定义,只不过这里使用的名词形式的函数名,即名字尽量使用名词,定义后是把它当作一个属性去使用,而不是函数fullName: function(){return this.firstName + ' ' + this.lastName}}})
</script>
</body>
</html>

计算属性的应用场景:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>总价格:{{totalPrice}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {books: [{id: 110, name: 'Unix编程艺术', price: 119},{id: 112, name: 'java编程艺术', price: 120},{id: 113, name:'深入计算机原理', price: 130},{id: 114, name: '代码大全', price: 140},{id: 115, name: '现代操作系统', price: 88}]},//使用计算属性,计算总价格computed: {totalPrice: function(){let result = 0for(let i=0; i<this.books.length; i++){result += this.books[i].price}//或者/*for(let book of this.books){result += book.price}*/return result;}}})
</script>
</body>
</html>

计算属性,在html中多次调用时,只执行一次,而函数没调用一次就执行一次,所以计算属性的执行效率更高。

Vue Day01回顾:

一、邂逅Vuejs

1.1.认识vuejs

  • 为什么学习vuejs
  • Vue读音
  • vue的渐进式
  • Vue的特点

1.2.安装vue

  • CDN引入
  • 下载引入
  • npm安装

1.3.vue初体验

  • Hello vuejs

    • mustache->体验vue响应式
  • vue列表展示

    • v-for
    • 后面给数组追加元素的时候,新的元素也可以在界面中渲染出来
  • Vue计数器小案例

1.4.vue中的MVVM

1.5.创建vue时,options可以放哪些东西

  • el
  • data
  • methods
  • 声明周期函数

二、插值语法

  • mustache语法
  • v-once
  • v-html
  • v-text
  • v-pre:{{}}
  • v-cloak:斗篷

三、v-bind

3.1.v-bind绑定基本属性

  • v-bind:src
  • v-bind:href

3.2.v-bind动态绑定class

  • 对象语法:class=’{类名:boolean}’
  • 数组语法:

3.3.v-bind动态绑定style

  • 对象语法
  • 数组语法

四、计算属性

  • 案例一:firstName + lastName
  • 案例二: books->price

1.2、计算属性的setter和getter

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: 'Kobe',lastName: 'Bryant'},computed: {//函数形式的写法/*fullName: function(){return this.firstName + ' ' + this.lastName;}*///setter和getter写法 属性名:{} {}表示一个对象,对象中setter和getter方法fullName: {set: function(){},get: function(){//当我们使用计算属性的属性名时,本质就是来调用这个get方法return this.firstName + ' ' + this.lastName;}}}})
</script>
</body>
</html>

注意:计算属性一般是不希望设置值的,即计算属性一般没有set方法,是一个只读属性

因为删除了set方法,所以计算属性有一种更为简洁的写法,如下:(即我们之前用的方法)

fullName: function(){}

给计算属性赋值:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: 'Kobe',lastName: 'Bryant'},computed: {//函数形式的写法/*fullName: function(){return this.firstName + ' ' + this.lastName;}*///setter和getter写法 属性名:{} {}表示一个对象,对象中setter和getter方法fullName: {set: function(newValue){//console.log('-----', newValue)const names = newValue.split(' ')this.firstName = names[0]this.lastName = names[1]},get: function(){//当我们使用计算属性的属性名时,本质就是来调用这个get方法return this.firstName + ' ' + this.lastName;}}}})
</script>
</body>
</html>

计算属性的缓存

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--直接拼接:语法过于繁琐--><h3>{{firstName}} {{lastName}}</h3><!--通过methods--><h3>{{getFullName()}}</h3><h3>{{getFullName()}}</h3><h3>{{getFullName()}}</h3><h3>{{getFullName()}}</h3><!--通过计算属性--><h3>{{fullName}}</h3><h3>{{fullName}}</h3><h3>{{fullName}}</h3><h3>{{fullName}}</h3><h3></h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: 'Lemon',lastName: 'Time'},//methodsmethods: {getFullName: function(){console.log('getFullName')return this.firstName + ' ' + this.lastName}},computed: {fullName: function(){console.log('fullName')return this.firstName + ' ' + this.lastName}}})
</script>
</body>
</html>

可以看到methods打印了4次getFullName,而计算属性值打印了1次fullName,说明计算属性值执行了一次,之后再使用计算属性时,它直接把结果返回回来,而不会再去执行里面的操作(即直接返回return后的结果,里面的细节都不会再执行一次),但是返回值的计算表达式中的值发生了变化,计算属性还是会重新执行一次的,以保证结果的正确性

1.3、ES6语法

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button><script>//1.变量作用域:变量在什么范围内起作用{var name = 'why';console.log(name)}//name在块外也起作用console.log(name)// 没有块级作用域引起的问题var func;if(true){var name = 'why';func = function(){console.log(name);}}//在调用func前修改了name的值name = 'Kobe';//打印的name不是我们希望的why,而是Kobefunc()//没有块级作用域引起的问题: for的块级var btns = document.getElementsByTagName('button')//当我们点击按钮时,几乎都会输出'第5个按钮被点击了‘,原因跟上面的列子差不多,我们在事件监听的函数中使用的i是外部的i,由于//外部的i是增加事件监听的,执行的很快,于是i就被变为了5,当我们点击按钮的时候,执行事件监听的方法,于是i还是5for(var i = 0; i < btns.length; i++){btns[i].addEventListener('click', function(){console.log('第' + i + '个按钮被点击');})}
</script>
</body>
</html>

以前为了解决这个问题,需要使用闭包

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button><script>//函数的作用域var name = 'why'function abc(name){console.log(name)}//我们这里只是改变了函数外name的值,并不会影响形参的name,形参的name是我们传什么就是什么name = 'Kobe'abc('hello')//没有块级作用域引起的问题: for的块级var btns = document.getElementsByTagName('button')//使用闭包解决这个问题://为什么闭包能解决这个问题:因为函数是一个作用域for(var i = 0; i < btns.length; i++){(function(i){btns[i].addEventListener('click', function(){console.log('第' + i + '个按钮被点击');})})(i)}
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button><script>//函数的作用域var name = 'why'function abc(name){console.log(name)}//我们这里只是改变了函数外name的值,并不会影响形参的name,形参的name是我们传什么就是什么name = 'Kobe'abc('hello')//没有块级作用域引起的问题: for的块级var btns = document.getElementsByTagName('button')//使用闭包解决这个问题://为什么闭包能解决这个问题:因为函数是一个作用域for(var i = 0; i < btns.length; i++){(function(num){btns[i].addEventListener('click', function(){console.log('第' + num + '个按钮被点击');})})(i)}
</script>
</body>
</html>

ES6之前因为if和for都没有块级作用域的概念,所以在很多时候,我们必须借助于function的作用域来解决外面变量的问题。这个function一般是匿名function

使用let解决块级作用域:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button><script>const btns = document.getElementsByTagName('button');for(let i=0;i<btns.length;i++){btns[i].addEventListener('click', function(){console.log('第' + i + '个按钮被点击');})}
</script></body>
</html>

1.4、const的使用和注意点

在es6中,优先使用const,只有需要改变某一个变量是,才使用let

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<script>//正常定义对象// const obj = new Object()//字面量写法:{}就表示字面量//原来的写法/*const obj = {name: 'why',age: 18,run: function(){console.log('在奔跑')}*/// 1.属性的增强写法const name = 'why';const age = 18;const height = 180;// ES5的写法/*const obj = {name: name,age: age,height: height}*/// ES6的写法,他会把变量名作为key,把变量的value作为valueconst obj = {name,age,height,}console.log(obj)//2.函数的增强写法//ES5的写法/* const obj1 = {run: function(){},eat: function(){}*/// ES6的写法const obj1 = {run(){},eat(){}}
</script>
</body>
</html>

1.5、v-on的使用

v-on:事件监听

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--写法一--><!--<button v-on:click="counter++">+  {{counter}}</button><button v-on:click="counter--">-  {{counter}}</button>--><!--写法二--><h3>{{counter}}</h3><button v-on:click="increment()">+</button><button v-on:click="decrement()">-</button><!--小括号可以省略--><button v-on:click="increment">+</button><button v-on:click="decrement">-</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {counter: 0},methods: {increment(){this.counter++},decrement(){this.counter--}}})
</script>
</body>
</html>

v-on的语法糖为:@

小括号可以省略的条件:事件监听中,调用的方法不需要传递参数

<!--在方法传递时,即需要参数,又需要event对象:这时event会被vue认为是一个data中的变量,他就会去data中找
event对应的变量,并把其值拿出来,传入到方法中-->
<button @click="btn3Click(123,event)">按钮6</button>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--事件调用的方法没有参数:加上小括号--><button @click="btn1Click()">按钮1</button><!--不加括号--><button @click="btn1Click">按钮2</button><!--在事件定义时,写函数时省略了小括号,但要求传一个参数:这时会传递一个Event(事件对象)即Vue会默认将浏览器生成的event事件对象作为参数传入到方法中--><button @click="btn2Click">按钮3</button><!--有参数的正常写法--><button @click="btn2Click(123)">按钮4</button><!--有参数的函数,但不传参数(小括号照写):它会给函数默认传一个参数undefined--><button @click="btn2Click()">按钮5</button><!--在方法传递时,即需要参数,又需要event对象:这时event会被vue认为是一个data中的变量,他就会去data中找event对应的变量,并把其值拿出来,传入到方法中--><button @click="btn3Click(123,event)">按钮6</button><!--在调用方法时:获取事件的正确写法:$event--><button @click="btn3Click(123,$event)">按钮6</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',event: 'Hello,你好'},methods: {btn1Click(){console.log('btn1Click');},btn2Click(param){console.log('-----', param, '------');},btn3Click(param, event){console.log('++++++', param,'event:', event);}}})
</script>
</body>
</html>

v-on的修饰符

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click="btnClick">按钮</button></div>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");}}})
</script>
</body>
</html>

当我点击button中的按钮时,由于事件冒泡的存在,divClick也被点击了

阻止事件冒泡:stop

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click.stop="btnClick">按钮</button></div>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");}}})
</script>
</body>
</html>

prevent修饰符的使用:阻止默认事件的使用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click.stop="btnClick">按钮</button></div><br><!-- .prevent修饰符的使用--><form action="baidu"><input type="submit" value="提交"></form>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");}}})
</script>
</body>
</html>

点击按钮后,vue会自动提交:

我们不希望使用vue的默认提交,需要使用自己定义的提交:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click.stop="btnClick">按钮</button></div><br><!-- .prevent修饰符的使用--><form action="baidu"><input type="submit" @click.prevent="submitClick" value="提交"></form>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");},submitClick(){console.log("submitClick");}}})
</script>
</body>
</html>

现在就只打印,不提交,如果要提交的话,可以自己定义自己的请求

监听某个键盘的某个键的点击:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click.stop="btnClick">按钮</button></div><br><!-- .prevent修饰符的使用--><form action="baidu"><input type="submit" @click.prevent="submitClick" value="提交"></form><!--监听某个键盘的某个键的点击--><input type="text" @keyup="keyUp">
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");},submitClick(){console.log("submitClick");},keyUp(){console.log("keyUp")}}})
</script>
</body>
</html>

当键按下抬起时就会被监听到

只监听某个特殊的按键,如回车键:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click.stop="btnClick">按钮</button></div><br><!-- .prevent修饰符的使用--><form action="baidu"><input type="submit" @click.prevent="submitClick" value="提交"></form><!--监听某个键盘的某个键的点击--><input type="text" @keyup.enter="keyUp">
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");},submitClick(){console.log("submitClick");},keyUp(){console.log("keyUp")}}})
</script>
</body>
</html>

这时只有按下回车键才会被监听到

.once只触发一次回调

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><div @click="divClick">按钮<button @click.stop="btnClick">按钮</button></div><br><!-- .prevent修饰符的使用--><form action="baidu"><input type="submit" @click.prevent="submitClick" value="提交"></form><!--监听某个键盘的某个键的点击--><input type="text" @keyup.enter="keyUp"><!--once修饰符的使用--><button @click.once="btn2Click">btn2</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {divClick(){console.log("divClick");},btnClick(){console.log("btnClick");},submitClick(){console.log("submitClick");},keyUp(){console.log("keyUp")},btn2Click(){console.log("btn2Click");}}})
</script>
</body>
</html>

只有第一次点击才起作用

1.6、条件判断

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h3 v-if="isShow"><div>{{message}}</div></h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',isShow: true}})
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h3 v-if="isShow"><div>abc</div><div>def</div></h3><h2 v-else>isShow为false时显示我</h2>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',isShow: true}})
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h3 v-if="score>=90">优秀</h3><h3 v-else-if="score>=80">良好</h3><h3 v-else>一般</h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {score: 99}})
</script>
</body>
</html>

逻辑复杂的时候不建议这样做,如果逻辑复杂,最好使用计算属性的方式

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h3>{{showLevel}}</h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {score: 99},computed: {showLevel(){if(this.score>=90){return '优秀';}else if(this.score>=80){return '良好';}else{return '一般';}}}})
</script>
</body>
</html>

1.7、登录小案例

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><span v-if="isUser"><label for="username">用户账号</label><input type="text" id="username" placeholder="用户账号"></span><span v-else><label for="email">用户邮箱</label><input type="text" id="email" placeholder="用户邮箱"></span><button @click="isUser=!isUser">切换类型</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {isUser: true}})
</script>
</body>
</html>

当前小案列的小问题:例如我在用户账号界面输入了一些东西,突然想到应该用邮箱登录,于是切换到用户邮箱界面登录,但切换到用户邮箱叫界面时,在用户账号界面输入的账号也会携带过去,并不会清空

切换

vue底层原理:虚拟DOM,尽可能复用(只修改不同的地方)

需求:切换时清除之前的输入:使用key做标识,只用key相同时,才会复用,如果key不相同,vue就不会复用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><span v-if="isUser"><label for="username">用户账号</label><input type="text" id="username" placeholder="用户账号" key="username"></span><span v-else><label for="email">用户邮箱</label><input type="text" id="email" placeholder="用户邮箱" key="email"></span><button @click="isUser=!isUser">切换类型</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {isUser: true}})
</script>
</body>
</html>

1.8、v-show

v-show:和v-if一样,也可以决定一个元素是否渲染

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h3 v-if="isShow">{{message}}</h3><h3 v-show="isShow">{{message}}</h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: "你好呀",isShow: true}})
</script>
</body>
</html>

当为true时,两个显示效果一样

修改为false:两个都不显示

查看html源码:

v-if在显示同时,html源码中也没有相应代码,v-show只是添加了一个行内样式:display:none

1.9、循环遍历

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--1.在遍历的过程中,没有使用索引(下标值)--><ul><li v-for="item in names">{{item}}</li></ul><!--遍历的过程中,使用索引--><ul><li v-for="(item, index) in names">{{index+1}}-item</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {names: ['why', 'who', 'what', 'whom', 'curry', 'lemon']}})
</script>
</body>
</html>

在遍历的过程中,没有使用索引(下标值)

使用下标

v-for遍历对象:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--对象少的时候:比较笨的方法--><ul><li>{{info.name}}</li><li>{{info.age}}</li><li>{{info.height}}</li></ul><!--使用v-for:在遍历的过程中,如果只是获取一个值,那么获取到的是value--><ul><li v-for="item in info">{{item}}</li></ul><!--获取key和value:(value,key)--><ul><li v-for="(value, key) in info">{{key}} - {{value}}</li></ul><!--获取key,value和index--><ul><li v-for="(value, key, index) in info">{{index}} - {{key}} - {{value}}</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {info: {name: 'why',age: 18,height: 188}}})
</script>
</body>
</html>

1.10、组件key的使用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><ul><li v-for="item in letters">{{item}}</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G']}})
</script>
</body>
</html>

官方推荐使用v-for时添加key属性

splice():函数的使用:删除元素:splice(开始位置,删除元素的个数)

添加元素:splice(开始位置,0, 要添加的元素(如果要添加多个,以逗号分割)),splice(1),从第一个删到最后一个元素,即只留下第一个元素

替换元素:splic(开始位置,要替换元素的个数,要替换的元素(如果有多个,以逗号分割)):也可以理解为,先删除要替换元素的个数的元素,在插入要替换的元素

如果不加key,vue会从第二个位置开始替换,即把第二个位置改为F,第三个位置改为C,…,最后一个位置改为G,性能很低。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><ul><!--最好保证我们绑定的元素与要展示的元素一一对应,使用要展示的元素绑定即可,如item--><li v-for="item in letters" :kye="item">{{item}}</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G']}})
</script>
</body>
</html>

使用key时,vue会把key和要展示的元素一一对应,只要key对应的值不变,他就会不动,key不同的就会在创建一个key和值对应,然后再插入到正确的位置即可。

1.11、数组中有哪些方法是响应式的

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><ul><li v-for="item in letters" :key="item">{{item}}</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {letters: ['a', 'c', 'd', 'e', 'f']}})
</script>
</body>
</html>

push是响应式的:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><ul><!--为了防止点击多次数组中又重复元素导致:key相同的错误,这里就不绑定key了--><li v-for="item in letters">{{item}}</li></ul><button @click="btnClick">按钮</button>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {letters: ['a', 'c', 'd', 'e', 'f']},methods: {btnClick(){// 1.push方法// this.letters.push('g');//2.通过索引值修改数组中的元素this.letters[0] = 'aaa';}}})
</script>
</body>
</html>
通过索引值修改数组中的元素不能做到响应式点击并不会被修改

一下都是可以做到响应式的方法:

  • pop()

  • shift() 删除数组中的第一个元素

  • unshift() 在数组中的第一个位置加入元素,可以同时加入多个元

  • splic()

  • sort()

  • reverse()

注意:通过索引值修改元素的值不是响应式的。

如果确实要修改,并且要把修改的结果渲染到界面,使用splice函数

也可以使用:Vue.set()函数,该函数时vue内部实现的一个函数

Vue.set(要修改的数组,修改的位置,要修改为什么值)

1.12、作业的回顾和完成

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>.active{color: red;}</style>
</head>
<body>
<div id="app"><ul><li v-for="(item, index) in movies":class="{active: currentIndex === index}" @click="liClick(index)">{{index}}.{{item}}</li></ul>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {movies: ['海王', '海贼王', '加勒比海盗', '海尔兄弟', '大海', '小海'],// 记录当前哪一个li标签显示红色currentIndex: 0},methods: {liClick(index){this.currentIndex = index;}}})
</script>
</body>
</html>

1.13、图书购物车案例

项目架构

style.css

table {border: 1px solid #e9e9e9;border-collapse: collapse;border-spacing: 0;
}th, td {padding: 8px 16px;border: 1px solid #e9e9e9;text-align: center;
}th {background-color: #f7f7f7;color: #5c6b77;font-weight: 600;
}

main.js

const app = new Vue({el: '#app',data: {books: [{id: 1,name: '《算法导论》',date: '2006-9',price: 85.00,count: 1},{id: 2,name: '《Unix编程艺术》',date: '2006-2',price: 59.00,count: 1},{id: 3,name: '《编程珠玑》',date: '20027-3',price: 39.00,count: 1},{id: 4,name: '《代码大全》',date: '2010-6',price: 128.00,count: 1}]}
})

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"><table><thead><tr><th></th><th>书籍名称</th><th>出版日期</th><th>价格</th><th>购买数量</th><th>操作</th></tr></thead><tbody><tr v-for="item in books"><td v-for="value in item">{{value}}</td></tr></tbody></table>
</div><script src="../js/vue.js"></script>
<script src="main.js"></script>
</script>
</body>
</html>

上面的代码对购买数量中的操作不好做,于是改为下面的方式:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"><table><thead><tr><th></th><th>书籍名称</th><th>出版日期</th><th>价格</th><th>购买数量</th><th>操作</th></tr></thead><tbody><tr v-for="item in books"><td>{{item.id}}</td><td>{{item.name}}</td><td>{{item.date}}</td><td>{{item.price}}</td><td><button>-</button>{{item.count}}<button>+</button></td><td><button>移除</button></td></tr></tbody></table>
</div><script src="../js/vue.js"></script>
<script src="main.js"></script>
</script>
</body>
</html>

细节处理:如价格保留两位小数和一个¥符号:方式一:toFixed()方法

<td>{{'¥' + item.price.toFixed(2)}}</td>

方式二:methods方法

methods: {getFinalPrice(price){return '¥' + price.toFixed(2)}
}

使用:

<td>{{getFinalPrice(item.price)}}</td>

方式三:过滤器

filters: {// 过滤器是一个函数showPrice(price){return '¥' + price.toFixed(2)}
}

使用:{{item.price | 过滤器}}

{{item.price | showPrice}}

添加和删除:

总价格:计算属性

最终:style.css没有变化

main.js

const app = new Vue({el: '#app',data: {books: [{id: 1,name: '《算法导论》',date: '2006-9',price: 85.00,count: 1},{id: 2,name: '《Unix编程艺术》',date: '2006-2',price: 59.00,count: 1},{id: 3,name: '《编程珠玑》',date: '20027-3',price: 39.00,count: 1},{id: 4,name: '《代码大全》',date: '2010-6',price: 128.00,count: 1}]},methods: {/*getFinalPrice(price){return '¥' + price.toFixed(2)}*/increment(index){this.books[index].count++},// 移除decrement(index){this.books[index].count--},removeHandle(index){this.books.splice(index, 1)}},filters: {// 过滤器是一个函数showPrice(price){return '¥' + price.toFixed(2)}},computed: {totalPrice(){let totalPrice = 0for(let i = 0; i < this.books.length; i++){totalPrice += this.books[i].price * this.books[i].count;}return totalPrice}}
})

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"><!--books不为空时显示--><div v-if="books.length"><table><thead><tr><th></th><th>书籍名称</th><th>出版日期</th><th>价格</th><th>购买数量</th><th>操作</th></tr></thead><tbody><tr v-for="(item, index) in books"><td>{{item.id}}</td><td>{{item.name}}</td><td>{{item.date}}</td><td>{{item.price | showPrice}}</td><td><!--移除:限制为1时不能点击--><button @click="decrement(index)" :disabled="item.count<=1">-</button>{{item.count}}<button @click="increment(index)">+</button></td><td><button @click="removeHandle(index)">移除</button></td></tr></tbody></table><h3>总价格:{{totalPrice | showPrice}}</h3></div><!--购物车为空时显示--><h3 v-else>购物车为空......</h3>
</div><script src="../js/vue.js"></script>
<script src="main.js"></script>
</script>
</body>
</html>

计算总价格也可以写为:

totalPrice(){let totalPrice = 0for(let book of this.books){totalPrice += book.price * book.count}return totalPrice
}

二、javaScript高阶函数的使用

数组操作的高级函数:

以前的编程范式:

  • 命令式编程
  • 声明式编程:

另一种分类方式:

  • 面向对象编程:谁是第一公民(对象)
  • 函数式编程:第一公民(函数),尽可能的把一些东西抽象程函数,函数式编程可以支持链式调用

例如:

const nums = [10, 20, 111, 222, 444, 40, 50]
// 需求1:取出所有小于100的数字
// 以前的方式
let newNums = []
for(let n of nums){if(n < 100){newNums.push(n)}
}// 需求2:将所有小于100的数组*2
let new2Nums = []
for(let n of newNums){new2Nums.push(n * 2)
}// 需求3:将所有的数字相加得到最终结果
let total = 0
for(let n of new2Nusm){total += n
}

以上的需求使用高级函数编程:filter(),他需要传一个回调函数(即传一个函数),该回调函数的执行节点:它会在每一次从数组中遍历出来一个数字的时候,就去执行我们传入的函数,并且把遍历出来的数字传给我们的函数,filter中的回调函数有一个要求:必须返回一个Boolean值,当返回true时,函数内部会自动将这次回调传入的值加入到新的数组中,当返回false时,函数内部会过滤掉这次传入的值

const nums = [10, 20, 111, 222, 444, 40, 50]
let newNums = nums.filter(function(n){return n < 100
})

高级函数map()的使用:

map()也需要传入一个回调函数,map()传入的函数的返回值作为另一个数组的新的值加入到新的数组中

const nums = [10, 20, 111, 222, 444, 40, 50]
let newNums = nums.filter(function(n){return n < 100
})
let new2Nums = newNums.map(function(n){return n * 2
})

高级函数reduce()的使用:需要出入一个回调函数,回调函数中需要传两个值,一个是前一个值(previousVlaue)和当前遍历的值,这里的previousValue是上一次遍历的值

reduce还有一个重载函数,该重载函数第一个为回调函数,第二个为初始化值

作用:对数组中所有的内容进行汇总

const nums = [10, 20, 111, 222, 444, 40, 50]
let newNums = nums.filter(function(n){return n < 100
})
let new2Nums = newNums.map(function(n){return n * 2
})// 对new2Nums求和
// 上一次输出的结果:即new2Nums = [20, 40, 80, 100]
// 使用reduce()函数,使用第二个重载函数
// 第一次: preValue 0(自定义初始化值为0),n 20
// 第二次: preValue 20  n 40
// 第三次: preValue 60  n 80
// 第四次: preValue 140 n 100
let total = new2Nums.reduce(function(preValue, n){return preValue + n
}, 0)// 最终:total = 240

以上的需求使用更简洁的代码:

const nums = [10, 20, 111, 222, 444, 40, 50]
let total = nums.filter(function(n){return n < 100
}).map(function(n){return n * 2
}).reduce(function(preValue, n){return preValue + n
},0)

还可以使用更简洁的写法:lambda表达式

const nums = [10, 20, 111, 222, 444, 40, 50]
let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n)

上面的计算总价的计算属性可以写为:

computed: {totalPrice(){return this.books.reduce(function(preValue, book){return preValue + book.price * book.count}, 0)}
}

2.1、v-model

v-model:表单绑定,表单双向绑定

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><input type="text" v-model="message">{{message}}
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

现在vue data中的数据和表单中的数据就进行了双向绑定,即message中的数据会自动填充到表单中,并且只要data或是输入框中的数据发生了变化,另外一方的数据也会同步(及双向绑定)。

以前的mastche是响应式的,即只有vue中的数据变了,才会显示到前端,而前端的数据变了并不会影响到后端。

双向绑定的原理:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--v-model相当于两个指令的集合:v-on绑定input的value属性和v-bind监听input的input事件 --><!--首首先使用v-on绑定input的value属性,让vue data中的数据能显示到前端,现在实现了响应式(如果前端输入框修改了数据,message并不会变化)--><input type="text" :value="message"><!--其次监听input的input事件--><input type="text" :value="message" @:input="valueChange">{{message}}
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {valueChange(event){this.message = event.target.value}}})
</script>
</body>
</html>

简化写法:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--v-model相当于两个指令的集合:v-on绑定input的value属性和v-bind监听input的input事件 --><!--首首先使用v-on绑定input的value属性,让vue data中的数据能显示到前端,现在实现了响应式(如果前端输入框修改了数据,message并不会变化)--><input type="text" :value="message"><!--其次监听input的input事件--><input type="text" :value="message" @:input="message = $event.target.vlaue">{{message}}
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {valueChange(event){this.message = event.target.value}}})
</script>
</body>
</html>

2.2、v-model和radio的结合使用

当存在多个单选框时:可以使用v-model直接绑定一个radio中的值到vue的data中

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--单选框:同一个name是同一组--><label for="male">男<!--当单选框组绑定了同一个遍历时,可以不要name属性,只要v-model绑定的变量相同,他们仍然是同一个组--><input type="radio" id="male" name="sex" value="男" v-model="sex"></label><label for="female">女<input type="radio" id="female" name="sex" value="女" v-model="sex"></label><h3>你选择的性别是:{{sex}}</h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',// 给定一个默认值sex: '女'}})
</script>
</body>
</html>

效果:

2.3、复选框checkbox

v-model实现单个多选框:一般是跟一个Boolean值绑定

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><label for="agree"><input type="checkbox" id="agree" v-model="isAgree">同意协议</label><h4>你选择的是:{{isAgree}}</h4><button :disabled="!isAgree">下一步</button></div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',isAgree: false}})
</script>
</body>
</html>

v-model绑定到一个Boolean值是,当点击时该布尔值会执行取反操作

当多选框有多个时:一般是跟一个数组绑定,当用户选择了某个多选框时,v-model绑定的数组就会把该多选框对应的value的值加入到数组中,取消选中时,v-model又会自动移除该值

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><input type="checkbox" value="篮球" v-model="hobbies">篮球<input type="checkbox" value="台球" v-model="hobbies">台球<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球<input type="checkbox" value="足球" v-model="hobbies">足球<h4>你的爱好是:{{hobbies}}</h4>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',hobbies: []}})
</script>
</body>
</html>

select(下拉框):也分为选择一个多个的情况

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--选择一个--><select name="test" id="" v-model="fruit"><option value="苹果">苹果</option><option value="香蕉">香蕉</option><option value="桃">桃</option><option value="西瓜">西瓜</option><option value="李子">李子</option></select><h4>你选择的水果是:{{fruit}}</h4>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',// 设置默选中fruit: '李子'}})
</script>
</body>
</html>

选择单个:

选择多个:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--选择一个--><select name="test" id="" v-model="fruit"><option value="苹果">苹果</option><option value="香蕉">香蕉</option><option value="桃">桃</option><option value="西瓜">西瓜</option><option value="李子">李子</option></select><h4>你选择的水果是:{{fruit}}</h4><!--选择多个--><select name="test2" id="t2" v-model="fruits" multiple><option value="苹果">苹果</option><option value="香蕉">香蕉</option><option value="桃">桃</option><option value="西瓜">西瓜</option><option value="李子">李子</option></select><h4>你选择的水果是:{{fruits}}</h4>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',// 设置默选中fruit: '李子',fruits: []}})
</script>
</body>
</html>

可以按住ctl键选择多个

2.4、input中的值绑定

我们之前input中的值都是通过value属性绑定的,我们也可以将input中的值在vue的data中预先写好,然后再从vue中取出到前端显示

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><label :for="item" v-for="(item, index) in originHobbies"><input type="checkbox" :value="item" v-model="hobbies" :id="item">{{item}}</label><h4>你的选择是:{{hobbies}}</h4>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',originHobbies: ['篮球', '足球', '排球', '乒乓球', '高尔夫球', '台球'],hobbies: []}})
</script>
</body>
</html>

2.5、v-model修饰符的使用

1、lazy修饰符

lazy:懒加载

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--lazy:懒加载v-model的好处是可以实时绑定前端输入的内容到后台,去点是刷新频率太高,每次输入都会绑定,因此我们可以使用lazy修饰符,当我们输入失去焦点或是回车时再绑定--><input type="text" v-model.lazy="message">{{message}}
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

number修饰符:

input输入的东西都会被认为是字符类型,所以使用numer把数字字符转换为数字类型

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--lazy:懒加载v-model的好处是可以实时绑定前端输入的内容到后台,去点是刷新频率太高,每次输入都会绑定,因此我们可以使用lazy修饰符,当我们输入失去焦点或是回车时再绑定--><input type="text" v-model.lazy="message">{{message}}<!--number修饰符:把输入转换为数字--><br><input type="number" v-model.number="age"><h4>{{age}}--{{typeof(age)}}</h4>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',age: 0}})
</script>
</body>
</html>

trim修饰符:去除字符串两端空格

虽然浏览器显示时会帮我们自动去除空格,但后端存储的字符串中左右两端的空格还是存在的。

使用trim后

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--lazy:懒加载v-model的好处是可以实时绑定前端输入的内容到后台,去点是刷新频率太高,每次输入都会绑定,因此我们可以使用lazy修饰符,当我们输入失去焦点或是回车时再绑定--><input type="text" v-model.lazy="message">{{message}}<!--number修饰符:把输入转换为数字--><br><input type="number" v-model.number="age"><h4>{{age}}--{{typeof(age)}}</h4><!--trim修饰符:取出左右两端的空格--><br><input type="text" v-model.trim="content"><h3>输入为:{{content}}</h3>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',age: 0,content: ''}})
</script>
</body>
</html>

空格已被去除

三、组件化开发

注册组件的基本步骤:

  • 创建组件构造器
  • 注册组件
  • 使用组件

3.1、vue组件的基本使用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h3>我是标题</h3><p>我是内容,哈哈哈哈哈哈哈哈哈哈哈哈</p><p>我是内容,呵呵呵呵呵呵呵呵呵呵呵</p>
</div>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

上面的内容如果要显示多次,就可以把它抽象为一个组件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><my-cpn></my-cpn><!--使用多次--><my-cpn></my-cpn><my-cpn></my-cpn><my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>// 1.创建组件构造器对象const cpnC = Vue.extend({// ES6的新语法: ``包含的字符串可以换行template: `<div><h3>我是标题</h3><p>我是内容,哈哈哈哈哈哈哈哈哈哈哈哈</p><p>我是内容,呵呵呵呵呵呵呵呵呵呵呵</p></div>`})// 2.注册组件:Vue.component('组件的标签名', 组件的构造器对象)Vue.component('my-cpn', cpnC)const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

3.2、全局组件和局部组件

以下方式注册的组件为全局组件:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>const cpnC = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,哈哈哈</p></div>`})Vue.component('my-cpn', cpnC)const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

全局组件:意味着可以同时再多个vue实列下使用,如下,再创建一个vue实列挂载app2:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><my-cpn></my-cpn>
</div><!--刚才注册的组件在这个div下也是可以使用的-->
<div id="app2"><my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>const cpnC = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,哈哈哈</p></div>`})Vue.component('my-cpn', cpnC)const app = new Vue({el: '#app',data: {message: '你好呀'}})const app2 = new Vue({el: '#app2'})
</script>
</body>
</html>

怎么注册的组件才是局部组件:在特定的vue下注册的组件是局部组件:

在Vue实列中使用components注册组件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--<my-cpn></my-cpn>--><cpn></cpn>
</div><!--刚才注册的组件在这个div下也是可以使用的-->
<div id="app2"><!--<my-cpn></my-cpn>--><cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>const cpnC = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,哈哈哈</p></div>`})Vue.component('my-cpn', cpnC)const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: cpnC}})const app2 = new Vue({el: '#app2'})
</script>
</body>
</html>

可以发现:cpn在app2的vue实列中不能使用了

3.3、父组件和子组件

注册组件的方式:

  • 全局组件注册: Vue.componet(组件名,组件构造器)
  • 局部组件注册:在vue是实列中,使用components{组件名:组件构造器}
  • 在组件构造器中使用components注册,这样的好处是可以在组件构造器中模板中该组件注册器中注册过的组件

这里使用第三种方式

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>// 1.创建第一个组件(子组件)const cpnC1 = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,哈哈哈</p></div>`})// 2.创建第二个组件构建器(父组件)const cpnC2 = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,呵呵呵</p><cpn1></cpn1></div>`,// 在组件构造器2中注册组件构造器1components: {cpn1: cpnC1}})const app = new Vue({el: '#app',data: {message: '你好呀'},// 这里的组件构造器2中注册了组件构造器1,然后组件构造器2在这里注册,就能保证两个组件构造器都被注册了components: {cpn2: cpnC2}})
</script>
</body>
</html>

一般认为vue实列也是一个组件

注意:在vue挂载的html下,直接使用在组件构造器中注册的组件是不能被识别的:如下就会报错:

<div id="app"><cpn2></cpn2><cpn1></cpn1>
</div>

如果要是用的话,可以在vue的实列中再注册一次:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn2></cpn2><cpn1></cpn1>
</div>
<script src="../js/vue.js"></script>
<script>// 1.创建第一个组件(子组件)const cpnC1 = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,哈哈哈</p></div>`})// 2.创建第二个组件构建器(父组件)const cpnC2 = Vue.extend({template: `<div><h3>我是标题</h3><p>我是内容,呵呵呵</p><!--遇到cpn1,vue会先在自己的组件构造器中寻找cpn1,如果找不到,才会到全局变量中去找,找到后会把该组件对应的html代码直接把<cpn1></cpn1>替换掉--><cpn1></cpn1></div>`,// 在组件构造器2中注册组件构造器1components: {cpn1: cpnC1}})const app = new Vue({el: '#app',data: {message: '你好呀'},// 这里的组件构造器2中注册了组件构造器1,然后组件构造器2在这里注册,就能保证两个组件构造器都被注册了components: {cpn2: cpnC2,cpn1: cpnC1}})
</script>
</body>
</html>

3.4、注册组件的语法糖

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn1></cpn1><cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>/*语法糖方式注册:注意内部还是调用extend函数语法糖注册全局组件*/Vue.component('cpn1',{template: `<div><h3>我是标题</h3><p>我是内容</p></div>`})const app = new Vue({el: '#app',data: {message: '你好呀'},// 语法糖注册局部组件components: {'cpn2': {template: `<div><h3>我是标题</h3><p>我是内容</p></div>`}}})
</script>
</body>
</html>

也可以写为:key可以加单引号也可以不加

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn1></cpn1><cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>/*语法糖方式注册:注意内部还是调用extend函数语法糖注册全局组件*/Vue.component('cpn1',{template: `<div><h3>我是标题</h3><p>我是内容</p></div>`})const app = new Vue({el: '#app',data: {message: '你好呀'},// 语法糖注册局部组件components: {cpn2: {template: `<div><h3>我是标题</h3><p>我是内容</p></div>`}}})
</script>
</body>
</html>

语法糖优点:省去了调用extend的步骤

组件模板的简单分离写法:

1、分离写法一

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn>
</div><!--模板写在html的script中,注意类型必须是text/x-template-->
<script type="text/x-template" id="cpn">
<div><h3>我是标题</h3><p>我是内容</p>
</div>
</script>
<script src="../js/vue.js"></script>
<script>// 使用分离写法注册全局组件,通过script的id获得template内容Vue.component('cpn', {template: '#cpn'})const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

分离写法2:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn>
</div><!--分离写法2:直接使用template标签-->
<template id="cpn"><div><h3>我是标题</h3><p>我是内容</p></div>
</template>
<script src="../js/vue.js"></script>
<script>Vue.component('cpn', {template: '#cpn'})const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

组件可以访问vue中的数据码?

组件内部是不能直接访问vue实列中的数据的。

结论:组件中也有自己的data属性,但data不能是一个对象,只能以一个函数。而且这个函数返回一个对象,对象内部保存着数据。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn>
</div><!--分离写法2:直接使用template标签-->
<template id="cpn"><div><h3>{{title}}</h3><p>我是内容</p></div>
</template>
<script src="../js/vue.js"></script>
<script>Vue.component('cpn', {template: '#cpn',data(){return{title: '我是标题'}}})const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

vue组件中的data为什么必须是一个组件:

需求:把之前的计数器案例封装为一个组件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--各个组件实列,使用的是不同的data()函数对象,即我们每次创建一个组件实列,就会返回一个data对象,从而保证每个组件实列不共享data对象空间,也就保证了多个组件都有一个独享的data空间,从而保证了数据的安全--><cpn></cpn><cpn></cpn><cpn></cpn>
</div><template id="cpn"><div><h3>当前计数:{{counter}}</h3><button @click="increment">+</button><button @click="decrement">-</button></div>
</template>
<script src="../js/vue.js"></script>
<script>// 注册组件Vue.component('cpn', {template: '#cpn',data(){return{counter: 0}},methods: {increment(){this.counter++},decrement(){this.counter--}}})const app = new Vue({el: '#app',data: {message: '你好呀'}})
</script>
</body>
</html>

各个组件的counter并不会相互影响:这也是组件中的data必须返回一个函数的原因,因为每个函数都是返回一个对象,每个对象在内存空间中的地址是互不相同的。

3.5、父子组件的通信

下面的代码中,把vue实列看作父组件,组件看作子组件

props:使用数组传递数据:[]数组中单引号中的字符就是在使用该组件时的属性,通过v-bind绑定这个属性就可以把这个值绑定到vue实列中的变量

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--传递数据给子组件--><cpn v-bind:cmovies="movies" :cmessage="message"></cpn>
</div><template id="cpn"><div><p>{{cmovies}}</p><p>{{cmessage}}</p></div>
</template>
<script src="../js/vue.js"></script>
<script>// 定义模板对象:在vue中注册实列:// components{//  cpn: {对象}// }//下面定义的对象就是vue实列中要注册的对象const cpn = {template: '#cpn',// 可以使用数组形式传递数据props: ['cmovies', 'cmessage']}const app = new Vue({el: '#app',data: {message: '你好呀',movies: ['海王', '海贼王', '海尔兄弟']},// 注册组件:使用属性增强写法components: {cpn}})
</script>
</body>
</html>

注意:在使用组件时必须使用v-bind,如果不使用的话,vue会直接把双引号中的字符穿个组件props数组中的变量。

错误使用如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--传递数据给子组件--><cpn cmovies="movies" cmessage="message"></cpn>
</div><template id="cpn"><div><p>{{cmovies}}</p><p>{{cmessage}}</p></div>
</template>
<script src="../js/vue.js"></script>
<script>// 定义模板对象:在vue中注册实列:// components{//  cpn: {对象}// }//下面定义的对象就是vue实列中要注册的对象const cpn = {template: '#cpn',// 可以使用数组形式传递数据props: ['cmovies', 'cmessage']}const app = new Vue({el: '#app',data: {message: '你好呀',movies: ['海王', '海贼王', '海尔兄弟']},// 注册组件:使用属性增强写法components: {cpn}})
</script>
</body>
</html>

使用数组的话:[]中放的是字符,而我们又把它当作变量使用,不符合习惯。

传递方式二:props中使用对象(好处:可以指定类型)

props: {//类型限制cmovies: Array,cmessage: String
}

还可以提供默认值:默认值在父组件没有传递值的时候显示

props: {cmovies: Array,cmessage: {type: String,default: 'hello',// required为true时要求使用的时候必须传值required: true}
}

类型是数组或对象时默认值需要使用工厂函数返回

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--传递数据给子组件--><cpn :cmessage="message"></cpn>
</div><template id="cpn"><div><p>{{cmovies}}</p><p>{{cmessage}}</p></div>
</template>
<script src="../js/vue.js"></script>
<script>// 定义模板对象:在vue中注册实列:// components{//  cpn: {对象}// }//下面定义的对象就是vue实列中要注册的对象const cpn = {template: '#cpn',// 可以使用数组形式传递数据props: {cmovies: {type: Array,default(){return []}},cmessage: {type: String,default: 'hello',required: true}}}const app = new Vue({el: '#app',data: {message: '你好呀',movies: ['海王', '海贼王', '海尔兄弟']},// 注册组件:使用属性增强写法components: {cpn}})
</script>
</body>
</html>

其他写法:

注意:目前的vue中v-bind不支持驼峰命名,因此在有v-bind的地方,驼峰命名需要转换为-连接的形式

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn :cinfo="info"></cpn>
</div><template id="cpn"><div>{{cinfo}}</div>
</template>
<script src="../js/vue.js"></script>
<script>const cpn = {template: '#cpn',props: {cinfo: {type: Object,default(){return {}}}}}const app = new Vue({el: '#app',data: {message: '你好呀',info: {name: 'why',age: 18,height: 188}},components: {cpn}})
</script>
</body>
</html>

有驼峰命名时:

转化例子:myChildrenMessage 转换为 my-children-message

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn :c-info="info"></cpn>
</div><template id="cpn"><div>{{cInfo}}</div>
</template>
<script src="../js/vue.js"></script>
<script>const cpn = {template: '#cpn',props: {cInfo: {type: Object,default(){return {}}}}}const app = new Vue({el: '#app',data: {message: '你好呀',info: {name: 'why',age: 18,height: 188}},components: {cpn}})
</script>
</body>
</html>

父子间的通信:子传父

子组件通过$emit(‘要发送的事件名’,传递的参数)发送事件,父组件通过使用子组件时,监听子组件发送的事件获取子组件发来的数据:(注意子组件中不要使用驼峰命名),在脚手架中可以使用驼峰命名

父组件监听语法:@子组件发送的事件名=“父组件中的处理函数”

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--父组件监听子组件发送的事件:注意:这里如果省略了参数,它会把子组件传来的参数item传递给cpnClick函数--><cpn @item-click="cpnClick"></cpn>
</div><template id="cpn"><div><button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button></div>
</template>
<script src="../js/vue.js"></script>
<script>const cpn = {template: '#cpn',data(){return{categories: [{id: 'aaa', name: '热门推荐'},{id: 'bbb', name: '手机数码'}]}},methods: {btnClick(item){// 把我们点击了谁传递给父组件:$emit('要发送的事件名',传递的参数)this.$emit('item-click', item)console.log(item);}}}const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn},methods: {cpnClick(item){console.log("父组件", item);console.log("conClick")}}})
</script>
</body>
</html>

3.6、知识回顾

计算属性

  • 计算属性的本质:get,set
  • 计算属性和methods的对比
    • 使用多次时,计算属性只调用一次,methods调用多次
    • 计算属性有缓存

事件监听

  • 事件监听的基本使用
  • 参数问题

修饰符

  • stop
  • prevent
  • .enter
  • .once
  • .native

条件判断

  • v-if/v-else-if/v-else
  • 登录小案例
  • v-show

循环遍历

  • 数组遍历

  • 对象遍历

  • 数组的那些方法是响应式的

  • 作业完成

书籍案例

v-model的基本使用

  • v-model的基本使用
  • v-model和radio/checkbox/select

v-model的修饰符

  • lazy
  • number
  • trim

组件化开发

  • 认识组件
  • 组件的基本使用
  • 全局组件和局部组件
  • 父组件和子组件
  • 组件注册的语法糖
  • 模板的分类写法
    • script
    • template

组件数据的存放

  • 子组件不能直接访问父组件
  • 子组件中有自己的data,而且必须是一个函数
  • 为什么必须是一个函数

父子组件的通信

  • 父传子:props
  • 子传父:$emit

3.7、父子组件通信-结合双向绑定案例

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn :number1="num1" :number2="num2"></cpn>
</div>
<template id="cpn"><div><h4>props:{{number1}}</h4><input type="text" v-model="number1"><h4>props:{{number2}}</h4><input type="text" v-model="number2"></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',num1: 1,num2: 0},components: {cpn: {template: '#cpn',props: {number1: Number,number2: Number}}}})
</script>
</body>
</html>

子组件中的props和input直接进行双向绑定,这是官方不推荐使用的方式,因为props中的数据是父组件传来的,它的值应该有父组件去改变,如果直接通过v-model直接修改,将会导致修改混乱,因此报错(虽然可以修改),官方推荐我们使用计算属性或者data去修改props中的值

正确的修改方法

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn :number1="num1" :number2="num2"></cpn>
</div>
<template id="cpn"><div><h4>props:{{number1}}  data:{{dnumber1}}</h4><input type="text" v-model="dnumber1"><h4>props:{{number2}}  data:{{dnumber2}}</h4><input type="text" v-model="dnumber2"></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',num1: 1,num2: 0},components: {cpn: {template: '#cpn',props: {number1: Number,number2: Number},data(){return{dnumber1: this.number1,dnumber2: this.number2}}}}})
</script>
</body>
</html>

现在的需求:希望子组件中的输入框中的值改变的时候,父组件中的num1和num2也随着改变

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn :number1="num1" :number2="num2"@num1change="num1Change"@num2change="num2Change"></cpn>
</div>
<template id="cpn"><div><h4>props:{{number1}}  data:{{dnumber1}}</h4><!--v-model等价于--><!--<input type="text" v-bind="dnumber1" @input="dnumber1=$event.target.value">--><input type="text" :value="dnumber1" @input="num1Input"><h4>props:{{number2}}  data:{{dnumber2}}</h4><input type="text" :value="dnumber2" @input="num2Input" ></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',num1: 1,num2: 0},methods:{num1Change(value){// 因为value是子组件输入框传来的数据,默认是字符串,所以因该把它转换为int型// 转换方法一:使用*数字的方法: 如 this.num1 = value * 1// 方式二:使用parseInt方法this.num1 = parseInt(value)},num2Change(value){this.num2 = parseInt(value)}},components: {cpn: {template: '#cpn',props: {number1: Number,number2: Number},data(){return{dnumber1: this.number1,dnumber2: this.number2}},methods: {num1Input(event){this.dnumber1 = event.target.value// 在值改变的同时,发送事件到父组件this.$emit('num1change', this.dnumber1)},num2Input(event){this.dnumber2 = event.target.valuethis.$emit('num2change', this.dnumber2)}}}}})
</script>
</body>
</html>

新的需求:让上面的data是下面data的1/100,下面data是上面data的100倍

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn :number1="num1" :number2="num2"@num1change="num1Change"@num2change="num2Change"></cpn>
</div>
<template id="cpn"><div><h4>props:{{number1}}  data:{{dnumber1}}</h4><!--v-model等价于--><!--<input type="text" v-bind="dnumber1" @input="dnumber1=$event.target.value">--><input type="text" :value="dnumber1" @input="num1Input"><h4>props:{{number2}}  data:{{dnumber2}}</h4><input type="text" :value="dnumber2" @input="num2Input" ></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',num1: 1,num2: 0},methods:{num1Change(value){// 因为value是子组件输入框传来的数据,默认是字符串,所以因该把它转换为int型// 转换方法一:使用*数字的方法: 如 this.num1 = value * 1// 方式二:使用parseInt方法this.num1 = parseFloat(value)},num2Change(value){this.num2 = parseFloat(value)}},components: {cpn: {template: '#cpn',props: {number1: Number,number2: Number},data(){return{dnumber1: this.number1,dnumber2: this.number2}},methods: {num1Input(event){this.dnumber1 = event.target.value// 在值改变的同时,发送事件到父组件this.$emit('num1change', this.dnumber1)// 同时修改dnumber2的值this.dnumber2 = this.dnumber1 * 100// 同时让如组件的num2也改变this.$emit('num2change', this.dnumber2)},num2Input(event){this.dnumber2 = event.target.valuethis.$emit('num2change', this.dnumber2)this.dnumber1 = this.dnumber2 / 100// 同时让如组件的num2也改变this.$emit('num1change', this.dnumber1)}}}}})
</script>
</body>
</html>

案例逻辑整理:

3.8、父子组件的访问方式

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn><!--需求点击按钮的时候打印子组件的信息--><button @click="btnClick">点击</button>
</div><template id="cpn"><div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {btnClick(){// 通过$children访问子组件console.log(this.$children);}},components: {cpn: {template: '#cpn',methods: {showMessage(){console.log("showMessage");}}}}})
</script>
</body>
</html>

可以发现this.$children拿到的是一个数组,并且这个数组中有一个VueComponent的vue组件,该组件可以看到我们定义的方法

于是我们通过该数组中的vue组件拿到刚才我们定义个函数

this.$children[0].showMessage()

拷贝三个组件:

<div id="app"><cpn></cpn><cpn></cpn><cpn></cpn><!--需求点击按钮的时候打印子组件的信息--><button @click="btnClick">点击</button>
</div>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn><cpn></cpn><cpn></cpn><!--需求点击按钮的时候打印子组件的信息--><button @click="btnClick">点击</button>
</div><template id="cpn"><div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {btnClick(){// 通过$children访问子组件console.log(this.$children);// 拿到子组件中的方法this.$children[0].showMessage()// 去除name的namefor(let c of this.$children){console.log(c.name);c.showMessage()}}},components: {cpn: {template: '#cpn',data(){return{name: '我是子组件的name'}},methods: {showMessage(){console.log("showMessage");}}}}})
</script>
</body>
</html>

如果我们要拿到子组件中内容,一般不会使用children(如果以后的代码修改了,通过下标闹到的内容就会改变),而是使用另一个方法:children(如果以后的代码修改了,通过下标闹到的内容就会改变),而是使用另一个方法:children(如果以后的代码修改了,通过下标闹到的内容就会改变),而是使用另一个方法:refs

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn ref="a"></cpn><cpn ref="b"></cpn><cpn ref="c"></cpn><!--需求点击按钮的时候打印子组件的信息--><button @click="btnClick">点击</button>
</div><template id="cpn"><div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},methods: {btnClick(){// 通过$children访问子组件console.log(this.$children);// 拿到子组件中的方法this.$children[0].showMessage()// 去除name的namefor(let c of this.$children){console.log(c.name);c.showMessage()}// 通过ref拿到子组件对象console.log(this.$refs.a)// 拿到name属性console.log(this.$refs.a.name)}},components: {cpn: {template: '#cpn',data(){return{name: '我是子组件的name'}},methods: {showMessage(){console.log("showMessage");}}}}})
</script>
</body>
</html>

3.9、子访问父

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn>
</div><template id="cpn"><div><h4>我是子组件</h4><button @click="btnClick">按钮</button></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn',methods: {// 1.访问父组件:$parentbtnClick(){console.log(this.$parent);}}}}})
</script>
</body>
</html>

打印出来是一个Vue,这是因为该子组件的父亲就是vue实列

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn>
</div><template id="cpn"><div>我是cpn组件<ccpn></ccpn></div>
</template><template id="ccpn"><div><h4>我是子组件</h4><button @click="btnClick">按钮</button></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn',data(){return{name: '我是cpn组件的name'}},// 在嵌套一个子组件components: {ccpn: {template: '#ccpn',methods: {// 1.访问父组件:$parentbtnClick(){console.log(this.$parent);// 拿到父组件的nameconsole.log(this.$parent.name);// 访问根组件console.log(this.$root);// 访问根组件的messageconsole.log(this.$root.message);}}}}}}})
</script>
</body>
</html>

四、插槽

4.1、插槽的基本使用

插槽:slot

需求不一样:

希望插槽具有扩展性

使用插槽:抽取共性,保留不同

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn><button>按钮</button></cpn><cpn><span>哈哈哈</span></cpn><cpn><i>呵呵呵</i></cpn><cpn><button>按钮</button></cpn>
</div><template id="cpn"><div><h4>我是组件</h4><p>哈哈哈</p><slot></slot></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn'}}})
</script>
</body>
</html>

也可以给插槽一个默认值:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn><cpn><span>哈哈哈</span></cpn><cpn><i>呵呵呵</i></cpn><cpn></cpn>
</div><template id="cpn"><div><h4>我是组件</h4><p>哈哈哈</p><slot><button>按钮</button></slot></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn'}}})
</script>
</body>
</html>

如果一个插槽中插入多个值,则插槽会把所有的内容都插进去

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn><i>呵呵呵</i><span>哈哈哈</span><button>按钮</button></cpn>
</div><template id="cpn"><div><h4>我是组件</h4><p>哈哈哈</p><slot><button>按钮</button></slot></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn'}}})
</script>
</body>
</html>

4.2、具名插槽

指定位置插入:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn><span>标题</span></cpn>
</div><template id="cpn"><div><slot><span>左边</span></slot><slot><span>中间</span></slot><slot><span>右边</span></slot></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn'}}})
</script>
</body>
</html>

这时vue会把所有的都替换掉,要解决这个问题,需要指定名字(具名)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn><span>标题</span></cpn>
</div><template id="cpn"><div><slot name="left"><span>左边</span></slot><slot name="mid"><span>中间</span></slot><slot name="right"><span>右边</span></slot><slot>哈哈</slot></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn'}}})
</script>
</body>
</html>

这时只会替换没有名字的:

具名插槽的使用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><!--指定插槽替换--><cpn><span slot="mid">标题</span></cpn><cpn><button slot="left">返回</button></cpn>
</div><template id="cpn"><div><slot name="left"><span>左边</span></slot><slot name="mid"><span>中间</span></slot><slot name="right"><span>右边</span></slot></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn'}}})
</script>
</body>
</html>

4.3、编译的作用域

组件和vue实列中各有一个isShow,其中实列中的isShow为true, 组件中的为false,我们在使用组件时,使用isShow,他用的是哪里的isShow?

使用的是vue实例中的isShow

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn v-show="isShow"></cpn>
</div><template id="cpn"><div><h4>我是子组件</h4><p>哈哈哈</p></div>
</template>
<script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀',isShow: true},components: {cpn: {template: '#cpn',data(){return{isShow: false}}}}})
</script>
</body>
</html>

修改为false时:

通过实验发现,绑定的变量它只看他在那个模板下,刚才我们使用cpn组件是在vue实例下,所以他使用的是vue实例下的遍变量,它不管使用的是那个组件

4.4、作用域插槽

父组件替换插槽的标签,但是内容有子组件提供

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn>
</div><template id="cpn"><div><ul><li v-for="item in pLanguages">{{item}}</li></ul></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn',data(){return {pLanguages: ['javaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']}}}}})
</script></body>
</html>

默认展示效果

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn><cpn><!--目的是获取子组件中的pLanguages2.5.x以下,下面必须使用template模板,2.5.x以上使用div也可以注意:slot-scope=后面的名字也是随便起的--><template slot-scope="slot"><span v-for="item in slot.data">{{item}} - </span></template></cpn><cpn><!-- 以*分割--><template slot-scope="slot"><span v-for="item in slot.data">{{item}} * </span></template></cpn>
</div><template id="cpn"><div><!--这里的data的名字是随便起的,如也可以用:datas等--><slot :data="pLanguages"><ul><li v-for="item in pLanguages">{{item}}</li></ul></slot></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn',data(){return {pLanguages: ['javaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']}}}}})
</script></body>
</html>

去除尾部的-或*:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><cpn></cpn><cpn><!--目的是获取子组件中的pLanguages2.5.x以下,下面必须使用template模板,2.5.x以上使用div也可以注意:slot-scope=后面的名字也是随便起的--><template slot-scope="slot"><span>{{slot.data.join(' * ')}}</span></template></cpn><cpn><!-- 以*分割--><template slot-scope="slot"><span>{{slot.data.join(' - ')}}</span></template></cpn>
</div><template id="cpn"><div><!--这里的data的名字是随便起的,如也可以用:datas等--><slot :data="pLanguages"><ul><li v-for="item in pLanguages">{{item}}</li></ul></slot></div>
</template><script src="../js/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '你好呀'},components: {cpn: {template: '#cpn',data(){return {pLanguages: ['javaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']}}}}})
</script></body>
</html>

作用插槽:就是让父组件拿到子组件中的数据,并以自己希望的格式展示。

五、模块化开发

javascript

别人改了自己的变量,导致的问题:

示例项目结构

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<!--
1、项目组长
2、小明
3、小红
-->
<script src="main.js"></script>
<script src="a.js"></script>
<script src="b.js"></script>
<script src="xm.js"></script>
<script></script>
</body>
</html>

main.js:没有写内容

a.js

// 小明
var name = '小明'
var age = 20
function sum(num1, num2){return num1 + num2
}var flag = trueif(flag){console.log(sum(12, 20))
}

b.js

// 小红
var name = '小红'
var flag = false
console.log(name)

xm.js

if(flag){console.log("小明是天才");
}

运行时发现:小明是天才没有输出,因为小明知道自己定义了一个flag变量,并且为true

5.1、前端模块化雏形和Commonjs

之前问题的解决方式之一:

使用匿名函数闭包解决

a.js

// 小明
(function(){var name = '小明'var age = 20function sum(num1, num2){return num1 + num2}var flag = trueif(flag){console.log(sum(12, 20))}
})()

但这时又带了一个问题,我想在xm.js中使用flag和sum函数时,发现不能用了,即代码的复用性大打折扣。

解决方式二:模块化

index.html:一样

a.js

// 小明
// 导出后:moduleA就等价于obj,而moduleA属于全局变量
var moduleA = (function(){// 定义导出的对象var obj = {}var name = '小明'var age = 20function sum(num1, num2){return num1 + num2}var flag = trueif(flag){console.log(sum(12, 20))}// 给obj动态加一个属性flag和一个函数sumobj.flag = flag;obj.sum = sum;// 导出return obj
})()

b.js

// 小红
var moduleB = (function(){var obj = {}var name = '小红'var flag = falseconsole.log(name)obj.flag = flagreturn obj
})()

xm.js

if(moduleA.flag){console.log("小明是天才");
}

结果

这是之前常用的解决命名冲图的办法,现在几乎不这样做了,因为现在有很多别人写好的,规范的模块化开发框架规范,我们直接使用就可以了。

常见的模块化规范:

CommonJS,AMD,CMD,也有ES6的Modules

CommonJS只是一个规范,具体的实现有一个比较著名的:Node

了解CommonJs:以下的语法需要底层Node的支持

5.2、ES6的模块化实现

导出/导入: export/import

示例项目结构

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><!--使用了type="module"说明导入的js应该看成一个模块,从而避免命名冲突-->
<script src="a.js" type="module"></script>
<script src="b.js" type="module"></script>
<script src="xm.js" type="module"></script>
</body>
</html>

a.js

导出方式一:

var name = '小明'
var age = 18
var flag = truefunction sum(num1, num2){return num1 + num2
}if(flag){console.log(sum(10, 20));
}// 导出flag和sum
// 导出一个对象:export{}
export{// es6的增强写法flag,sum
}

b.js

var name = '小红'
var age = 20
var flag = false

xm.js

import {flag, sum} from "./a.js"
if(flag){console.log("小明是天才");
}

导出方式二:a.js

var name = '小明'
var age = 18
var flag = truefunction sum(num1, num2){return num1 + num2
}if(flag){console.log(sum(10, 20));
}// 导出flag和sum
// 导出一个对象:export{}
// 导出方式一
export{// es6的增强写法flag,sum
}// 导出方式二
export var num1 = 1000
export var height = 188// 导出函数或类
export function mul(num1, num2){return num1 * num2
}// 导出类
export class Person{run(){console.log("在奔跑");}
}// export default:导入时可以自己命名,在一个js文件中,export default只能有一个
const address = '北京市'
export default address

xm.js

import {flag, sum} from "./a.js"
import {num1, height}  from "./a.js";if(flag){console.log("小明是天才");
}console.log(num1, height)// 导入类和函数
import {mul, Person} from './a.js'console.log(mul(220, 30));const p = new Person()
p.run()// 导入default,可以自定义名字
import addr from './a.js'console.log(addr);

同一全部导入:

import {flag, sum} from "./a.js"
import {num1, height}  from "./a.js";if(flag){console.log("小明是天才");
}console.log(num1, height)// 导入类和函数
import {mul, Person} from './a.js'console.log(mul(220, 30));const p = new Person()
p.run()// 导入default,可以自定义名字
import addr from './a.js'console.log(addr);// 同一全部导入
import * as ex from './a.js'console.log(ex.sum(12, 13));

上面的代码属于ES6规范,es6规范,浏览器都做了支持。

六、Webpack

6.1、认识webpack

webpack是一个现代的JavaScript应用静态模块打包工具:模块和打包

使用webpack打包,如果使用了CommonJs等相关代码,它会自动转换,以得到浏览器支持的代码。

webpack为了可以正常运行,必须依赖node环境。

node环境为了可以正常执行很多的代码,会依赖各种依赖包,所以node环境就需要一个包管理工具:npm

6.2、安装webpack

查看是否安装node环境

node -v

要求:node环境使用8.9以上版本

全局安装webpack(这里先制定版本号3.6.0,因为vue cli2依赖该版本,该版本可以看到依赖环境,如果使用cli3,很多信息就不能看到)

npm install webpack@3.6.0 -g

查看webpack版本:以确认是否安装

webpack --version

局部安装webpack(后续才需要)

--save-dev  是开发时依赖,项目打包后不需要使用的
cd 对应目录
npm install webpack@3.6.0 --save-dev

局部安装的作用:以后在packjson中定义的脚本可以用到

6.3、webpack起步

项目结构:

src:开发目录(源码)

dist:发布目录

使用了webpack后,js使用模块化开发,且可以使用任何你想使用的规范都可以,并且可以混合使用

mathUtils.js

// 使用commonJs模块化开发
function add(num1, num2){return num1 + num2
}function mul(num1, num2){return num1 * num2
}module.exports = {// js属性增强写法add,mul
}

main.js

// commonJS的导入
const {add, mul} = require('./mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));

使用webpack打包:webpack会自动处理文件之间的依赖,向main.js中依赖了mathUtils.js,则mathUtils.js也会被打包

# 把src目录下的main.js打包到dist下的bundle.js中
webpack ./src/main.js ./dist/bundle.js

打包成功:

webpack3.6.0的可以打包到:bundle.js

webpack5.x会打包到main.js

index.js

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><!--使用了commonJS规范开发,并不能直接使用script导入js文件,因为浏览器根本就不认识commonJS规范开发的文件
这时就要使用webpack打包为浏览器认识的文件,然后引入这个文件即可-->
<script src="./dist/main.js"></script>
</body>
</html>

需求2:希望自己只用在命令行输入webpack即可把我们的js文件打包输出到指定位置。

如果当前项目中用到一些与node有关的东西,就应该初始化以下项目:初始化项目命令为:npm init

package name: 给自己的包起一个名字,不包含中文即可

version: 可以不填,直接回车

description: 描述,也可以不填

entry point: 入口点,随便写一个,我们目前还用不到(可以先写一个index.js)

test command:回车

后面的一路回车即可

liscense: 开源采用,不开源的化删了也可以

完成后就会生成一个package.json文件

如果package.json中有依赖的话,我们使用npm install命令可以帮我们把所有的依赖都装上。

1、新建一个webpack.config.js文件,文件内容如下

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'}
}

2、在命令行输入webpack即可完成我们刚才的功能

在以后的项目中,我们一般是直接使用npm run build构建项目,那如何把webpack和npm run命令结合起来呢。

1、首先找到package.json文件,里面有一个脚本,该脚本中有一个默认的test命令,以后我们运行npm run test,就会执行test后面的脚本,test可在npm init的时候修改为其他名字,也可以在package.json中直接修改

webpack银蛇build命令:

{"name": "meetwebpack","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "webpack"},"author": "","license": "ISC"
}

现在把bundle.js删了,然后执行npm run build,起效果与web pack一样

这样写的好处:当执行webpack的时候,它会优先去找本地的webpack,如果本地安装了,就使用本地的webpack,如果本地没有安装,他才回去找全局安装的webpack(而我们开发时一般用的都是本地的webpack),但这里我们并没有安装本地webpack,所以用的全局的webpack(但在命令行直接输入webpack时,使用的是全局的webpack)

安装本地依赖:

npm install webpack@3.6.0 --save-dev也可以使用cnpm安装
cnpm install webpack@3.6.0 -D

安装完成后多了一个依赖

6.4、loader

项目结构:

如果有静态资源需要使用loader

新建css文件:

normal.css

body{background-color: red;
}

css文件也当作一个模块,因此我们不应该直接在html文件中引用css文件,而应该也罢css文件打包后在引入(这这样做的好处是,如果有多个css文件,我们打包成一个css文件,在html中引入一次就可以了)

在main.js中依赖css,这样webpack才会去打包(因为webpack会打包main.js中依赖的与之相关的所有依赖)

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')

运行npm run build的时候出错(没有loader)

去webpackjs.com中找相应的loader

1、安装css-loader

npm install --save-dev css-loader如果安装失败,可以使用
cnpm install --save-dev css-loader@2.0.2

2、配置

在webpack.config.js中配置

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'},module: {rules: [{test: /\.css$/,use: ['css-loader']}]}
}

现在再使用npm run build,打包成功

但是现在html中的css效果并没有生效,因为css-loader只负责css文件的加载,并不负责css文件的渲染,要让css生效,还要安装一个loader:style-loader

style-loader:负责将样式添加到DOM

注意:webpack使用多个loader时,是从右向左读取的,所以在webpack.config.js中要反着写(正常情况下,是先把css加载css文件,在把它添加到DOM中,所以use中的第一个是css-loader,然后是style-loader,但放在use时要反着写,如下:

cnpm install style-loader@0.23.1 --save-dev

配置:

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']}]}
}

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><!--使用了commonJS规范开发,并不能直接使用script导入js文件,因为浏览器根本就不认识commonJS规范开发的文件
这时就要使用webpack打包为浏览器认识的文件,然后引入这个文件即可-->
<script src="./dist/bundle.js"></script>
</body>
</html>

样式起效果:

6.5、less文件处理

1、创建一个less文件

提示是否需要使用watcher编译less文件,这里我们不需要,待会我们会使用webpack编译打包

// 定义变量使用@符号,
@font-size: 50px;
@fontColor: orange;
// 把body中的文字大小设置为font-size,字体颜色设置为fontColor
body {font-size: @font-size;color: @fontColor;
}

1、依赖less文件

main.js

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')

现在直接npm run build,报错

2、安装less-loader和less

less的作用:对less文件代码进行加载解析

cnpm install --save-dev less-loader@4.1.0 less@3.9.0

安装后

3、配置

module: {rules: [{test: /\.less$/,use: [{loader: "style-loader" // creates style nodes from JS strings}, {loader: "css-loader" // translates CSS into CommonJS}, {loader: "less-loader" // compiles Less to CSS}]}]}

main.js

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]}]}
}

main.js

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')

6.6、图片文件的处理

网上随便找几张张图片,然后在src目录下新建img文件,把图片放到该目录下

修改normal.css

body{/*background-color: red;*/background: url("../img/jt1.jpg");
}

webpack遇到图片时也会当作模块处理,现在直接执行npm run build时报错,解决办法:

1、安装url-loader

cnpm install --save-dev url-loader@1.1.2

2、配置

{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {// 当加载的图片小于limit时,会将图片编译成base64的字符串形式,如果图片大于limit                       //  时,需要使用file-loaderlimit: 45000}}]
}

3、npm run build

测试

注意:如果图片小,它默认是平铺的

我们刚才的图片是jpg格式的,webpack把jpg格式的图片转换为了base64,然后从服务器请求过来的图片为base64格式的字符串。

注意如果图片大于limit时,需要安装file-loader(不需要额外配置,安装就可以了)

cnpm install file-loader@3.0.1 --save-dev

把limit改小:

use: [{loader: 'url-loader',options: {limit: 8192}}
]

npm run build打包

测试发现图片不显示:原因,webpack打包的时候把图片看作一个模块,会跟js文件一个打包到dist目录下,,而前端找图片是在相对于index.html的目录下索引图片,所以图片路径不对,无法加载

打包后的图片:

html加载图片位置:

解决办法:在webpack中配置publicPath:只要配置了这个路径,以后涉及到url的路径都会拼接上我们配置的路路径。

可以显示,并且签名url已经拼接了/dist。

注意:以后正式上线了,index.html也会放到dist目录下,到时候publicPath就不需要了。

图片打包规范:

即打包后也需要一个文件夹来存储,并且我让图片的名字为原来的名字+hash值(可以指定位数)+图片扩展名)

webpack.config.js

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]}]}
}

打包

并且图片也能自动加载

因为css中的路径也是当前目录下的/img/文件名

body{/*background-color: red;*/background: url("../img/jt1.jpg");
}

6.7、ES6转ES5

刚才的代码并没把ES6的代码转换为ES5,有的浏览器不支持(即不能用正常使用)

ES6的代码转换为ES5需要使用babel工具,同时在使用babel的时候需要用到babel-core

babel-preset-es2015即es6,官网上使用的是:babel-preset-env

env:环境相关,还需要另外配置一些东西

cnpm install --save-dev babel-loader@7 babel-core babel-preset-es2015

配置:

module: {rules: [{test: /\.js$/,exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}}]
}

6.8、webpack配置vue

1.新建03_webpack配置vue,并把刚才的项目复制过去

2.vue以模块化方式导入,并且vue在运行时也需要使用

cnpm install vue --save

3.使用vue进行开发

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')// 使用vue进行开发
import Vue from 'vue'
const app = new Vue({el: '#app',data: {message: 'hello,world'}
})

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app"><h4>{{message}}</h4>
</div><!--使用了commonJS规范开发,并不能直接使用script导入js文件,因为浏览器根本就不认识commonJS规范开发的文件
这时就要使用webpack打包为浏览器认识的文件,然后引入这个文件即可-->
<script src="./dist/bundle.js"></script>
</body>
</html>

报错:不显示vue中的message

vue在构建版本时,会构建两个版本:

1.runtime-only->运行时代码中不可以有任何的template

2.runtime-compiler:代码中可以template,因为编译时版本中包含可以用于编译template的代码。

解决办法:webpack.config.js中配置

resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js,$表示结尾'vue$': 'vue/dist/vue.esm.js'}
}

位置:

我们指定vue.esm.js,这个版本中有可以编译template的代码。

完整配置:

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'}}
}

SPA:单页面复应用中我们一般只有一个index.html,并且这个文件一般是不动的,里面的东西也是固定的,一般如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="app">
</div><!--使用了commonJS规范开发,并不能直接使用script导入js文件,因为浏览器根本就不认识commonJS规范开发的文件
这时就要使用webpack打包为浏览器认识的文件,然后引入这个文件即可-->
<script src="./dist/bundle.js"></script>
</body>
</html>

如果要在index.html中展示东西,可以在main.js中写template,如

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')// 使用vue进行开发
import Vue from 'vue'
const app = new Vue({el: '#app',template: `<div><h4>{{message}}</h4><button @click="btnClick">按钮</button><h4>{{name}}</h4></div>`,methods: {btnClick(){}},data: {message: 'hello,world',name = 'code why'}
})

即定义了el,又定义了template,vue会把el关在的html直接使用template替换掉。

6.9、vue的终极使用方案

main.js

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')// 使用vue进行开发
import Vue from 'vue'
// vue的终极使用方案
const App = {template: `<div><h4>{{message}}</h4><button @click="btnClick">按钮</button><h4>{{name}}</h4></div>`,data(){return {message: 'hello,world',name: 'code why'}},methods: {btnClick(){}}
}const app = new Vue({el: '#app',// 使用vue组件template: '<App></App>',// 注册组件components: {App}
})

在把main.js中的template抽取为js:

app.js

export default {template: `<div><h4>{{message}}</h4><button @click="btnClick">按钮</button><h4>{{name}}</h4></div>`,data(){return {message: 'hello,world',name: 'code why'}},methods: {btnClick(){}}
}

main.js

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')// 使用vue进行开发
import Vue from 'vue'
import App from './vue/app'const app = new Vue({el: '#app',// 使用vue组件template: '<App></App>',// 注册组件components: {App}
})

效果一样

以上的代码还有不好的地方:模板和js没有分离

继续抽取:

在vue目录下新家vue模板

App.vue

<template><!--模板相关代码--><div><h4 class="title">{{message}}</h4><button @click="btnClick">按钮</button><h4>{{name}}</h4></div>
</template><script>
// js相关代码export default {name: "App",data(){return {message: 'hello,world',name: 'code why'}},methods: {btnClick(){}}}
</script><style scoped>/*样式相关代码*/.title {color: green;}
</style>

main.js

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')// 使用vue进行开发
import Vue from 'vue'
// import App from './vue/app'
import App from './vue/App.vue'const app = new Vue({el: '#app',// 使用vue组件template: '<App></App>',// 注册组件components: {App}
})

npm run build报错,因为不知道如何解析.vue文件

1、配置loader:vue-loader

vue-template-compiler(vue模板编译)

cnpm install --save-dev vue-loader vue-template-compiler

2、在webpack.config.js中配置loader

可能遇到的问题:

14.x后的版本要使用vue-loader需要使用一个相关的插件,如不不想装插件的话,可以修改vue-loader的版本为13.x

手动修改版本

运行:cnpm install 安装

问题解决

仍能正常运行

注意:命名vue文件时,一般首字母大写

在创建一个vue文件

Cpn.vue

<template><div><h4>我是cpn组件的标题</h4><p>我是cpn组件的内容</p></div>
</template><script>
export default {name: "Cpn",data(){return{name: 'cpn组件的name'}}
}
</script><style scoped></style>

在App.vue中使用:

<template><!--模板相关代码--><div><h4 class="title">{{message}}</h4><button @click="btnClick">按钮</button><h4>{{name}}</h4><Cpn/></div>
</template><script>
import Cpn from './Cpn.vue'
// js相关代码export default {name: "App",data(){return {message: 'hello,world',name: 'code why'}},methods: {btnClick(){}},// 注册组件components: {Cpn}}
</script><style scoped>/*样式相关代码*/.title {color: green;}
</style>

结果

如果我们想导入vue,js,css等文件时,省略后面的后缀,需要在webpack.config.js的resolve中配置:

resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']
}

完整:

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']}
}

这是我们导入Cpn.vue文件时:

import Cpn from ‘./Cpn’

6.10、认识plugin

1、添加版权的插件

BannerPlugin:Banner横幅的意思

BannerPlugin是webpack自带的插件,我们要用的时候直接导入webpack就可以了

在webpack.config.js中导入

const webpack = require('webpack')

配置:

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')
const webpack = require('webpack')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']},// 配置插件plugins: [new webpack.BannerPlugin('最终版权归msj所有')]
}

打包:npm run build

bundle.js中多了一行:

2、打包html的plugin

需要使用HtmlWebpackPlugin

安装HtmlWebpackPlugin

cnpm install html-webpack-plugin --sava-dev
// 如果出错
cnpm install html-webpack-plugin@3.2.0 --save-dev

配置:

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')
const webpack = require('webpack')
// 导入htmlwebpackPlugin
const htmlWebpackPlugin = require('html-webpack-plugin')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']},// 配置插件plugins: [new webpack.BannerPlugin('最终版权归msj所有'),new htmlWebpackPlugin()]
}

现在在dist目录下它会帮我们生成一个html文件,

这个文件会自动帮我们引入bundle.js,但现在index.html和bundle.js在同一个目录,所以dist/这一级目录就不需要了,可以把webpack.config.js中的publicPath这句删了。

还有一个问题:它不会把我我们vue挂载的div一起生成,所以我们要指定index.html的生成模板:

模板如下:引文编译后bundle.js会自动引入,所以模板中就不写了

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app">
</div></body>
</html>

webpack.js

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')
const webpack = require('webpack')
// 导入htmlwebpackPlugin
const htmlWebpackPlugin = require('html-webpack-plugin')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',// publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']},// 配置插件plugins: [new webpack.BannerPlugin('最终版权归msj所有'),new htmlWebpackPlugin({template: 'index.html'})]
}

成功插入:

3、js压缩的plugin

uglifyjs-webpack-plugin

uglify:丑化

该插件可以把js中的所有空格去掉,并用一些符号代替变量

安装:

cnpm install uglifyjs-webpack-plugin@1.1.1 --save-dev

配置:

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')
const webpack = require('webpack')
// 导入htmlwebpackPlugin
const htmlWebpackPlugin = require('html-webpack-plugin')
// 导入uglify插件
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',// publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']},// 配置插件plugins: [new webpack.BannerPlugin('最终版权归msj所有'),new htmlWebpackPlugin({template: 'index.html'}),// 使用压缩js插件new uglifyjsWebpackPlugin()]
}

未压缩前:

压缩后:

注意:压缩后所有的注释都会被去掉,包括作者信息,所以压缩插件和添加版权插件只能选择其中一个使用。

6.11、搭建本地服务器

现在比如我在main.js中写了一个新功能,测试时只能先使用npm run build编译打包后才能测试

搭建本地服务,可以实时测试代码

1、安装

cnpm install webpack-dev-server@2.9.3 --save-dev

2、配置

3、启动服务

配置脚本启动局部安装的webpack-dev-server,在package.json中配置如下:

"scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "webpack","dev": "webpack-dev-server"
},

启动服务

npm run dev

修改页面:main.js

// commonJS的导入
const {add, mul} = require('./js/mathUtils.js')console.log(add(10, 20));
console.log(mul(10, 20));// 依赖css文件
require('./css/normal.css')// 依赖less文件
require('./css/special.less')// 使用document给html添加文字
document.writeln('<h2>你好呀,李银河</h2>')// 使用vue进行开发
import Vue from 'vue'
// import App from './vue/app'
import App from './vue/App.vue'const app = new Vue({el: '#app',// 使用vue组件template: '<App></App>',// 注册组件components: {App}
})document.writeln('<button>按钮</button>')

不用再次编译也能看到效果

使用ctl+C结束

等代码编译完成后就可以使用npm run build打包发行。

启动服务后自动打开网页配置:

"scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "webpack","dev": "webpack-dev-server --open"
},

开发阶段不建议使用js压缩,只有发布时才需要,就需要把这些配置文件分离。

6.12、webpack配置文件的分离

对开发时依赖和运行时依赖配置的不同,而进行配置文件的分离

新建build目录,在该目录下新建dev.config.js,prod.config.js和base.js

base.js:存放公共配置文件

ev.config.js:存放开发时配置文件

prod.config.js:存放运行时配置文件

开发时使用的配置文件:dev.config.js+base.js

运行时使用的配置文件:prod.config.js+base.js

base.js

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')
const webpack = require('webpack')
// 导入htmlwebpackPlugin
const htmlWebpackPlugin = require('html-webpack-plugin')
// 导入uglify插件
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',// publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']},// 配置插件plugins: [new webpack.BannerPlugin('最终版权归msj所有'),new htmlWebpackPlugin({template: 'index.html'}),// 使用压缩js插件// new uglifyjsWebpackPlugin()],/*devServer: {// 需要监听的文件夹contentBase: './dist',// 是否实时监听,true为实时监听inline: true}*/
}

prod.config.js

const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')module.exports = {plugins: [new uglifyjsWebpackPlugin()]
}

dev.config.js

module.exports = {devServer: {// 需要监听的文件夹contentBase: './dist',// 是否实时监听,true为实时监听inline: true}
}

为了能让配置文件合并,在项目目录下安装:

cnpm install webpack-merge@4.1.5 --save-dev

在开发时配置中配置:

合并base.js和prod.config.js,修改prod.config.js

const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')// 导入base.js
const baseConfig = require('./base')// 把baseConfig和prod.config合并
module.exports = webpackMerge(baseConfig, {plugins: [new uglifyjsWebpackPlugin()]
})
/*
module.exports = {plugins: [new uglifyjsWebpackPlugin()]
}
*/

合并base.js和dev.config.js,修改dev.config.js

dev.config.js

const webpackMerge = require('webpack-merge')
const baseConfig = require('./base')module.exports = webpackMerge(baseConfig, {devServer: {// 需要监听的文件夹contentBase: './dist',// 是否实时监听,true为实时监听inline: true}
})/*module.exports = {devServer: {// 需要监听的文件夹contentBase: './dist',// 是否实时监听,true为实时监听inline: true}
}*/

现在可以把webpack.config.js可以删除了。

但此时运行npm run build 时报错

修改package.json,指定配置文件

{"name": "meetwebpack","version": "1.0.0","description": "","main": "webpack.config.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "webpack --config ./build/prod.config.js","dev": "webpack-dev-server --open --config ./build/dev.config.js"},
}

但这时又有一个问题,npm run build打包到了build/dist目录去了,因为我们之前配置的output路径为打包的js文件的同级目录的dist下,而现在的配置文件base.js在build下,所以就会打包到build/dist目录下

修改拼接路径:base.js

// 使用commonJS导入path依赖,如果我们导入了依赖,就需要使用npm init生成一个package.json,
// 让node来帮我们管理和安装依赖
const path = require('path')
const webpack = require('webpack')
// 导入htmlwebpackPlugin
const htmlWebpackPlugin = require('html-webpack-plugin')
// 导入uglify插件
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')module.exports = {// 入口entry: './src/main.js',output: {// 出口路径:需要写绝对路径// __dirname是node中的一个全局变量,它保存的就是当前文件的路径// resolve可以把两个字符串拼接为一个路径path: path.resolve(__dirname, '../dist'),filename: 'bundle.js',// publicPath: 'dist/'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.less$/,use: [{loader: "style-loader"  // creates style nodes from JS string}, {loader: "css-loader"  // 把css翻译为commonJS规范}, {loader: "less-loader"  // 把less编译为css}]},{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192,// 配置打包后的图片文件规范管理// [name]取到原来图片的名字// .表示name后面跟上// [hash:8] 截取8为hash值// [ext]扩展名name: 'img/[name].[hash:8].[ext]'}}]},{test: /\.js$/,// 需要排除的文件exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['es2015']}}},{test: /\.vue$/,use: ['vue-loader']}]},resolve: {// 别名alias: {// 使用了vue$时,当我们以后有关vue的操作会首先来这里找到指定的文件夹// 即当前文件夹的npm安装路径/vue/dist/vue.esm.js'vue$': 'vue/dist/vue.esm.js'},// 配置要省略的后缀extensions: ['.js', '.vue', '.ccc']},// 配置插件plugins: [new webpack.BannerPlugin('最终版权归msj所有'),new htmlWebpackPlugin({template: 'index.html'}),// 使用压缩js插件// new uglifyjsWebpackPlugin()],/*devServer: {// 需要监听的文件夹contentBase: './dist',// 是否实时监听,true为实时监听inline: true}*/
}

npm run build,大功告成

测试npm run dev

七、vue CLI

vue CLI使用前提:Node

7.1、安装vue脚手架

node配置

首先卸载vue-cli 2.x

npm uninstall -g vue-cli

如果卸载不成功,直接删除vue-cli文件夹

配置node:

新的nodejs安装完之后,建议在nodejs的安装目录下,新建两个文件夹:

用来存放,npm全局安装的包,便于管理,不然默认会被安装到这个目录。

C:\Users\用户名\AppData\Roaming\npm

然后在命令行执行:

npm config set prefix D:\Software\devolop\NodeJs\node_global
npm config set cache D:\Software\devolop\NodeJs\node_cache

这样,npm全局安装的包就会到这个目录下,而不是默认的C:\Users\用户名\AppData\Roaming\npm这个目录。

此时,你去C:\Users\用户名 这个目录下,也能看到.npmrc这个文件,打开它,你可以看到你刚刚配置的一些东西。

prefix=D:\Software\devolop\NodeJs\node_global
cache=D:\Software\devolop\NodeJs\node_cache
registry=http://registry.npm.taobao.org

到此,npm和nodejs配置好了, 你可以执行npm -v 查到npm的版本,确定它是否安装成功。

这个时候你再去全局安装@vue/cli,

npm install -g @vue/cli
# 如果安装失败:
npm install -g @vue/cli --force
# 如果出现rename错误,再去删除刚才那个文件夹: @vue,然后再执行
npm install -g @vue/cli --force
# 注意:node依赖python2环境,如果你电脑没有的需要安装python2环境,如果安了多套(python2和python3),暂时把python3的环境变量从用户变量删除,安装成功后再配置。

安装完成后注意配置node的环境变量r

脚手架直接使用全局安装即

npm install -g @vue/cli
// 查看vue版本:需要3.x(脚手架3)
vue --version
# 脚手架3初始化项目
vue create my-project

在脚手架3的基础上使用脚手架2,需要拉取脚手架2的模板

官网:https://cli.vue.org

拉取脚手架2的模板:

npm install -g @vue/cli-init
# vue init的运行效果将会跟 vue-cli@2.x相同
# 脚手架2初始化项目
vue init webpack my-project

使用脚手架2初始化项目:

进入要创建项目的文件夹,在终端输入:

vue init webpack vuecli2test
# 回车后就会下载模板
project name: 给项目起一个名字(之后会在package.json中看到,一般跟文件名一样,即vuecli2test,如果一样的话,直接回车即可(也可以不一样))。
project decription: 项目描述(随便写,如test vue cli2)
Author:作者
Runtime+Compiler和Runtime-only:通过方向键选择,现在暂时选择Runtime+Compiler
是否安装vue-router? 因为现在没学,暂时选择n
Use ESlint to lint your code:是否对es代码做限制(es即js代码,如果做限制的话,以后代码不规范时,在编译期间就报错了),暂时选n
Pick an ESlint to lint your code?选择一个es代码规范模式(可以有自己的限制模式,主要有三种模式:Standard,Airbnb,none(没有))
Set up unit test? 是否有单元测试(现在用的比较少,选择n)
Setup e2e tests with Nighwatch?  e2e: end to end端到端的测试,即是否选择Nighwatch端到端测试。一般选择n
Should we run 'npm install' for your after the project has been created?(recommended),选择npm,yarn还是其他来管理我们的项目,一般选择NPM

vue init webpack vuecli2test

作者:有默认的作者,这是git中配置的(即.gitconfig中配置的)

生成后的项目结构:

build和config目录都是配置文件

package.json

{"name": "vuecli2test","version": "1.0.0","description": "test vue cli2","author": "msj <2669159659@qq.com>","private": true,"scripts": {"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js","start": "npm run dev","build": "node build/build.js"},"dependencies": {"vue": "^2.5.2"},"devDependencies": {"autoprefixer": "^7.1.2","babel-core": "^6.22.1","babel-helper-vue-jsx-merge-props": "^2.0.3","babel-loader": "^7.1.1","babel-plugin-syntax-jsx": "^6.18.0","babel-plugin-transform-runtime": "^6.22.0","babel-plugin-transform-vue-jsx": "^3.5.0","babel-preset-env": "^1.3.2","babel-preset-stage-2": "^6.22.0","chalk": "^2.0.1","copy-webpack-plugin": "^4.0.1","css-loader": "^0.28.0","extract-text-webpack-plugin": "^3.0.0","file-loader": "^1.1.4","friendly-errors-webpack-plugin": "^1.6.1","html-webpack-plugin": "^2.30.1","node-notifier": "^5.1.2","optimize-css-assets-webpack-plugin": "^3.2.0","ora": "^1.2.0","portfinder": "^1.0.13","postcss-import": "^11.0.0","postcss-loader": "^2.0.8","postcss-url": "^7.2.1","rimraf": "^2.6.0","semver": "^5.3.0","shelljs": "^0.7.6","uglifyjs-webpack-plugin": "^1.1.1","url-loader": "^0.5.8","vue-loader": "^13.3.0","vue-style-loader": "^3.0.1","vue-template-compiler": "^2.5.2","webpack": "^3.6.0","webpack-bundle-analyzer": "^2.9.0","webpack-dev-server": "^2.9.1","webpack-merge": "^4.1.0"},"engines": {"node": ">= 6.0.0","npm": ">= 3.0.0"},"browserslist": ["> 1%","last 2 versions","not ie <= 8"]
}

注意:node是可以直接执行js文件的

以前的js需要在html中引入,然后在浏览器上面查看效果。

node 执行js

由上面的package.json可知,npm run build时,执行了build/build.js

build.js

'use strict'
require('./check-versions')()process.env.NODE_ENV = 'production'const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')const spinner = ora('building for production...')
spinner.start()
// rm:remove即第二次执行npm run build的时候,它会把之前的一些文件删除
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {if (err) throw errwebpack(webpackConfig, (err, stats) => {spinner.stop()// 如果由异常,抛出异常if (err) throw errprocess.stdout.write(stats.toString({colors: true,modules: false,children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.chunks: false,chunkModules: false}) + '\n\n')if (stats.hasErrors()) {console.log(chalk.red('  Build failed with errors.\n'))process.exit(1)}console.log(chalk.cyan('  Build complete.\n'))console.log(chalk.yellow('  Tip: built files are meant to be served over an HTTP server.\n' +'  Opening index.html over file:// won\'t work.\n'))})
})

const webpackConfig = require(’./webpack.prod.conf’)

webpack.prod.conf

'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')const env = require('../config/prod.env')const webpackConfig = merge(baseWebpackConfig, {module: {rules: utils.styleLoaders({sourceMap: config.build.productionSourceMap,extract: true,usePostCSS: true})},devtool: config.build.productionSourceMap ? config.build.devtool : false,output: {path: config.build.assetsRoot,filename: utils.assetsPath('js/[name].[chunkhash].js'),chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')},plugins: [// http://vuejs.github.io/vue-loader/en/workflow/production.htmlnew webpack.DefinePlugin({'process.env': env}),new UglifyJsPlugin({uglifyOptions: {compress: {warnings: false}},sourceMap: config.build.productionSourceMap,parallel: true}),// extract css into its own filenew ExtractTextPlugin({filename: utils.assetsPath('css/[name].[contenthash].css'),// Setting the following option to `false` will not extract CSS from codesplit chunks.// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110allChunks: true,}),// Compress extracted CSS. We are using this plugin so that possible// duplicated CSS from different components can be deduped.new OptimizeCSSPlugin({cssProcessorOptions: config.build.productionSourceMap? { safe: true, map: { inline: false } }: { safe: true }}),// generate dist index.html with correct asset hash for caching.// you can customize output by editing /index.html// see https://github.com/ampedandwired/html-webpack-pluginnew HtmlWebpackPlugin({filename: config.build.index,template: 'index.html',inject: true,minify: {removeComments: true,collapseWhitespace: true,removeAttributeQuotes: true// more options:// https://github.com/kangax/html-minifier#options-quick-reference},// necessary to consistently work with multiple chunks via CommonsChunkPluginchunksSortMode: 'dependency'}),// keep module.id stable when vendor modules does not changenew webpack.HashedModuleIdsPlugin(),// enable scope hoistingnew webpack.optimize.ModuleConcatenationPlugin(),// split vendor js into its own filenew webpack.optimize.CommonsChunkPlugin({name: 'vendor',minChunks (module) {// any required modules inside node_modules are extracted to vendorreturn (module.resource &&/\.js$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0)}}),// extract webpack runtime and module manifest to its own file in order to// prevent vendor hash from being updated whenever app bundle is updatednew webpack.optimize.CommonsChunkPlugin({name: 'manifest',minChunks: Infinity}),// This instance extracts shared chunks from code splitted chunks and bundles them// in a separate chunk, similar to the vendor chunk// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunknew webpack.optimize.CommonsChunkPlugin({name: 'app',async: 'vendor-async',children: true,minChunks: 3}),// copy custom static assetsnew CopyWebpackPlugin([{from: path.resolve(__dirname, '../static'),to: config.build.assetsSubDirectory,ignore: ['.*']}])]
})if (config.build.productionGzip) {const CompressionWebpackPlugin = require('compression-webpack-plugin')webpackConfig.plugins.push(new CompressionWebpackPlugin({asset: '[path].gz[query]',algorithm: 'gzip',test: new RegExp('\\.(' +config.build.productionGzipExtensions.join('|') +')$'),threshold: 10240,minRatio: 0.8}))
}if (config.build.bundleAnalyzerReport) {const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginwebpackConfig.plugins.push(new BundleAnalyzerPlugin())
}module.exports = webpackConfig

只是回顾:

组件化开发

  • children/refs
  • parent/root

slot的基本使用

  • 基本使用
  • 具名插槽
  • 编译的作用域
  • 作用域插槽

前端模块化

1、为什么要使用模块化

  • 简单写js带来的问题
  • 闭包引起代码不可复用
  • 自己实现了简单的模块化
  • AMD/CMD/CommonJS

2、ES6中模块化的使用

  • export
  • import

webpack

1、什么是webpack

  • webpack和gulp对比
  • webpack依赖环境
  • 安装webpack

2、webpack的起步

  • webpack命令
  • webpack配置:webpack.config.js/package.json(scripts)

3、webpack和loader

  • css-loader/style-loader
  • less-loader
  • url-loader/file-loader
  • babel-loader
  • vue-loader

4、webpack中使用plugin

5、搭建本地服务器

7、配置文件分离

vue CLI

1、什么是CLI

  • 脚手架是什么
  • CLI依赖webpack,node,npm
  • 安装cli3->拉取cl2模板

2、cl2初始化项目的过程

cli2产生目录结构的解析

runtime compiler和runtem only的区别:

使用cli2创建项目:

项目1:使用runtime + compiler

不使用路由,ESlint选择标准,后面除了管理工具选择NPM为其他都选择no

项目2:选择runtimeonly

修改名字:

使用了eslint后,js最后已经话也不能加逗号,也会报错,任何错误或代码不规范都会报错

关闭esLint

runrtime-compiler和runtime-only的区别

它们的区别仅仅在main.js

相当于:

即runtime compiler是先注册组件再使用

runtime-compiler

template->ast->render->virtual dom -> 真实dom(UI)

runtime-only:

render->virtual dom -> UI

结论:

runtime-only:

  • 性能更高
  • 代码更少

因此我们以后开发项目的时候多选择runtime-only

在runtime-compily中也可以使用render函数

数组中:放的是标签的内容

还可以一下面的方式写:

createElement的高级用法:

可以传入组件:

定义组件

传入组件对象:

也可以直接放入.vue组件

其中h代表createElement函数

我们导入的.vue文件已经不包含template模板了,已经转换为render函数了:

那么.vue文件中的template是由谁处理的?

是由vue-template-complier处理的。

7.2、render函数的使用

使用脚手架3创建项目

vue create 项目名Please pick preset:请选择配置(default(默认), Manually select features(手动选择))
我们选择手动选择
选择我们要的配置,点击空格即可选中
PWA(Progressive Web App),先进web App要把配置文件放在哪里?
我们选择把配置文件放在一个独立的文件夹中(in dedicated config files)是否保存我们刚才选的这些配置为项目特征?
选择yes的话:我们也暂时选择y
保存配置的名字:msj
然后直接敲回车现在再使用vue create testcli3,有一个msj的配置

选择配置

这里我们之选中Babel和Vuex

如果是vue4.x的话我们还要选中Choose Vue version,然后 选择3.x

删除自己保存的配置

可以删了自己想上传的东西

以rc命令的原因:来自Linux命名规范,以中端保存的东西都会加一个rc(run command)

创建成功

vsc: version system control,版本控制问题

跑脚手架3:

npm run serve

源代码放置位置:src

.$mout('#app')与el:'#app'效果一样,
使用el挂载的使用最终执行的也是$mouut()

图形化界面管理vue项目:

vue ui

启动vue ui管理

可以导入我们创建的项目:

可以安装依赖

具体配置

启动任务:

cli3找到配置文件:

如果要修改配置的话:可以再项目路径下新建一个vue.config.js(这个文件名字不能随概念改)

7.3、箭头函数

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><script>// 箭头函数也是一种定义函数的方式// 定义函数的方式1const a = function(){}// 方式2:对象字面量中定义函数const obj = {b: function(){},// 增强写法bb(){}}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><script>// 箭头函数也是一种定义函数的方式// 定义函数的方式1const a = function(){}// 方式2:对象字面量中定义函数const obj = {b: function(){},// 增强写法bb(){}}// 方式3:ES6中的箭头函数const c = (参数列表) => {}// 无参数定义方式const cc = () => {}
</script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<script>// 参数问题:两个参数const sum =  (num1, num2) => {return num1 + num2}// 只放入一个参数的时候,小括号可以省略const power = num => {return num * num}// 函数代码块中有多行代码时const test = () => {// 1.打印hello worldconsole.log('hello world');// 2.打印hello vueconsole.log('hello vue');}// 函数代码块中只有一行代码,可以简写为:即去点花括号,并且return可以省略(如果结果可以返回的话)const mul = (num1, num2) => num1 * num2// 结果不是一个返回值,代码也正确,并且它不会返回结果,这是返回一个undefinedconst demo = () => console.log('hello demo')console.log(demo());// cli3中的代码解释reder: h => return h(App)// 这里h是参数,只有一个参数,省略圆括号,h是一个函数,最终返回h(App)的结果</script>
</body>
</html>

箭头函数中this的使用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<script>// 什么时候使用箭头// 当我们准备把函数当作参数传到另一个函数时// 如:setTimeout(function(){console.log(this) // 打印的window对象 }, 1000)// 使用箭头函数setTimeout(() => {// 向外找一层,即script下console.log(this) // 打印的也是window对象}, 1000)// 在script下,this = window
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<script>// 什么时候使用箭头// 当我们准备把函数当作参数传到另一个函数时// 如:/*setTimeout(function(){console.log(this) // 打印的window对象}, 1000)// 使用箭头函数setTimeout(() => {console.log(this) // 打印的也是window对象}, 1000)*/const obj = {aa() {setTimeout(function() {// setTimeout是调用call,而call是把window作为对象传进去的console.log(this) // 打印的还是window对象}, 1000)setTimeout(() => {console.log(this)  // 打印的obj对象,会向外找一层,外一层是obj对象,而对象中必然有一个指向它的this对象})}}// 结论:箭头函数中的this引用的是最近作用域的对象// 即箭头函数是由内向外层一层层查找this,知道查到this的定义</script>
</body>
</html>

八、路由

使用脚手架2创建项目

vue init webpack learnvuerouter

路由选择y,选择runtime-only

路由就是一种映射关系

后端路由阶段:

什么是前端渲染,什么是后端渲染?

阶段一:网页的渲染都是后端渲染:jsp/php

阶段二:后端路由阶段

阶段三:前后端分离阶段

后端只负责数据,不负责任何阶段的内容

前后端分离阶段就是前端渲染阶段

现在网站已经发展到第四阶段了:单页面富应用(SPA页面 )

整个网页只有一个html页面

这时就需要一个技术来支撑:即前端路由

8.1、url的hash和Html5的和history

如何改变页面的url,并且让页面不发生改变

# 进入刚才创建的页面
cd learnvuerouter
# 启动项目
npm run dev

打开浏览器控制台:修改hash值

location.hash = 'aaa'

浏览器地址改了,但服务器并没有请求任何东西

Html5的修改方式:history

history.pushState(对象参数,title参数,url)
history.pushState({},'','home')
pushState是一个栈结构
连续压入:
history.pushState({},'','about')
history.pushState({},'','me')
浏览器地址栏显示的永远是最后一次压入栈的(后进先出)出栈:
history.back() 浏览器地址栏显示的倒数第二次入栈的
也可以直接点击浏览的返回按钮,也是出栈history.go(-1)等价于history.back()
history.go(-1) 弹出me,此时栈顶是about,所以浏览器地址栏显示的about
history.go(-2) 弹出两个栈顶元素,此时栈顶是home
history.go(2), 把最后弹出的两个url入栈,地址栏显示最后一次入栈的元素

页面也改变了

出栈:

history.replaceState({},'','home')
直接替换,不能返回(不是栈结构)

8.2、认识vue-router

步骤一:安装路由

步骤二:

光彩我们创建项目的时候已经选择了路由:可以直接使用

router目录:存放路由相关的信息

router/index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
export default new Router({routes: [{path: '/',name: 'HelloWorld',component: HelloWorld}]
})

也可以简写为:

import Router from 'vue-router'
import Vue from 'vue'
Vue.use(Roueter)const routes = []const router = new Router({// 配置路由和组件之间的映射关系:增强写法routes
})// 导出路由
export default router

使用router:main.js

import Vue from 'vue'
import App from './App'
// 会默认从router文件夹中找index.js,所以index.js可以写,也可以不写
import router from './router'Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',router,render: h => h(App)
})

配置路由的映射关系:

首先删除Hello.vue,在components目录下创建一个vue组件

Home.vue

<template><div><h4>我是Home</h4><p>我是内容,哈哈哈</p></div>
</template><script>
export default {name: "Home"
}
</script><style scoped></style>

About.vue

<template><div><h4>我是About</h4><p>我是关于的内容,呵呵</p></div>
</template><script>
export default {name: "About"
}
</script><style scoped></style>

index.js(配置路由)

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'import Home from '../components/Home'
import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
const routes = [{path: '/home',component: Home},{path: '/about',component: About}
]const router = new Router({routes
})export default router

App.vue

<template><div id="app"><router-link to="/home">首页</router-link><router-link to="/about">关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><router-view></router-view></div>
</template><script>
export default {name: 'App'
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

使用:main.js

import Vue from 'vue'
import App from './App'
// 会默认从router文件夹中找index.js,所以index.js可以写,也可以不写
import router from './router'Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',router,render: h => h(App)
})

效果

默认路由的配置:

方式一:path缺省的时候默认显示

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'import Home from '../components/Home'
import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
const routes = [{path: '',component: Home},{path: '/about',component: About}
]const router = new Router({routes
})export default router

但是路径也为空

方式二:重定向

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'import Home from '../components/Home'
import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home},{path: '/about',component: About}
]const router = new Router({routes
})export default router

去掉url中的hash中#号

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'import Home from '../components/Home'
import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home},{path: '/about',component: About}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history'
})export default router

8.3、router-link补充

router-link默认的显示样式是一个a标签

router-link显示样式为button

App.vue

<template><div id="app"><router-link to="/home" tag="button">首页</router-link><router-link to="/about" tag="button">关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><router-view></router-view></div>
</template><script>
export default {name: 'App'
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

vue组件中默认使用的pushState,即在浏览器它可以点击返回,如果我们要禁用返回,可以使用replaceState模式:

<div id="app"><router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about" tag="button" replace>关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><router-view></router-view>
</div>

刚才我们使用tag=button模式,vue不仅仅给我们添加了button标签,而且当我们点击某个按钮的时候,会给我们添加xxx-active(激活的class)

我们可以通过这个属性修改点击时按钮字体的颜色

<template><div id="app"><router-link to="/home" tag="button">首页</router-link><router-link to="/about" tag="button">关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><router-view></router-view></div>
</template><script>
export default {name: 'App'
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
/*改变颜色*/
.router-link-active {color: red;
}
</style>

当我们觉得router-link-active名字过长不好记时,可以使用active-class属性修改

<template><div id="app"><router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><router-view></router-view></div>
</template><script>
export default {name: 'App'
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
.router-link-active {color: red;
}.active {color: #f00;
}
</style>

也可以在router下的index.js中使用linkActiveCalss进行修改

index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'import Home from '../components/Home'
import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home},{path: '/about',component: About}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})export default router

App.vue

<template><div id="app">
<!--    <router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link>--><router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about" tag="button" replace>关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><router-view></router-view></div>
</template><script>
export default {name: 'App'
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}.active {color: #f00;
}
</style>

通过代码跳转路由:

<template><div id="app">
<!--    <router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link>-->
<!--    <router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about" tag="button" replace>关于</router-link>--><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里--><button @click="homeClick">首页</button><button @click="aboutClick">关于</button><router-view></router-view></div>
</template><script>
export default {name: 'App',methods: {homeClick(){// 使用history.pushState的方式修改路由,相当于跳过了vue-router去修改路由,不建议使用,使用下面的方式(router)比较好// router来自于vue-router(它朝所有的vue中都添加了router属性this.$router.push('/home')// 也可以使用this.$router.replace()console.log('homeClick');},aboutClick() {this.$router.push('/about')console.log('aboutClick');}}
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}.active {color: #f00;
}
</style>

使用代码跳转的时候,active属性并不会自动添加

8.4、动态路由的使用

拼接userId

App.vue

<template><div id="app">
<!--    <router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link>--><router-link to="/home" tag="button" replace>首页</router-link><router-link :to="'/about/' + userId" tag="button" replace>关于</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里-->
<!--    <button @click="homeClick">首页</button><button @click="aboutClick">关于</button>--><router-view></router-view></div>
</template><script>
export default {name: 'App',data(){return {userId: 'lisi'}},methods: {homeClick(){// 使用history.pushState的方式修改路由,相当于跳过了vue-router去修改路由,不建议使用,使用下面的方式(router)比较好// router来自于vue-router(它朝所有的vue中都添加了router属性this.$router.push('/home')// 也可以使用this.$router.replace()console.log('homeClick');},aboutClick() {this.$router.push('/about')console.log('aboutClick');}}
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}.active {color: #f00;
}
</style>

index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'import Home from '../components/Home'
import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home},{// 拼接userIdpath: '/about/:userId',component: About}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})export default router

拿到userId

About.vue

<template><div><h4>我是About</h4><p>我是关于的内容,呵呵</p><h4>{{userId}}</h4></div>
</template><script>
export default {name: "About",// 使用计算属性取到computed: {userId(){// $route可以拿到index中定义的路由,params是拿到里面的参数,userId也是我们index.js中路径后定名的变量名// $router拿到的是index.js中new 的Router()对象return this.$route.params.userId}}
}
</script><style scoped></style>

也可以直接拿:

<template><div><h4>我是About</h4><p>我是关于的内容,呵呵</p><h4>{{$route.params.userId}}</h4></div>
</template><script>
export default {name: "About",// 使用计算属性取到computed: {userId(){// $route可以拿到index中定义的路由,params是拿到里面的参数,userId也是我们index.js中路径后定名的变量名// $router拿到的是index.js中new 的Router()对象return this.$route.params.userId}}
}
</script><style scoped></style>

8.5、vue-router打包文件的解析

文件打包:分层打包

8.6、路由的懒加载

路由懒加载写法:

index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home},{// 拼接userIdpath: '/about/:userId',component: About}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})export default router

8.7、路由的嵌套

创建子组件:

HomeNews.vue

<template><div><ul><li>新闻1</li><li>新闻2</li><li>新闻3</li><li>新闻4</li><li>新闻5</li></ul></div>
</template><script>
export default {name: "HomeNews"
}
</script><style scoped></style>

HomeMessage.vue

<template><div><ul><li>消息1</li><li>消息2</li><li>消息3</li><li>消息4</li></ul></div>
</template><script>
export default {name: "HomeMessage"
}
</script><style scoped></style>

在Home.vue中告诉组件的显示位置

Home.vue

<template><div><h4>我是Home</h4><p>我是内容,哈哈哈</p><router-link to="/home/news">新闻</router-link><router-link to="/home/message">消息</router-link><router-view></router-view></div>
</template><script>
export default {name: "Home"
}
</script><style scoped></style>

配置路由:

index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home,// 嵌套路由children: [{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{// 拼接userIdpath: '/about/:userId',component: About}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})export default router

效果:

项目结构:

配置默认进入首页后可以看到新闻:

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home,// 嵌套路由children: [{path: '',redirect: 'news'},{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{// 拼接userIdpath: '/about/:userId',component: About}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})export default router

8.8、路由传递消息

参数的传递方式:

新建:

User.vue

<template><div><h4>我是用户界面</h4><p>我是用户的相关信息</p><h4>{{userId}}</h4></div>
</template><script>
export default {name: "User",computed: {userId() {return this.$route.params.userId}}
}
</script><style scoped></style>

Profile.vue

<template><h4>我是Profile</h4>
</template><script>
export default {name: "Profile"
}
</script><style scoped></style>

配置路由:index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{path: '/home',component: Home,// 嵌套路由children: [{path: '',redirect: 'news'},{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{// 拼接userIdpath: '/about',component: About},{path: '/user/:userId',component: User},{path: '/profile',component: Profile}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})export default router

App.vue

<template><div id="app">
<!--    <router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link>--><router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about" tag="button">关于</router-link><!-- params方式传递--><router-link :to="'/user/' + userId" tag="button" replace>用户</router-link><!-- 普通方式,不能传对象:-->
<!--    <router-link to="/profile" tag="button">档案</router-link>--><!-- 传对象模式,需要使用v-bin绑定,否则{}会被认为是一个字符串:query方式传递--><router-link :to="{path: '/profile', query: {name: 'why', age: 18}}" tag="button">档案</router-link><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里-->
<!--    <button @click="homeClick">首页</button><button @click="aboutClick">关于</button>--><router-view></router-view></div>
</template><script>
export default {name: 'App',data(){return {userId: 'lisi'}},methods: {homeClick(){// 使用history.pushState的方式修改路由,相当于跳过了vue-router去修改路由,不建议使用,使用下面的方式(router)比较好// router来自于vue-router(它朝所有的vue中都添加了router属性this.$router.push('/home')// 也可以使用this.$router.replace()console.log('homeClick');},aboutClick() {this.$router.push('/about')console.log('aboutClick');}}
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}.active {color: #f00;
}
</style>

效果:参数已经拼接上了

取到query中的信息

Profile.vue

<template><div><h4>我是Profile</h4><p>{{$route.query}}</p><!--取到名字和age--><p>{{$route.query.name}}</p><p>{{$route.query.age}}</p></div>
</template><script>
export default {name: "Profile"
}
</script><style scoped></style>

通过按钮跳转时:

App.vue

<template><div id="app">
<!--    <router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link>--><router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about" tag="button">关于</router-link><!-- params方式传递-->
<!--    <router-link :to="'/user/' + userId" tag="button" replace>用户</router-link>--><!-- 普通方式,不能传对象:-->
<!--    <router-link to="/profile" tag="button">档案</router-link>--><!-- 传对象模式,需要使用v-bin绑定,否则{}会被认为是一个字符串:query方式传递-->
<!--    <router-link :to="{path: '/profile', query: {name: 'why'}}" tag="button">档案</router-link>--><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里-->
<!--    <button @click="homeClick">首页</button><button @click="aboutClick">关于</button>--><button @click="userClick">用户</button><button @click="proClick">档案</button><router-view></router-view></div>
</template><script>
export default {name: 'App',data(){return {userId: 'lisi'}},methods: {homeClick(){// 使用history.pushState的方式修改路由,相当于跳过了vue-router去修改路由,不建议使用,使用下面的方式(router)比较好// router来自于vue-router(它朝所有的vue中都添加了router属性this.$router.push('/home')// 也可以使用this.$router.replace()console.log('homeClick');},aboutClick() {this.$router.push('/about')console.log('aboutClick');},userClick() {this.$router.push('/user/' + this.userId)},proClick() {this.$router.push({path: '/profile',query: {name: 'kobe',age: 30,height: 188}})}}
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}.active {color: #f00;
}
</style>

8.9、route和route和route和router的区别

在任何组件中都可以拿到打印router对象

User.vue

<template><div><h4>我是用户界面</h4><p>我是用户的相关信息</p><h4>{{userId}}</h4><button @click="btnClick">按钮</button></div>
</template><script>
export default {name: "User",computed: {userId() {return this.$route.params.userId}},methods: {btnClick() {// 拿到router对象并打印console.log(this.$router);}}
}
</script><style scoped></style>

在main.js中直接打印router

import Vue from 'vue'
import App from './App'
// 会默认从router文件夹中找index.js,所以index.js可以写,也可以不写
import router from './router'Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',router,render: h => h(App)
})// 直接打印导入的router:这里打印的就是index.js中new出来的Router
console.log(router);

可以看到main.js中router和组件中的router是同一个

查看$route

<template><div><h4>我是用户界面</h4><p>我是用户的相关信息</p><h4>{{userId}}</h4><button @click="btnClick">按钮</button></div>
</template><script>
export default {name: "User",computed: {userId() {return this.$route.params.userId}},methods: {btnClick() {// 拿到router对象并打印console.log(this.$router);console.log(this.$route);}}
}
</script><style scoped></style>

他拿到的是当前活跃的router(index.js中配置的路由)

要看为什么可以拿到router和toute就需要看源码,去github现在自己使用vue-router版本,

Vue.use(插件),内部是执行了插件的install方法

源码中注册的全局组件:

注册组件的时候是大写的,但我们使用的时候约定俗成的写为了小写字母加短线连接的方式

vue组件:所有的组件都继承自Vue类的原型,意味着:比如在main.js中

import Vue from 'vue'
import App from './App'
// 会默认从router文件夹中找index.js,所以index.js可以写,也可以不写
import router from './router'Vue.config.productionTip = false// 在Vue中添加test方法,意味着所有的vue组件中都会有test方法
Vue.prototype.test = function(){}/* eslint-disable no-new */
new Vue({el: '#app',router,render: h => h(App)
})// 直接打印导入的router:这里打印的就是index.js中new出来的Router
console.log(router);

Vue.prototype.test = function(){},给Vue中添加方法,则在任何组件中都可使用该方法

在任意的vue组件的scritp代码中都可调用上面的方法

this.test()

router和router和router和route就是在Vue中天加了router和router和router和route属性

vue源码中给对象添加属性的一种方法:

// 原来
const obj = {name: 'why'
}// 使用Object.definProperty(对象名,'属性名', '属性值')
Object.defineProperty(obj, 'age', 18)  // 给obj添加age属性

8.10、导航守卫

导航守卫:就是监听路由的跳转

小案例:当我点击关于的时候,就把title该为关于,点击用户的时候就把title改为用户

使用钩子函数:created():当组件被创建的时候毁掉的函数

Home.js

<template><div><h4>我是Home</h4><p>我是内容,哈哈哈</p><router-link to="/home/news">新闻</router-link><router-link to="/home/message">消息</router-link><router-view></router-view></div>
</template><script>
export default {name: "Home",created() {document.title = '首页'console.log('home组件创建了')}
}
</script><style scoped></style>

User.vue

<template><div><h4>我是用户界面</h4><p>我是用户的相关信息</p><h4>{{userId}}</h4><button @click="btnClick">按钮</button></div>
</template><script>
export default {name: "User",computed: {userId() {return this.$route.params.userId}},methods: {btnClick() {// 拿到router对象并打印console.log(this.$router);console.log(this.$route);}},created() {// 修改title属性document.title = '用户'console.log('组件被创建了')}
}
</script><style scoped></style>

其他类似

每次都有一个组件被创建了,并且标题也修改了

这样的话:每个组件都要修改,工作量大

使用路由监听

NavigatingGuard是一个函数:

index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{// 添加数据meta: {title: '首页'},path: '/home',component: Home,// 嵌套路由children: [{path: '',redirect: 'news'},{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{meta: {title: '关于'},// 拼接userIdpath: '/about',component: About},{meta: {title: '用户'},path: '/user/:userId',component: User},{path: '/profile',component: Profile}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})// 导航守卫router.beforeEach():需要传入一个NavigatingGuard
router.beforeEach((to, from, next) => {// 从from到to,to指的就是当前的routerdocument.title = to.meta.title// 继续放行:next(),必须写,否则就会阻塞next()
})export default router

如果没有meta和meta中没有title:就会返回undefined

有时候点击首页会有undefined,这是因为首页是一个嵌套路由,即嵌套了news和message,这时候可以使用下面的方式:每次取到第0个meta:即不取嵌套中的meta

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{// 添加数据meta: {title: '首页'},path: '/home',component: Home,// 嵌套路由children: [{path: '',redirect: 'news'},{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{meta: {title: '关于'},// 拼接userIdpath: '/about',component: About},{meta: {title: '用户'},path: '/user/:userId',component: User},{path: '/profile',component: Profile}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})// 导航守卫router.beforeEach():需要传入一个NavigatingGuard
router.beforeEach((to, from, next) => {// 从from到to,to指的就是当前的routerdocument.title = to.matched[0].meta.title// 继续放行:next(),必须写,否则就会阻塞next()
})export default router

导航守卫的补充:

前置钩子(前置回调):钩子(hook),回调

后置钩子:后置回调

前置守卫:gaurd(守卫)

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')// 创建路由对象:Router对象 const router = new Router()
const routes = [{// '/'中的斜杠加不加都可以path: '/',redirect: '/home',},{// 添加数据meta: {title: '首页'},path: '/home',component: Home,// 嵌套路由children: [{path: '',redirect: 'news'},{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{meta: {title: '关于'},// 拼接userIdpath: '/about',component: About},{meta: {title: '用户'},path: '/user/:userId',component: User},{path: '/profile',component: Profile}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})// 导航守卫router.beforeEach():需要传入一个NavigatingGuard
router.beforeEach((to, from, next) => {// 从from到to,to指的就是当前的routerdocument.title = to.matched[0].meta.title// 继续放行:next(),必须写,否则就会阻塞next()
})// 后置钩子
router.afterEach((to, from) => {console.log('后置钩子')
})export default router

以上的前置和后置守卫都是全局守卫,对于一些独享守卫和组件内守卫需要去官方网站学习。

8.11、keep-alive

组件的创建与销毁在每次离开和再次回到该组件时都会被调用,如果不希望这两个函数被频繁的创建与调用,可以使用keep-alive,即把router-view放在keep-alive中,以下是App.vue
<template><div id="app">
<!--    <router-link to="/home" tag="button" active-class="active">首页</router-link><router-link to="/about" tag="button" active-class="active">关于</router-link>--><router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about" tag="button">关于</router-link><!-- params方式传递-->
<!--    <router-link :to="'/user/' + userId" tag="button" replace>用户</router-link>--><!-- 普通方式,不能传对象:-->
<!--    <router-link to="/profile" tag="button">档案</router-link>--><!-- 传对象模式,需要使用v-bin绑定,否则{}会被认为是一个字符串:query方式传递-->
<!--    <router-link :to="{path: '/profile', query: {name: 'why'}}" tag="button">档案</router-link>--><!-- router-view用于占位,告诉浏览器vue组件应该显示在哪里-->
<!--    <button @click="homeClick">首页</button><button @click="aboutClick">关于</button>--><button @click="userClick">用户</button><button @click="proClick">档案</button><keep-alive><router-view></router-view></keep-alive></div>
</template><script>
export default {name: 'App',data(){return {userId: 'lisi'}},methods: {homeClick(){// 使用history.pushState的方式修改路由,相当于跳过了vue-router去修改路由,不建议使用,使用下面的方式(router)比较好// router来自于vue-router(它朝所有的vue中都添加了router属性this.$router.push('/home')// 也可以使用this.$router.replace()console.log('homeClick');},aboutClick() {this.$router.push('/about')console.log('aboutClick');},userClick() {this.$router.push('/user/' + this.userId)},proClick() {this.$router.push({path: '/profile',query: {name: 'kobe',age: 30,height: 188}})}}
}
</script><style>
#app {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}.active {color: #f00;
}
</style>

Home.vue

<template><div><h4>我是Home</h4><p>我是内容,哈哈哈</p><router-link to="/home/news">新闻</router-link><router-link to="/home/message">消息</router-link><router-view></router-view></div>
</template><script>
export default {name: "Home",data() {return {message: '你好呀',path: '/home/news'}},// 组件的创建与销毁在每次离开和再次回到该组件时都会被调用,如果不希望这两个函数被频繁的创建与调用,可以使用keep-alivecreated() {document.title = '首页'this.$router.push(this.path)console.log('home组件创建了')},// 组件销毁时回调的函数destroyed() {console.log('home组件被销毁');}
}
</script><style scoped></style>

创建于销毁钩子函数并不会被调用

发现还是调用了,是因为index.js中使用了缺省值,每次回来的时候是到了缺省值的/home/news中的news组件,不是来到home组件

我们的目的:当我们在home中的news或是message组件时,跳转到别的组件,然后再跳转回来, 我还是停留在我跳出去的那个组件(news或是message)

现在我们在home的嵌套路由中不使用缺省值

这是我们也可以直接使用created()给定初始值/home/news,因为这样的话,当我们从别的组件回来的时候,使用的就是/home,不是/home/news,更不是/home/message

使用activated()函数:

发现也不行,因为activated和deactivated记录的是当前活跃和不活跃的组件(从活跃状态转到不活跃状态的组件),所以当我们跳转到别的组件的时候,this.$route.path记录的当前的组件的地址,所以发现当我们再跳转回Home组件的时候,发现跳不回去的情况。

使用:beforeRouteLeave()函数:

Home.vue

<template><div><h4>我是Home</h4><p>我是内容,哈哈哈</p><router-link to="/home/news">新闻</router-link><router-link to="/home/message">消息</router-link><router-view></router-view></div>
</template><script>
export default {name: "Home",data() {return {message: '你好呀',path: '/home/news'}},// 组件的创建与销毁在每次离开和再次回到该组件时都会被调用,如果不希望这两个函数被频繁的创建与调用,可以使用keep-alivecreated() {document.title = '首页'// this.$router.push(this.path)console.log('home组件创建了')},// 组件销毁时回调的函数destroyed() {console.log('home组件被销毁');},// 组件活跃时回调的函数// 注意:activated和deactivated()只有该组件被保持了状态(即router-view使用了keep-alive时才是有效的)activated() {this.$router.push(this.path)console.log('活跃的router:' + this.$route.path);},// 组件不活跃时回调的函数deactivated() {console.log('home组件退出活跃');},// 退出组件时回调的函数(即组件跳转前回调的函数)beforeRouteLeave(from, to, next) {console.log(this.$route.path);this.path = this.$route.path;// console.log(this.path);next()}
}
</script><style scoped></style>

注意:index.js中的嵌套路由中的缺省值应该去除:

index.js

import Vue from 'vue'
// 导入路由
import Router from 'vue-router'// import Home from '../components/Home'
// import About from '../components/About'// 通过Vue.use(插件) ,安装插件
Vue.use(Router)// 路由懒加载的写法:动态导入
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const User = () => import('../components/User')
const Profile = () => import('../components/Profile')// 创建路由对象:Router对象 const router = new Router()
const routes = [{path: '',redirect: '/home'},{// 添加数据meta: {title: '首页'},path: '/home',component: Home,// 嵌套路由children: [{// 子路由不需要添加斜杠,会自动拼接path: 'news',component: HomeNews},{path: 'message',component: HomeMessage}]},{meta: {title: '关于'},// 拼接userIdpath: '/about',component: About},{meta: {title: '用户'},path: '/user/:userId',component: User},{meta: {title: '个人信息'},path: '/profile',component: Profile}
]const router = new Router({routes,// 把模式改为html5的history,就没有#号了mode: 'history',linkActiveClass: 'active'
})// 导航守卫router.beforeEach():需要传入一个NavigatingGuard
router.beforeEach((to, from, next) => {// 从from到to,to指的就是当前的routerdocument.title = to.matched[0].meta.title// 继续放行:next(),必须写,否则就会阻塞next()
})// 后置钩子
router.afterEach((to, from) => {console.log('后置钩子')
})export default router

注意:这是由于App.vue中的router-view被keep-alive包裹,所以并不会每次都调用创建与销毁函数

如果这个时候在User,Profile,About等组件中实现创建与销毁函数,由于keep-avliev包裹的router-view中是这些组件显示的位置,所以只要这些组件中实现了创建与销毁函数,这两个函数也不会被频繁的调用,在每次离开组件时,都会被缓存。

如果这时我们不希望把Profile也缓存,即我们希望Profile组件每次进入与离开时能够重新创建和销毁,这时keep-alive可以使用exclude属性排除不希望缓存的组件

<keep-alive exclude="Profile"><router-view></router-view>
</keep-alive>

如果像排除多个组件:

<!--注意:exclude中逗号后不能使用空格,否则会出问题-->
<keep-alive exclude="Profile,User"><router-view></router-view>
</keep-alive>

九、TabBar小案例的实现

9.1、TabBar实现思路

创建一个新的vue-cli2项目

vue init webpack tabbar

刚才忘记选择路由,不过待会可以自己安装

删除我用的东西:如HelloWorld相关的东西,App.vue的默认样式等,assets中的图标

基础实现

App.vue

<template><div id="app"><div id="tab-bar"><div>首页</div><div>分类</div><div>购物车</div><div>我的</div></div></div>
</template><script>export default {name: 'App',components: {}}
</script><style>
</style>

发现body有默认边框:

在assets中新建img,css文件夹,在css文件夹中新建base.css

base.css

body {padding: 0;margin: 0;
}

导入base.css

main.js

import Vue from 'vue'
import App from './App'Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',render: h => h(App)
})require('./assets/css/base.css')

效果:没有边框

在main.js中导入css会显得比较乱,所以我们可以直接在App.vue的style中直接写css代码

App.vue

<template><div id="app"><div id="tab-bar"><div>首页</div><div>分类</div><div>购物车</div><div>我的</div></div></div>
</template><script>export default {name: 'App',components: {}}
</script><style>
body {padding: 0;margin: 0;
}
</style>

这样也是可以的:

如果要初始化的样式非常多,全部写在vue文件中,也会显得十分复杂,我们也可以在css文件中写好这些样式代码,然后在vue中导入:

App.vue

<template><div id="app"><div id="tab-bar"><div>首页</div><div>分类</div><div>购物车</div><div>我的</div></div></div>
</template><script>export default {name: 'App',components: {}}
</script><style>
@import "./assets/css/base.css";
</style>

让App.vue中的内容水平排列

box-shadow的用法:x为正,y为正时

App.vue

<template><div id="app"><div id="tab-bar"><div class="tab-bar-item">首页</div><div class="tab-bar-item">分类</div><div class="tab-bar-item">购物车</div><div class="tab-bar-item">我的</div></div></div>
</template><script>export default {name: 'App',components: {}}
</script><style>
@import "./assets/css/base.css";#tab-bar {display: flex;  /*水平排列*/background-color: #f6f6f6;/*让tab-bar在底部*/position: fixed;left: 0;right: 0;bottom: 0;/*过度阴影: 最后一个.8表示透明度为0.8*/box-shadow: 0 -3px 1px rgba(100,100,100,.3);
}/*让每个class均等分,并且居中*/
.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;
}
</style>

效果:

以上面的方式搭建框架,不能复用

9.2、独立组件的封装

在components文件夹下新建tabbar文件夹,在该文件夹下新建TabBar.vue

TabBar.vue

<template><div id="tab-bar"><div class="tab-bar-item">首页</div><div class="tab-bar-item">分类</div><div class="tab-bar-item">购物车</div><div class="tab-bar-item">我的</div></div>
</template><script>
export default {name: "TabBar"
}
</script><style scoped>#tab-bar {display: flex;  /*水平排列*/background-color: #f6f6f6;/*让tab-bar在底部*/position: fixed;left: 0;right: 0;bottom: 0;/*过度阴影: 最后一个.8表示透明度为0.8*/box-shadow: 0 -3px 1px rgba(100,100,100,.3);}/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;}
</style>

在img文件夹下新建tabbar文件夹,该文件夹下放与tabbar有关的图片

TabBar.vue

<template><div id="tab-bar"><div class="tab-bar-item"><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div><div class="tab-bar-item"><img src="../../assets/img/tabbar/home.svg" alt=""><div>分类</div></div><div class="tab-bar-item"><img src="../../assets/img/tabbar/home.svg" alt=""><div>购物车</div></div><div class="tab-bar-item"><img src="../../assets/img/tabbar/home.svg" alt=""><div>我的</div></div></div>
</template><script>
export default {name: "TabBar"
}
</script><style scoped>#tab-bar {display: flex;  /*水平排列*/background-color: #f6f6f6;/*让tab-bar在底部*/position: fixed;left: 0;right: 0;bottom: 0;/*过度阴影: 最后一个.8表示透明度为0.8*/box-shadow: 0 -3px 1px rgba(100,100,100,.3);}/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;}
</style>

这时我们发现TabBar中封装了tar-bar-item一些东西,即我们只希望TabBar中只封装与TabBar有关的样式和属性,对于在TabBar中的一些小组件,我们不应该封装到一起

修改方式一:TabBar.vue中使用插槽

TabBar.vue

<template><div id="tab-bar"><slot></slot></div>
</template><script>
export default {name: "TabBar"
}
</script><style scoped>#tab-bar {display: flex;  /*水平排列*/background-color: #f6f6f6;/*让tab-bar在底部*/position: fixed;left: 0;right: 0;bottom: 0;/*过度阴影: 最后一个.8表示透明度为0.8*/box-shadow: 0 -3px 1px rgba(100,100,100,.3);}/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;}
</style>

App.vue

<template><div id="app"><tab-bar><div class="tab-bar-item"><img src="./assets/img/tabbar/home.svg" alt=""><div>首页</div></div><div class="tab-bar-item"><img src="./assets/img/tabbar/home.svg" alt=""><div>分类</div></div><div class="tab-bar-item"><img src="./assets/img/tabbar/home.svg" alt=""><div>购物车</div></div><div class="tab-bar-item"><img src="./assets/img/tabbar/home.svg" alt=""><div>我的</div></div></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";export default {name: 'App',components: {TabBar}}
</script><style>@import "./assets/css/base.css";/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;}
</style>

这时App.vue中的代码又比较多

在抽取:TabBarItem.vue

<template><div class="tab-bar-item"><div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div></div>
</template><script>
export default {name: "TabBarItem"
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}
</style>

App.vue

<template><div id="app"><tab-bar><tab-bar-item></tab-bar-item><tab-bar-item></tab-bar-item><tab-bar-item></tab-bar-item><tab-bar-item></tab-bar-item></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";import TabBarItem from "./components/tabbar/TabBarItem";export default {name: 'App',components: {TabBar,TabBarItem}}
</script><style>@import "./assets/css/base.css";
</style>

效果:

问题:所有的TarBarItem组件显示的内容都是相同的

解决办法:使用插槽

TabBarItem.vue

<template><div class="tab-bar-item"><slot name="item-icon"></slot><slot name="item-text"></slot>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem"
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}
</style>

App.vue

<template><div id="app"><tab-bar><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/home.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/category.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";import TabBarItem from "./components/tabbar/TabBarItem";export default {name: 'App',components: {TabBar,TabBarItem}}
</script><style>@import "./assets/css/base.css";
</style>

效果:

现在图片可以插入了,但是当我们点击某个按钮的时候,我们还要传入一张活跃时的图片(一开始的时候就把两张图片都插入,只是用的时候动态决定显示那一张图片)

TarBarItem.vue

<template><div class="tab-bar-item"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :class="{active: isActive}"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",data() {return {isActive: true}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}.active {color: red;}
</style>

App.vue

<template><div id="app"><tab-bar><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/home.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/category.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";import TabBarItem from "./components/tabbar/TabBarItem";export default {name: 'App',components: {TabBar,TabBarItem}}
</script><style>@import "./assets/css/base.css";
</style>

效果 :

路由跳转:

安装路由:

npm install vue-router --save

在src下新建router文件夹,该文件夹下新建index.js

在新建views目录,其内容如下:

Home.vue

<template><div>首页</div>
</template><script>
export default {name: "Home"
}
</script><style scoped></style>

Profile.vue

<template><div>个人</div>
</template><script>
export default {name: "Profile"
}
</script><style scoped></style>

Cart.vue

<template><div>购物车</div>
</template><script>
export default {name: "Cart"
}
</script><style scoped></style>

Category.vue

<template><div>分类</div>
</template><script>
export default {name: "Category"
}
</script><style scoped></style>

配置映射关系:

index.js

import Vue from 'vue'
import VueRouter from "vue-router"
const Home = () => import('../views/home/Home')
const Cart = () => import('../views/cart/Cart')
const Category = () => import('../views/category/Category')
const Profile = () => import('../views/profile/Profile')// 安装路由插件
Vue.use(VueRouter)// 创建路由对象
const routes = [{path: '',redirect: '/home'},{path: '/home',component: Home},{path: '/cart',component: Cart},{path: '/category',component: Category},{path: '/profile',component: Profile}]const router = new VueRouter({routes,mode: 'history'
})// 导出
export default router

监听组件:在App.vue中需要监听4个组件跳转,我们可以直接在TabBarItem中监听,这样的话只用监听一次。

TabBarItem.vue

<template><div class="tab-bar-item" @click="itemClick"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :class="{active: isActive}"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",// 让程序外部传入路径props: {path: String},data() {return {isActive: true}},methods: {itemClick() {// 可以使用push,也可以使用replacethis.$router.push(this.path)}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}.active {color: red;}
</style>

效果:

监听组件是否活跃:

使用计算属性:

TabBarItem.vue

<template><div class="tab-bar-item" @click="itemClick"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :class="{active: isActive}"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",// 让程序外部传入路径props: {path: String},data() {return {// isActive: true}},computed: {isActive() {// this.$route.path中存放的是当前组件的路由,this.path是当前活跃的路由,如果当前活跃的路由和当前的组件的路由相同,返回true,否则放回falsereturn this.$route.path.indexOf(this.path) !== -1}},methods: {itemClick() {// 可以使用push,也可以使用replacethis.$router.push(this.path)}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}.active {color: red;}
</style>

可以动态修改文字颜色:

TabBarItem.vue:使用计算属性返回颜色

<template><div class="tab-bar-item" @click="itemClick"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :style="activeStyle"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",// 让程序外部传入路径props: {path: String,activeColor: {type: String,default: 'red'}},data() {return {// isActive: true}},computed: {isActive() {// this.$route.path中存放的是当前组件的路由,this.path是当前活跃的路由,如果当前活跃的路由和当前的组件的路由相同,返回true,否则放回falsereturn this.$route.path.indexOf(this.path) !== -1},activeStyle() {// 如果处于活跃状态,使用动态传入的颜色,否则使用默认值return this.isActive ? {color: this.activeColor} : {}}},methods: {itemClick() {// 可以使用push,也可以使用replacethis.$router.push(this.path)}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}
</style>

App.vue:传入颜色

<template><div id="app"><router-view></router-view><tab-bar><tab-bar-item path="/home"><img slot="item-icon" src="./assets/img/tabbar/home.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><!-- 传入自定义颜色--><tab-bar-item path="/category" active-color="blue"><img slot="item-icon" src="./assets/img/tabbar/category.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item path="/cart" active-color="pink"><img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item path="/profile"><img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";import TabBarItem from "./components/tabbar/TabBarItem";export default {name: 'App',components: {TabBar,TabBarItem}}
</script><style>@import "./assets/css/base.css";
</style>

现在App.vue中代码太多,我们可以把它抽取出来

在components中新建MainTabBar.vue

<template><tab-bar><tab-bar-item path="/home"><img slot="item-icon" src="../assets/img/tabbar/home.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/home_active.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><!-- 传入自定义颜色--><tab-bar-item path="/category" active-color="blue"><img slot="item-icon" src="../assets/img/tabbar/category.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/category_active.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item path="/cart" active-color="pink"><img slot="item-icon" src="../assets/img/tabbar/shopcart.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/shopcart_active.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item path="/profile"><img slot="item-icon" src="../assets/img/tabbar/profile.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/profile_active.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar>
</template><script>import TabBar from "./tabbar/TabBar";import TabBarItem from "./tabbar/TabBarItem";export default {name: "MainTabBar",components: {TabBar,TabBarItem}}
</script><style scoped></style>

App.vue

<template><div id="app"><router-view></router-view><main-tab-bar></main-tab-bar></div>
</template><script>import MainTabBar from "./components/MainTabBar";export default {name: 'App',components: {MainTabBar}}
</script><style>@import "./assets/css/base.css";
</style>

效果:

十、Promise

Es6语法:Promise

本周学习内容:Promise,Vuex,网络请求封装,项目开发

上次tabbar项目的路径问题:

跟文件夹起别名:在webpack中配置:

webpack.base.conf.js

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')function resolve (dir) {return path.join(__dirname, '..', dir)
}module.exports = {context: path.resolve(__dirname, '../'),entry: {app: './src/main.js'},output: {path: config.build.assetsRoot,filename: '[name].js',publicPath: process.env.NODE_ENV === 'production'? config.build.assetsPublicPath: config.dev.assetsPublicPath},resolve: {extensions: ['.js', '.vue', '.json'],alias: {// 给路径src起了一个别名@,即xxx/src/components/tabbar可以写为@/components/tabbar'@': resolve('src'),// 起其他的别名'assets': resolve('src/assets'),'components': resolve('src/components'),'views': resolve('src/views')}},module: {rules: [{test: /\.vue$/,loader: 'vue-loader',options: vueLoaderConfig},{test: /\.js$/,loader: 'babel-loader',include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]},{,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :class="{active: isActive}"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",data() {return {isActive: true}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}.active {color: red;}
</style>

App.vue

<template><div id="app"><tab-bar><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/home.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/category.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item><img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";import TabBarItem from "./components/tabbar/TabBarItem";export default {name: 'App',components: {TabBar,TabBarItem}}
</script><style>@import "./assets/css/base.css";
</style>

效果 :

[外链图片转存中…(img-Vph5XIP6-1628263318761)]

路由跳转:

安装路由:

npm install vue-router --save

在src下新建router文件夹,该文件夹下新建index.js

在新建views目录,其内容如下:

[外链图片转存中…(img-G5bZpHnO-1628263318762)]

Home.vue

<template><div>首页</div>
</template><script>
export default {name: "Home"
}
</script><style scoped></style>

Profile.vue

<template><div>个人</div>
</template><script>
export default {name: "Profile"
}
</script><style scoped></style>

Cart.vue

<template><div>购物车</div>
</template><script>
export default {name: "Cart"
}
</script><style scoped></style>

Category.vue

<template><div>分类</div>
</template><script>
export default {name: "Category"
}
</script><style scoped></style>

配置映射关系:

index.js

import Vue from 'vue'
import VueRouter from "vue-router"
const Home = () => import('../views/home/Home')
const Cart = () => import('../views/cart/Cart')
const Category = () => import('../views/category/Category')
const Profile = () => import('../views/profile/Profile')// 安装路由插件
Vue.use(VueRouter)// 创建路由对象
const routes = [{path: '',redirect: '/home'},{path: '/home',component: Home},{path: '/cart',component: Cart},{path: '/category',component: Category},{path: '/profile',component: Profile}]const router = new VueRouter({routes,mode: 'history'
})// 导出
export default router

监听组件:在App.vue中需要监听4个组件跳转,我们可以直接在TabBarItem中监听,这样的话只用监听一次。

TabBarItem.vue

<template><div class="tab-bar-item" @click="itemClick"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :class="{active: isActive}"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",// 让程序外部传入路径props: {path: String},data() {return {isActive: true}},methods: {itemClick() {// 可以使用push,也可以使用replacethis.$router.push(this.path)}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}.active {color: red;}
</style>

效果:

[外链图片转存中…(img-OBODx7YO-1628263318762)]

监听组件是否活跃:

使用计算属性:

TabBarItem.vue

<template><div class="tab-bar-item" @click="itemClick"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :class="{active: isActive}"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",// 让程序外部传入路径props: {path: String},data() {return {// isActive: true}},computed: {isActive() {// this.$route.path中存放的是当前组件的路由,this.path是当前活跃的路由,如果当前活跃的路由和当前的组件的路由相同,返回true,否则放回falsereturn this.$route.path.indexOf(this.path) !== -1}},methods: {itemClick() {// 可以使用push,也可以使用replacethis.$router.push(this.path)}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}.active {color: red;}
</style>

[外链图片转存中…(img-IqyuRhI7-1628263318763)]

可以动态修改文字颜色:

TabBarItem.vue:使用计算属性返回颜色

<template><div class="tab-bar-item" @click="itemClick"><!-- 插槽外包装一层div的好处:在外部使用插槽的时候,是直接把外部的标签把slot插槽替换掉,为了防止替换后slot中的样式不起作用,在外层包装一层div,使之替换后样式仍能起作用--><div v-if="!isActive"><slot name="item-icon"></slot></div><div v-else><slot name="item-icon-active"></slot></div><div :style="activeStyle"><slot name="item-text"></slot></div>
<!--    <div><img src="../../assets/img/tabbar/home.svg" alt=""><div>首页</div></div>--></div>
</template><script>
export default {name: "TabBarItem",// 让程序外部传入路径props: {path: String,activeColor: {type: String,default: 'red'}},data() {return {// isActive: true}},computed: {isActive() {// this.$route.path中存放的是当前组件的路由,this.path是当前活跃的路由,如果当前活跃的路由和当前的组件的路由相同,返回true,否则放回falsereturn this.$route.path.indexOf(this.path) !== -1},activeStyle() {// 如果处于活跃状态,使用动态传入的颜色,否则使用默认值return this.isActive ? {color: this.activeColor} : {}}},methods: {itemClick() {// 可以使用push,也可以使用replacethis.$router.push(this.path)}}
}
</script><style scoped>/*让每个class均等分,并且居中*/.tab-bar-item {/*均等分*/flex: 1;text-align: center;height: 49px;/*设置文字大小*/font-size: 15px;}/*设置图片大小*/.tab-bar-item img{width: 24px;height: 24px;/*去除文字与图标之间的距离:默认为3个像素*/vertical-align: middle;/*设置图片与文字之间的距离为2个像素*/margin-bottom: 2px;}
</style>

App.vue:传入颜色

<template><div id="app"><router-view></router-view><tab-bar><tab-bar-item path="/home"><img slot="item-icon" src="./assets/img/tabbar/home.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><!-- 传入自定义颜色--><tab-bar-item path="/category" active-color="blue"><img slot="item-icon" src="./assets/img/tabbar/category.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item path="/cart" active-color="pink"><img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item path="/profile"><img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt=""><img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar></div>
</template><script>import TabBar from "./components/tabbar/TabBar";import TabBarItem from "./components/tabbar/TabBarItem";export default {name: 'App',components: {TabBar,TabBarItem}}
</script><style>@import "./assets/css/base.css";
</style>

现在App.vue中代码太多,我们可以把它抽取出来

在components中新建MainTabBar.vue

<template><tab-bar><tab-bar-item path="/home"><img slot="item-icon" src="../assets/img/tabbar/home.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/home_active.svg" alt=""><div slot="item-text">首页</div></tab-bar-item><!-- 传入自定义颜色--><tab-bar-item path="/category" active-color="blue"><img slot="item-icon" src="../assets/img/tabbar/category.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/category_active.svg" alt=""><div slot="item-text">分类</div></tab-bar-item><tab-bar-item path="/cart" active-color="pink"><img slot="item-icon" src="../assets/img/tabbar/shopcart.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/shopcart_active.svg" alt=""><div slot="item-text">购物车</div></tab-bar-item><tab-bar-item path="/profile"><img slot="item-icon" src="../assets/img/tabbar/profile.svg" alt=""><img slot="item-icon-active" src="../assets/img/tabbar/profile_active.svg" alt=""><div slot="item-text">我的</div></tab-bar-item></tab-bar>
</template><script>import TabBar from "./tabbar/TabBar";import TabBarItem from "./tabbar/TabBarItem";export default {name: "MainTabBar",components: {TabBar,TabBarItem}}
</script><style scoped></style>

App.vue

<template><div id="app"><router-view></router-view><main-tab-bar></main-tab-bar></div>
</template><script>import MainTabBar from "./components/MainTabBar";export default {name: 'App',components: {MainTabBar}}
</script><style>@import "./assets/css/base.css";
</style>

效果:

[外链图片转存中…(img-GqSEBW5B-1628263318763)]

十、Promise

Es6语法:Promise

本周学习内容:Promise,Vuex,网络请求封装,项目开发

上次tabbar项目的路径问题:

跟文件夹起别名:在webpack中配置:

webpack.base.conf.js

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')function resolve (dir) {return path.join(__dirname, '..', dir)
}module.exports = {context: path.resolve(__dirname, '../'),entry: {app: './src/main.js'},output: {path: config.build.assetsRoot,filename: '[name].js',publicPath: process.env.NODE_ENV === 'production'? config.build.assetsPublicPath: config.dev.assetsPublicPath},resolve: {extensions: ['.js', '.vue', '.json'],alias: {// 给路径src起了一个别名@,即xxx/src/components/tabbar可以写为@/components/tabbar'@': resolve('src'),// 起其他的别名'assets': resolve('src/assets'),'components': resolve('src/components'),'views': resolve('src/views')}},module: {rules: [{test: /\.vue$/,loader: 'vue-loader',options: vueLoaderConfig},{test: /\.js$/,loader: 'babel-loader',include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]},{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,

codewhy_vue笔记01相关推荐

  1. PHP 学习笔记 01

    例子: 为什么要学PHP 主观原因: 前段时间在学校处理了毕业的一些事情,回到上海后开始了找工作的旅程.意向工作是WPF开发或者ASP.NET 作为后端的WEB开发. 陆陆续续一直在面试,其中有一家公 ...

  2. 状态机系列学习笔记01

    状态机系列学习笔记01 有限状态机(FSM)概念 定义 总的来说,有限状态机系统,是指在不同阶段会呈现出不同的运行状态的系统,这些状态是有限的.不重叠的.这样的系统在某一时刻一定会处于其所有状态中的一 ...

  3. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)--hello bitzhuwei's OS! 最初的OS代码 1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序的 ...

  4. 独立式环境与宿主式环境————《标准C语言指南》读书笔记01

    独立式环境与宿主式环境----<标准C语言指南>读书笔记01 在编写和转换一个C程序之前,需要考虑它的执行环境,因为这关系到源文件的内容(程序应当如何编写),也关系到转换后的程序能否正常执 ...

  5. MyBatis-学习笔记01【01.Mybatis课程介绍及环境搭建】

    Java后端 学习路线 笔记汇总表[黑马程序员] MyBatis-学习笔记01[01.Mybatis课程介绍及环境搭建][day01] MyBatis-学习笔记02[02.Mybatis入门案例] M ...

  6. 微信公众号Java开发-笔记01【微信公众号介绍、开发环境搭建】

    学习网址:哔哩哔哩网站 微信公众号开发-Java版 微信公众号Java开发-笔记01[微信公众号介绍.开发环境搭建] 微信公众号Java开发-笔记02[] 微信公众号Java开发-笔记03[] 微信公 ...

  7. JavaWeb黑马旅游网-学习笔记01【准备工作】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb黑马旅游网-学习笔记01[准备工作] JavaWeb黑马旅游网-学习笔记02[注册功能] JavaWeb黑马旅游网-学习笔记03[登陆和 ...

  8. Maven-学习笔记01【基础-Maven基本概念】

    Java后端 学习路线 笔记汇总表[黑马程序员] 黑马程序员(腾讯微云)Maven基础讲义.pdf Maven-学习笔记01[基础-Maven基本概念] Maven-学习笔记02[基础-Maven的安 ...

  9. Redis-学习笔记01【Redis环境搭建】

    Java后端 学习路线 笔记汇总表[黑马程序员] Redis-学习笔记01[Redis环境搭建] Redis-学习笔记02[Redis命令操作] Redis-学习笔记03[Redis持久化] Redi ...

最新文章

  1. UML工具 MAGICDraw
  2. 实现DUBBO服务环境隔离
  3. mybatis使用Mapper时对参数处理的设计与实现
  4. Spring文档学习
  5. indows 平台下 Go 语言的安装和环境变量设置
  6. 使用JQUERY实现局部页面定时刷新
  7. Linux安装无法运行install,linux 无法 安装swoole
  8. 集中式整合之编写springsecurity配置类
  9. java api 1.6 下载_Java JDK API
  10. UART/I2C/SPI/1-wire四大通信接口的神解释
  11. C语言车辆管理报告,用c语言编的车辆管理
  12. 资产模型数据初始化时应注意的事项
  13. java label 超链接_Swing之带超链接的label简单实现。
  14. 在Mac中如何通过命令对NTFS磁盘格式化
  15. eclipse 输入提示插件_【STM32】搭建基于Eclipse平台的STM32调试环境
  16. Java获取网络视频封面图片
  17. html5华文行楷字体代码,html5 支持的字体样式
  18. 昂达v80 plus linux,8英寸便携平板 昂达V80 Plus一体工艺来袭
  19. 深度学习中的激活函数及其作用
  20. 要重复多少次变成潜意识_从骨子里的改变-潜意识的力量!

热门文章

  1. android手机存储空间猛增,为什么安卓手机运行内存和储存空间增长速度这么快,什么原因呢?...
  2. ️ 后羿采集器——最良心的爬虫软件
  3. 短视频拍摄技巧分享,巧用转场提升高级感,拥有自己的风格很重要
  4. 高光谱成像的传感器和相机要求
  5. Median (25)
  6. 根号 巴比伦_建立巴比伦卫生设计系统
  7. Android使用Fragment打造万能页面切换框架(一)
  8. C#【时间操作类】使用TimeSpan计算时间差
  9. the Contextual Loss论文理解
  10. ubuntu18.04安装caffe-cpu版