学习资料来源于哔哩哔哩-王红元老师网课
Vue学习
Vue Day 01
一. Vuejs
1.1. 认识Vuejs
-
Vue的读音
读音 /vjuː/,类似于 view -
Vue的渐进式
渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验。即一点点的对项目进行重构,比如将项目里的jQuery一点一点去掉用vue替代。
或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统。
比如Core+Vue-router+Vuex,也可以满足你各种各样的需求。 -
Vue的特点
(先简单了解,后面加深)
解耦视图和数据
可复用的组件
前端路由技术
状态管理
虚拟DOM
PS:用了vue一般都不用jQuery了,只有老项目还存在jQuery
1.2. 安装Vue
- CDN引入
为了快,不用随时调用,前期学习建议下载引入
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
-
下载引入(官方网站—安装)
开发环境 https://vuejs.org/js/vue.js
生产环境 https://vuejs.org/js/vue.min.js -
npm安装(主流)
后续通过webpack和CLI的使用,我们使用该方式。
1.3. Vue的初体验
- Hello Vuejs PPT*6
- mustache -> 体验vue响应式
- Vue列表展示 PPT*7
- v-for
- 后面给数组追加元素的时候, 新的元素也可以在界面中渲染出来
- Vue计数器小案例 PPT*8
- 事件监听: click -> methods
1.4. Vue中的MVVM
详细看 PPT*9
Model–view–viewmodel
通常我们学习一个概念,最好的方式是去看维基百科(千万别看成了百度百科)
https://zh.wikipedia.org/wiki/MVVM
维基百科的官方解释,这里不再赘述。
1.5. 创建Vue时, options可以放那些东西
option
详细解析: https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE
-
开发中什么时候称之为方法, 什么时候称之为函数?
- 方法: method(类里的东西)
- 函数: function
-
el:
- 类型:string | HTMLElement
- 作用:决定之后Vue实例会管理哪一个DOM。
-
data:
- 类型:Object | Function (在组件当中data必须是一个函数)
- 作用:Vue实例对应的数据对象。
-
methods:
- 类型:{ [key: string]: Function }
- 作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用。
-
生命周期函数 PPT*12
Vue源码:https://github.com/vuejs/vue/tree/v2.6.14
开发过程中分两个版本:debug/release
GitHub上下载release版本
代码规范
代码规范:缩进4个空格, 2个空格.
CLI -> 自动创建.editconfig用于规范代码 2个空格.
vue的template :vscode在用户片段里设置,详细看http://mtw.so/5TnpAu
二.插值语法
- mustache语法
- 就是双大括号
- 翻译:胡子/胡须
<div id="app">
<h2>{{message}}</h2>
<h2>{{message}}, 李银河!</h2>
<!--mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式-->
<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>
- v-once PPT*16
- 该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的)
- 该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
用console不会被渲染
与v-for不一样
- v-html
- 如果我们直接通过{{}}来输出,会将HTML代码也一起输出。
- 但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容。
<div id="app">
<h2>{{url}}</h2>
<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>
- v-text
- v-text作用和Mustache比较相似:都是用于将数据显示在界面中
- v-text通常情况下,接受一个string类型
- 不好用,拼接很麻烦,会覆盖
<div id="app">
<h2>{{message}}, 李银河!</h2>
<h2 v-text="message">, 李银河!</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
- v-pre: {{}}
- v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
<div id="app">
<h2>{{message}}</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
- v-cloak: 斗篷
- 在某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签。
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
// 在vue解析之前, div中有一个属性v-cloak
// 在vue解析之后, div中没有一个属性v-cloak
setTimeout(function () {
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
}, 1000)
</script>
三. v-bind
3.1. v-bind绑定基本属性
除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
比如动态绑定a元素的href属性
比如动态绑定img元素的src属性
- v-bind
- 作用:动态绑定属性
- 缩写::
- 预期:any (with argument) | Object (without argument)
- 参数:attrOrProp (optional)
- v-bind:sr
- :href
- 作用:动态绑定属性
- 缩写::
- 预期:any (with argument) | Object (without argument)
- 参数:attrOrProp (optional)
- v-bind语法糖
<img :src="imgURL" alt="">
<div id="app">
<!-- 错误的做法: 这里不可以使用mustache语法-->
<!--<img src="{{imgURL}}" alt="">-->
<!-- 正确的做法: 使用v-bind指令 -->
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHref">百度一下</a>
<!--<h2>{{}}</h2>-->
<!--语法糖的写法-->
<img :src="imgURL" alt="">
<a :href="aHref">百度一下</a>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp',
aHref: 'http://www.baidu.com'
}
})
</script>
3.2. v-bind动态绑定class
- v-bind动态绑定class(对象)语法
- 当数据为某个状态时,字体显示红色。
- 当数据另一个状态时,字体显示黑色。
- 对象语法: 作业 :class=’{类名: boolean}’
-
用法一:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2>
-
用法二:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
-
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
-
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
-
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app">
<!--<h2 class="active">{{message}}</h2>-->
<!--<h2 :class="active">{{message}}</h2>-->
<!-- 对象里可以放键值对 -->
<!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>-->
<!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>-->
<!-- class和动态绑定,最后会一起合并 -->
<h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>
<h2 class="title" v-bind:class="getClasses()">{{message}}</h2>
<button v-on:click="btnClick">按钮</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
//默认为true
isActive: true,
isLine: true
},
methods: {
btnClick: function () {
//每次取反
this.isActive = !this.isActive
},
getClasses: function () {
return {active: this.isActive, line: this.isLine}
}
}
})
</script>
- 数组语法:
-
用法一:直接通过{}绑定一个类
<h2 :class="['active']">Hello World</h2>
-
用法二:也可以传入多个值
<h2 :class=“[‘active’, 'line']">Hello World</h2>
-
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
<h2 class="title" :class=“[‘active’, 'line']">Hello World</h2>
-
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
-
<div id="app">
<h2 class="title" :class="[active, line]">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
active: 'aaaaaa',
line: 'bbbbbbb'
},
methods: {
getClasses: function () {
return [this.active, this.line]
}
}
})
</script>
3.3. v-bind动态绑定style
我们可以利用v-bind:style来绑定一些CSS内联样式。
我们可以使用驼峰式 (camelCase) fontSize
或短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size’
- 绑定方式一:对象语法
:style="{color: currentColor, fontSize: fontSize + 'px'}"
- style后面跟的是一个对象类型
- 对象的key是CSS属性名称
- 对象的value是具体赋的值,值可以来自于data中的属性
- 对象语法:
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.title {
font-size: 50px;
color: red;
}
</style>
</head>
<body>
<div id="app">
<!--<h2 :style="{key(属性名): value(属性值)}">{{message}}</h2>-->
<!--'50px'必须加上单引号, 否则是当做一个变量去解析-->
<!--<h2 :style="{fontSize: '50px'}">{{message}}</h2>-->
<!--finalSize当成一个变量使用-->
<!--<h2 :style="{fontSize: finalSize}">{{message}}</h2>-->
<h2 :style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}</h2>
<h2 :style="getStyles()">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
finalSize: 100,
finalColor: 'red',
},
methods: {
getStyles: function () {
return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor}
}
}
})
</script>
- 数组语法:
- 绑定方式二:数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
- style后面跟的是一个数组类型
- 多个值以,分割即可
<div id="app">
<h2 :style="[baseStyle, baseStyle1]">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
baseStyle: {backgroundColor: 'red'},
baseStyle1: {fontSize: '100px'},
}
})
</script>
四. 计算属性
- 在模板中可以直接通过插值语法显示一些data中的数据。
- 但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
- 比如我们有firstName和lastName两个变量,我们需要显示完整的名称。
- 但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}} {{lastName}}
- 我们可以将上面的代码换成计算属性:
- 而计算属性是写在实例的computed选项中的。
- 案例一: firstName+lastName
<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'
},
// computed: 计算属性()
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName
}
}
})
</script>
- 案例二: books -> price
<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: 111, name: '代码大全', price: 105},
{id: 112, name: '深入理解计算机原理', price: 98},
{id: 113, name: '现代操作系统', price: 87},
]
},
computed: {
totalPrice: function () {
let result = 0
for (let i=0; i < this.books.length; i++) {
result += this.books[i].price
}
return result
// for (let i in this.books) {
// this.books[i]
// }
//
// for (let book of this.books) {
//
// }
}
}
})
</script>
</body>
计算属性的setter和getter
- 每个计算属性都包含一个getter和一个setter
- 一般情况不写get,默认只有setter
具体代码:
<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
// }
// name: 'leng'
// 计算属性一般是没有set方法, 只读属性.
fullName: {
//可用于赋值方法
set: function(newValue) {
// console.log('-----', newValue);
//截取,对字符串进行封装
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1];
},
get: function () {
return this.firstName + ' ' + this.lastName
}
},
//写下面就可以替代上面的,只是一个get
// fullName: function () {
// return this.firstName + ' ' + this.lastName
// }
}
})
</script>
</body>
计算属性的缓存
- 与method的对比
- 计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。
代码如下:
<body>
<div id="app">
<!--1.直接拼接: 语法过于繁琐-->
<h2>{{firstName}} {{lastName}}</h2>
<!--2.通过定义methods-->
<!--<h2>{{getFullName()}}</h2>-->
<!--<h2>{{getFullName()}}</h2>-->
<!--<h2>{{getFullName()}}</h2>-->
<!--<h2>{{getFullName()}}</h2>-->
<!--3.通过computed-->
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
// angular -> google
// TypeScript(microsoft) -> ts(类型检测)
// flow(facebook) ->
const app = new Vue({
el: '#app',
data: {
firstName: 'Kobe',
lastName: 'Bryant'
},
methods: {
getFullName: function () {
console.log('getFullName');
return this.firstName + ' ' + this.lastName
}
},
computed: {
fullName: function () {
console.log('fullName');
return this.firstName + ' ' + this.lastName
}
}
})
</script>
</body>
Vue Day 02
五. 事件监听
5.1 v-on
- 监听用户发生的时间,比如点击、拖拽、键盘事件等等
- v表示Vue
- 作用:绑定事件监听器
- 缩写:@
- 预期:Function | Inline Statement | Object
- 参数:event
代码如下:
<body>
<div id="app">
<h2>{{counter}}</h2>
<!--<h2 v-bind:title></h2>-->
<!--<h2 :title></h2>-->
<!--<button v-on:click="counter++">+</button>-->
<!--<button v-on:click="counter--">-</button>-->
<!--<button v-on:click="increment">+</button>-->
<!--<button v-on:click="decrement">-</button>-->
<button @click="increment">+</button>
<button @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>
5.1.1 v-on也有对应的语法糖
- v-on:click可以写成@click
- 情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
- 情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
<body>
<div id="app">
<!--1.事件调用的方法没有参数,加不加扩号都一样-->
<button @click="btn1Click()">按钮1</button>
<button @click="btn1Click">按钮1</button>
<!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的,
此时 Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
<!--<button @click="btn2Click(123)">按钮2</button>//undefined-->
<!--<button @click="btn2Click()">按钮2</button>-->
<button @click="btn2Click">按钮2</button>
<!--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->
<!-- 在调用方式, 如何手动的获取到浏览器产生的event对象: $event-->
<button @click="btn3Click(abc, $event)">按钮3</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
abc: 123
},
methods: {
btn1Click() {
console.log("btn1Click");
},
btn2Click(event) {
console.log('--------', event);
},
btn3Click(abc, event) {
console.log('++++++++', abc, event);
}
}
})
// 如果函数需要参数,但是没有传入, 那么函数的形参为undefined
// function abc(name) {
// console.log(name);
// }
//
// abc()
</script>
</body>
5.1.2 v-on修饰符
- 在某些情况下,拿到event的目的可能是进行一些事件处理。
- Vue提供了修饰符来帮助我们方便的处理一些事件:
- .stop - 调用 event.stopPropagation()。
- .prevent - 调用 event.preventDefault()。
- .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
- .native - 监听组件根元素的原生事件。
- .once - 只触发一次回调。
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--1. .stop修饰符的使用:停止冒泡,即分别冒泡-->
<div @click="divClick">
aaaaaaa
<button @click.stop="btnClick">按钮</button>
</div>
<!--2. .prevent修饰符的使用:阻止默认行为-->
<br>
<form action="baidu">
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
<!--3. .监听某个键盘的键帽-->
<input type="text" @keyup.enter="keyUp">
<!--4. .once修饰符的使用。点击回调只触发一次-->
<button @click.once="btn2Click">按钮2</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
console.log("btnClick");
},
divClick() {
console.log("divClick");
},
submitClick() {
console.log('submitClick');
},
keyUp() {
console.log('keyUp');
},
btn2Click() {
console.log('btn2Click');
}
}
})
</script>
</body>
</html>
5.2 条件判断 v-if、v-else-if、v-else
- 这三个指令与JavaScript的条件语句if、else、else if类似。
- Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
- v-if后面的条件为false时,对应的元素以及其子元素不会渲染。
- 也就是根本没有不会有对应的标签出现在DOM中。
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
<h1>{{result}}</h1>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
score: 99
},
//条件很多的时候,建议使用计算属性
computed: {
result() {
let showMessage = '';
if (this.score >= 90) {
showMessage = '优秀'
} else if (this.score >= 80) {
showMessage = '良好'
}
// ...
return showMessage
}
}
})
</script>
</body>
</html>
5.3 条件渲染案例*用户登录切换的案例
- 用户再登录时,可以切换使用用户账号登录还是邮箱地址登录。
用户登录切换的案例 - 用户再登录时,可以切换使用用户账号登录还是邮箱地址登录。
<body>
//span作为其的一个父级元素
<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>
5.3.1用户登录切换的案例的小问题
- 如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
- 但是按道理讲,我们应该切换到另外一个input元素中了。
- 在另一个input元素中,我们并没有输入内容。
- 为什么会出现这个问题呢?
- 问题解答:
这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
- 在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。
- 解决方案:
- 如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key
- 并且我们需要保证key的不同
<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>
5.4 v-show
- v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
- v-if和v-show对比
- v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
- v-if当条件为false时,压根不会有对应的元素在DOM中。
- v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
- 开发中如何选择呢?
- 当需要在显示与隐藏之间切片很频繁时,使用v-show
- 当只有一次切换时,通过使用v-if
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--v-if: 当条件为false时, 包含v-if指令的元素, 根本就不会存在dom中-->
<h2 v-if="isShow" id="aaa">{{message}}</h2>
<!--v-show: 当条件为false时, v-show只是给我们的元素添加一个行内样式: display: none-->
<h2 v-show="isShow" id="bbb">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
}
})
</script>
</body>
</html>
六. 循环遍历操作
6.1 v-for遍历数组
<body>
<div id="app">
<!--1.在遍历的过程中,没有使用索引值(下标值)-->
<ul>
<li v-for="item in names">{{item}}</li>
</ul>
<!--2.在遍历的过程中, 获取索引值index-->
<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', 'kobe', 'james', 'curry']
}
})
</script>
</body>
6.2 v-for遍历对象
- v-for的语法类似于JavaScript中的for循环。
- 格式如下:item in items的形式。
<body>
<div id="app">
<!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value-->
<ul>
<li v-for="item in info">{{item}}</li>
</ul>
<!--2.获取key和value 格式: (value, key) -->
<ul>
<li v-for="(value, key) in info">{{value}}-{{key}}</li>
</ul>
<!--3.获取key和value和index 格式: (value, key, index) -->
<ul>
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
info: {
name: 'why',
age: 18,
height: 1.88
}
}
})
</script>
</body>
6.2.1 组件key的属性
- 官方推荐在使用v-for时,给对应的元素或组件添加上一个:key属性。
- key的作用主要是为了高效的更新虚拟DOM。
添加元素:- push是在最后加元素
即:app.letters.push(‘F’) - splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。
注释:该方法会改变原始数组。包含index,就是替换后重新标号index性能比较低。
使用key进行绑定则
即:app.letters.splice();
app.letters(2,0,‘F’)//添加F
arrayObject.splice(index,howmany,item1,…,itemX)
- push是在最后加元素
参数 | 描述 |
---|---|
index | 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。 |
howmany | 必需。要删除的项目数量。如果设置为 0,则不会删除项目。 |
item1, …, itemX | 可选。向数组添加的新项目。 |
<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', 'B', 'C', 'D', 'E']
}
})
</script>
</body>
6.3 哪些数组方法是响应式
- Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
<body>
<div id="app">
<ul>
<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', 'b', 'c', 'd']
},
methods: {
btnClick() {
// 1.push方法
// this.letters.push('aaa')
// this.letters.push('aaaa', 'bbbb', 'cccc')
// 2.pop(): 删除数组中的最后一个元素
// this.letters.pop();
// 3.shift(): 删除数组中的第一个元素
// this.letters.shift();
// 4.unshift(): 在数组最前面添加元素
// this.letters.unshift()
// this.letters.unshift('aaa', 'bbb', 'ccc')
// 5.splice作用: 删除元素/插入元素/替换元素
// 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
// 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
// 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素
// splice(start)
// splice(start):
this.letters.splice(1, 3, 'm', 'n', 'l', 'x')
// this.letters.splice(1, 0, 'x', 'y', 'z')
// 5.sort()
// this.letters.sort()
// 6.reverse()//将数组内容进行反转:app.letters.reverse()
// this.letters.reverse()
// 注意: 通过索引值修改数组中的元素,非响应式的
// this.letters[0] = 'bbbbbb';非响应式。
// this.letters.splice(0, 1, 'bbbbbb')
// set(要修改的对象, 索引值, 修改后的值)
// Vue.set(this.letters, 0, 'bbbbbb')
}
}
})
// function sum(num1, num2) {
// return num1 + num2
// }
//
// function sum(num1, num2, num3) {
// return num1 + num2 + num3
// }
// function sum(...num) {
//...bum是一个可变参数,就可以加入无数数字
// console.log(num);
// }
// sum(20, 30, 40, 50, 601, 111, 122, 33)
</script>
</body>
6.4作业完成
作业一
<!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>
<!--<li :class="{active: 0===currentIndex}"></li>-->
<!--<li :class="{active: 1===currentIndex}"></li>-->
<!--<li :class="{active: 2===currentIndex}"></li>-->
<!--<li :class="{active: 3===currentIndex}"></li>-->
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
movies: ['海王', '海贼王', '加勒比海盗', '海尔兄弟'],
currentIndex: 0
},
methods: {
liClick(index) {
this.currentIndex = index
}
}
})
</script>
</body>
</html>
作业二 (书记购物车案例)
- 取值
- toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
- NumberObject.toFixed(num)
- num必需。规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围。
- 如果省略了该参数,将用 0 代替。
- 过滤器的使用
- totalPrice | 过滤器(filters)
使用filters:{}
- totalPrice | 过滤器(filters)
- button属性
属性 | 描述 |
---|---|
disabled | 返回是否禁用按钮 |
accessKey | 设置或返回访问某个按钮的快捷键。 |
disabled | 设置或返回是否禁用按钮。 |
form | 返回对包含按钮的表单的引用。 |
id | 设置或返回按钮的 id。 |
name | 设置或返回按钮的名称。 |
tabIndex | 设置或返回按钮的 Tab 键控制次序。 |
type | 返回按钮的表单类型。 |
value | 设置或返回显示在按钮上的文本。 |
-
计算属性 computed
-
// for (let i in/of this.books)
-
高阶函数// reduce
- 函数式编程
- 编程范式:命令式编程/声明式编程
- 编程范式:面向对象编程/函数式编程
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">
<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>
<button @click="decrement(index)" v-bind:disabled="item.count <= 1">-</button>
{{item.count}}
<button @click="increment(index)">+</button>
</td>
<td><button @click="removeHandle(index)">移除</button></td>
</tr>
</tbody>
</table>
<!--过滤器totalPrice | 过滤器(filters)-->
<h2>总价格: {{totalPrice | showPrice}}</h2>
</div>
<h2 v-else>购物车为空</h2>
</div>
<script src="../js/vue.js"></script>
<script src="main.js"></script>
<script>
</script>
</body>
</html>
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: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
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)
}
},
computed: {
totalPrice() {
//let totalPrice = 0
//return this.books.reduce(function (preValue, book) {
// return preValue + book.price * book.count
//}, 0)
//1.
for (let i = 0; i < this.books.length; i++) {
totalPrice += this.books[i].price * this.books[i].count
}
return totalPrice
// 2.for (let i in this.books)
// let totalPrice = 0
// for (let i in this.books) {
// const book = this.books[i]
// totalPrice += book.price * book.count
// }
//
// return totalPrice
// 3.for (let i of this.books)
// let totalPrice = 0
// for (let item of this.books) {
// totalPrice += item.price * item.count
// }
// return totalPrice
// for (let i in/of this.books)
// reduce
}
},
filters: {
showPrice(price) {
return '¥' + price.toFixed(2)
}
}
})
style.css
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
清楚缓存快捷键:Ctrl+Shift+Del
七.JavaScript高阶函数的使用
- 高阶函数// reduce
- 函数式编程
- 编程范式:命令式编程/声明式编程
- 编程范式:面向对象编程/函数式编程
- filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
- array.filter(function(currentValue,index,arr), thisValue)
- 注意: filter() 不会对空数组进行检测。
- 注意: filter() 不会改变原始数组。
- map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
- reduce作用对数组中所有的内容进行汇总(相加)
- array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
// 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
// filter/map/reduce
// filter中的回调函数有一个要求: 必须返回一个boolean值
// true: 当返回true时, 函数内部会自动将这次回调的n加入到新的数组中
// false: 当返回false时, 函数内部会过滤掉这次的n
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);
// console.log(total);
let total = nums.filter(function (n) {
return n < 100
}).map(function (n) {
return n * 2
}).reduce(function (prevValue, n) {
return prevValue + n
}, 0)
console.log(total);
// 1.filter函数的使用
// // 10, 20, 40, 50
// let newNums = nums.filter(function (n) {
// return n < 100
// })
// // console.log(newNums);
//
// // 2.map函数的使用
// // 20, 40, 80, 100
// let new2Nums = newNums.map(function (n) { // 20
// return n * 2
// })
// console.log(new2Nums);
//
// // 3.reduce函数的使用
// // reduce作用对数组中所有的内容进行汇总
// let total = new2Nums.reduce(function (preValue, n) {
// return preValue + n
// }, 0)
// console.log(total);
// 第一次: preValue 0 n 20
// 第二次: preValue 20 n 40
// 第二次: preValue 60 n 80
// 第二次: preValue 140 n 100
// 240
// // 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)
// }
//
// console.log(new2Nums);
//
//
// // 3.需求:将所有new2Nums数字相加,得到最终的记过
// let total = 0
// for (let n of new2Nums) {
// total += n
// }
//
// console.log(total);
八.表单绑定 v-model使用
8.1 v-model的基本使用
- Vue中使用v-model指令来实现表单元素和数据的双向绑定。
- v-model用于textarea元素
<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>
8.2 v-model的原理
- @input用于监听用户是否在输入
- event
- event.target
- $event.target.value 获取event对象
- v-model其实是一个语法糖,它的背后本质上是包含两个操作:
- 1.v-bind绑定一个value属性
- 2.v-on指令给当前元素绑定input事件
- 即:
<input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
<body>
<div id="app">
<!--<input type="text" v-model="message">-->
<!--<input type="text" :value="message" @input="valueChange">-->
<!-- @input用于监听用户是否在输入-->
<input type="text" :value="message" @input="message = $event.target.value">
<h2>{{message}}</h2>
</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>
8.3 v-model结合radio类型
- name属性一样即互斥
- v-model绑定同一个变量也是互斥的
- 给默认值则:在data中
sex:'女'
<body>
<div id="app">
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">男
</label>
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
</label>
<h2>您选择的性别是: {{sex}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
sex: '女'
}
})
</script>
</body>
8.4 v-model结合checkbox类型
- 单个勾选框:
- v-model即为布尔值。
- 此时input的value并不影响v-model的值。
- 多个复选框:
- 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
- 当选中某一个时,就会将input的value添加到数组中。
<body>
<div id="app">
<!--1.checkbox单选框-->
<!--<label for="agree">-->
<!--<input type="checkbox" id="agree" v-model="isAgree">同意协议-->
<!--</label>-->
<!--<h2>您选择的是: {{isAgree}}</h2>-->
<!--<button :disabled="!isAgree">下一步</button>-->
<!--2.checkbox多选框-->
<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">羽毛球
<h2>您的爱好是: {{hobbies}}</h2>
<label v-for="item in originHobbies" :for="item">
<input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
</label>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isAgree: false, // 单选框
hobbies: [], // 多选框,
//值绑定
originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
}
})
</script>
</body>
8.5 v-model结合select类型
- 单选:只能选中一个值。
- v-model绑定的是一个值。
- 当我们选中option中的一个时,会将它对应的value赋值到mySelect中
- 多选:可以选中多个值。
- v-model绑定的是一个数组。
- 当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
<body>
<div id="app">
<!--1.选择一个-->
<select name="abc" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是: {{fruit}}</h2>
<!--2.选择多个-->
<select name="abc" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是: {{fruits}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
fruit: '香蕉',
fruits: []
}
})
</script>
</body>
8.6 值绑定
- 动态的给value赋值
<div id="app">
<label v-for="item in originHobbies" :for="item">
<input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
</label>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isAgree: false, // 单选框
hobbies: [], // 多选框,
//值绑定
originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
}
})
</script>
</body>
8.7 v-model修饰符的使用
- lazy修饰符:
- 默认情况下,v-model默认是在input事件中同步输入框的数据的。
- 也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
- lazy修饰符可以让数据在失去焦点或者回车时才会更新:
- number修饰符:
- 默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
- 但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
- number修饰符可以让在输入框中输入的内容自动转成数字类型:
- trim修饰符:
- 如果输入的内容首尾有很多空格,通常我们希望将其去除
- trim修饰符可以过滤内容左右两边的空格
<body>
<div id="app">
<!--1.修饰符: lazy-->
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>
<!--2.修饰符: number-->
<input type="number" v-model.number="age">
<h2>{{age}}-{{typeof age}}</h2>
<!--3.修饰符: trim-->
<input type="text" v-model.trim="name">
<h2>您输入的名字:{{name}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
age: 0,
name: ''
}
})
var age = 0
age = '1111'
age = '222'
</script>
Vue Day 03
九. 组件化开发
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
- 我们将一个完整的页面分成很多个组件。
- 每个组件都用于实现页面的一个功能块。
- 而每一个组件又可以进行细分。
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
任何的应用都会被抽象成一颗组件树。
- 组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
9.1 组件化开发的基本使用
组件的使用分成三个步骤:
- 创建组件构造器 Vue.extend()
- 注册组件 Vue.component()
- 使用组件
查看运行结果:
和直接使用一个div看起来并没有什么区别。
但是我们可以设想,如果很多地方都要显示这样的信息,我们是不是就可以直接使用来完成呢?
<body>
<div id="app">
<!--3.使用组件-->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<div>
<div>
<my-cpn></my-cpn>
</div>
</div>
</div>
<my-cpn></my-cpn>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈哈</p>
<p>我是内容, 呵呵呵呵</p>
</div>`
})
// 2.注册组件
Vue.component('my-cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
9.2 全局组件和局部组件
- 调用Vue.component()注册组件时,组件的注册是全局的
- 注册的组件是挂载在某个实例中, 那么是一个局部组件
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈啊</p>
</div>
`
})
// 2.注册组件(全局组件, 意味着可以在多个Vue的实例下面使用)
// Vue.component('cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
// cpn使用组件时的标签名
//局部组件
cpn: cpnC
}
})
const app2 = new Vue({
el: '#app2'
})
</script>
</body>
9.3 父组件和子组件
组件和组件之间存在层级关系
<body>
<div id="app">
<cpn2></cpn2>
<!--<cpn1></cpn1>-->
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵呵</p>
<cpn1></cpn1>
</div>
`,
//组件构造器里加入了components注册属性,作用cpn1可以在cpnC2里使用
components: {
cpn1: cpnC1
}
})
// root组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
})
</script>
</body>
9.4 组件的语法糖
省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
<body>
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.全局组件注册的语法糖
// 1.创建组件构造器
// const cpn1 = Vue.extend()
// 2.注册组件
Vue.component('cpn1', {
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.注册局部组件的语法糖
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
'cpn2': {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵</p>
</div>
`
}
}
})
</script>
</body>
9.5 组件模板的分离写法
- 将组件和html分离出来
- Vue提供了两种方案来定义HTML模块内容:
- 使用
<script>
标签 - 使用
<template>
标签
- 使用
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--1.script标签, 注意:类型必须是text/x-template-->
<!--<script type="text/x-template" id="cpn">-->
<!--<div>-->
<!--<h2>我是标题</h2>-->
<!--<p>我是内容,哈哈哈</p>-->
<!--</div>-->
<!--</script>-->
<!--2.template标签-->
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
9.6 组件中数据存放问题
- 组件是一个单独功能模块的封装:
- 这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
- 组件对象也有一个data属性(也可以有methods等属性)
- 只是这个data属性必须是一个函数,不是对象类型
- 而且这个函数返回一个对象,对象内部保存着数据
- 组件内部是不能直接访问Vue实例里的数据
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--1.script标签, 注意:类型必须是text/x-template-->
<!--<script type="text/x-template" id="cpn">-->
<!--<div>-->
<!--<h2>我是标题</h2>-->
<!--<p>我是内容,哈哈哈</p>-->
<!--</div>-->
<!--</script>-->
<!--2.template标签-->
<template id="cpn">
<div>
<h2>{{title}}</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
//函数类型,而非对象类型且必须返回一个对象实例
data() {
return {
title: 'abc'
}
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
// title: '我是标题'
}
})
</script>
</body>
9.7 组件中的data为什么是函数
- 首先,如果不是一个函数,Vue直接就会报错。
- 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
<body>
<!--组件实例对象-->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>当前计数: {{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册组件
const obj = {
counter: 0
}
Vue.component('cpn', {
template: '#cpn',
// data() {
// return {
// counter: 0
// }
// },
data() {
return obj
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
<script>
// const obj = {
// name: 'why',
// age: 18
// }
//
// function abc() {
// return obj
// }
//
// let obj1 = abc()
// let obj2 = abc()
// let obj3 = abc()
//
// obj1.name = 'kobe'
// console.log(obj2);
// console.log(obj3);
</script>
</body>
9.7 组件通信
子组件是不能引用父组件或者Vue实例的数据
但是可以又上层向下层传递数据来进行展示
- 组件请求数据步骤
- 向服务器请求,请求列表数据
- 最外层请求的数据,传到内组件
9.7.1 父组件向子组件传递数据
-
通过props向子组件传递数据
-
通过事件向父组件发送消息
-
Vue实例和子组件的通信和父组件和子组件的通信过程是一样的
-
props的值有两种方式:
- 方式一:字符串数组,数组中的字符串就是传递时的名称。
- 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
- 需要对props进行类型等验证时,就需要对象写法
- 验证都支持哪些数据类型呢?
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 当我们有自定义构造函数时,验证也支持自定义的类型
<body>
<div id="app">
<!--<cpn v-bind:cmovies="movies"></cpn>-->
<!--<cpn cmovies="movies" cmessage="message"></cpn>-->
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
// props: ['cmovies', 'cmessage'],
props: {
// 1.类型限制
// cmovies: Array,
// cmessage: String,
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String,
default: 'aaaaaaaa',
//必传值
required: true
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
},
data() {
return {}
},
methods: {
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
</script>
</body>
9.7.2 父传子(props中的驼峰标识)
使用驼峰标识的话,绑定时需要
)-
来进行绑定
子组件里有很多的mustcahe需要一个确切的根(即外层的东西,如
<body>
<div id="app">
<cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cInfo}}</h2>
<h2>{{childMyMessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
cInfo: {
type: Object,
default() {
return {}
}
},
childMyMessage: {
type: String,
default: ''
}
}
}
const app = new Vue({
el: '#app',
data: {
info: {
name: 'why',
age: 18,
height: 1.88
},
message: 'aaaaaa'
},
components: {
cpn
}
})
</script>
</body>
9.8 子传父(自定义事件)
- 当子组件需要向父组件传递数据时,就要用到自定义事件了。
- v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
- 自定义事件的流程:
- 在子组件中,通过$emit()来触发事件。
- 在父组件中,通过v-on来监听子组件事件。
//整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。
//需要将子组件中的counter,传给父组件的某个属性,比如total。
<body>
<!--父组件模板-->
<div id="app">
<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>
// 1.子组件
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 发射事件: 自定义事件
this.$emit('item-click', item)
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
</script>
</body>
9.9 父子组件通信案例(Day04 部分)
9.9.1 父子组件的双向绑定
<body>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<!--<input type="text" v-model="dnumber1">-->
<input type="text" :value="dnumber1" @input="num1Input">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<!--<input type="text" v-model="dnumber2">-->
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
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) {
// 1.将input中的value赋值到dnumber中
this.dnumber1 = event.target.value;
// 2.为了让父组件可以修改值, 发出一个事件
this.$emit('num1change', this.dnumber1)
// 3.同时修饰dnumber2的值
this.dnumber2 = this.dnumber1 * 100;
this.$emit('num2change', this.dnumber2);
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('num2change', this.dnumber2)
// 同时修饰dnumber2的值
this.dnumber1 = this.dnumber2 / 100;
this.$emit('num1change', this.dnumber1);
}
}
}
}
})
</script>
</body>
9.9.2 父子组件的双向绑定(watch实现)
<body>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number,
name: ''
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
watch: {
dnumber1(newValue) {
this.dnumber2 = newValue * 100;
this.$emit('num1change', newValue);
},
dnumber2(newValue) {
this.number1 = newValue / 100;
this.$emit('num2change', newValue);
}
}
}
}
})
</script>
</body>
9.10 父子组件的访问方式
9.10.1 父访问子 children-refs
-
父组件访问子组件:使用 c h i l d r e n 或 children或 children或refs
- this.$children是一个数组类型,它包含所有子组件对象。
-
子组件访问父组件:使用$parent
-
$children的缺陷:
- 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
- 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
- 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
-
$refs的使用:
- $refs和ref指令通常是一起使用的。
- 首先,我们通过ref给某一个子组件绑定一个特定的ID。
- 其次,通过
this.$refs.ID
就可以访问到该组件了。
<child-cpn1 ref="chi1d1"></child-cpn1>
<child-cpn2 ref="chi1d2"></chi1d-cpn2>
<button @click="showRefscpn">通过refs访问子组件</button>
showRefsCpn() {
console.1og(this. $refs.chi1d1.message);
console.log(this. $refs.chi1d2.message);
}
实例
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<my-cpn></my-cpn>
<y-cpn></y-cpn>
<cpn ref="aaa"></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() {
// 1.$children
// console.log(this.$children);
// for (let c of this.$children) {
// console.log(c.name);
// c.showMessage();
// }
// console.log(this.$children[3].name);
// 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb'
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
},
}
})
</script>
</body>
9.10.2 子访问父 parent-root
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是子组件</h2>
<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: {
btnClick() {
// 1.访问父组件$parent
// console.log(this.$parent);
// console.log(this.$parent.name);
// 2.访问根组件$root
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
}
}
})
</script>
</body>
十. 组件化高级
10.1 slot 插槽的基本使用
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内容到底展示什么。
- 移动开发中,几乎每个页面都有导航栏。
- 导航栏我们必然会封装成一个插件,比如nav-bar组件。
- 一旦有了这个组件,我们就可以在多个页面中复用了。
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
<body>
<!--
1.插槽的基本使用 <slot></slot>
2.插槽的默认值 <slot>button</slot>
3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
-->
<div id="app">
<cpn></cpn>
<cpn><span>哈哈哈</span></cpn>
<cpn><i>呵呵呵</i></cpn>
<cpn>
<i>呵呵呵</i>
<div>我是div元素</div>
<p>我是p元素</p>
</cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件, 哈哈哈</p>
<!-- 设置默认值-->
<slot><button>按钮</button></slot>
<!--<button>按钮</button>-->
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
10.2 slot 具名插槽的基本使用
- 给slot元素一个name属性即可
<slot name='myslot'></slot>
<body>
<div id="app">
<cpn><span slot="center">标题</span></cpn>
<cpn><button slot="left">返回</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><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>
10.3 编译的作用域
- 在使用变量时会看变量的模板在哪里,就在哪里查找。
- 父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
<body>
<div id="app">
<cpn v-show="isShow"></cpn><!--实例里的isShow-->
<cpn v-for="item in names"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容, 哈哈哈</p>
<button v-show="isShow">按钮</button>
</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>
10.4 作用域插槽的案例
父组件替换插槽的标签,但是内容由子组件来提供。
- 在父组件使用我们的子组件时,从子组件中拿到数据:
- 我们通过
<template slot-scope="slotProps">
获取到slotProps属性
<body>
<!-- 内容在子组件,希望父组件告诉我们如何展示
利用slot作用域插槽就可以了
-->
<div id="app">
<cpn></cpn>
<cpn>
<!--目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data"> - {{item}}</span>-->
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<!--目的是获取子组件中的pLanguages-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data">{{item}} * </span>-->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
<!--<cpn></cpn>-->
</div>
<template id="cpn">
<div>
<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>
Vue Day 04
十一. 前端模块化
10.1 为什么要用模块化-匿名函数
- 因为js代码和文件越来越多。
- 全局变量重复使用,造成bug等
- 使用匿名函数来解决方面的重名问题
- 模拟块级作用域,减少全局变量。
- 执行完匿名函数,存储在内存中相对应的变量会被销毁,从而节省内存。
- 在大型多人开发的项目中,使用块级作用域,会大大降低命名冲突的问题,从而避免产生灾难性的后果。
- 自此开发者再也不必担心搞乱全局作用域了。
(function(){
var flag = true;
})()
但是代码的复用性降低
–
但是我们不使用以下的模块化去使用代码
我们采用的是别人已经规范的模块进行代码
bbb.js
var moduleB = (function () {
var obj = {}
var name = '小红'
var flag = false
console.log(name);
obj.flag = flag
return obj
})()
aaa.js
var moduleA = (function () {
// 导出的对象
var obj = {}
// 小明
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20));
}
obj.flag = flag;
obj.sum = sum;
return obj
})()
mmm.js
;(function () {
// 1.想使用flag
if (moduleA.flag) {
console.log('小明是天才, 哈哈哈');
}
// 2.使用sum函数
console.log(moduleA.sum(40, 50));
})()
10.2 常见的模块化规范
- 常见的模块化规范:
- CommonJS、AMD、CMD,也有ES6的Modules
- CommonJS(了解)
- 模块化有两个核心:导出和导入
10.2.1 CommonJS
a.js
//小明
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20));
}
//必须有webpack低沉来解析这个代码
module.exports = {
flag: flag,
sum: sum
}
b.js
//导入
var aaa = require('./aaa.js')
var flag = aaa.flag;
var sum = aaa.sum;
//上面三步等于下面一步
var {flag, sum} = require('./aaa.js')
sum(20, 30)
10.2.2 ES6模块化-export/import
- import
- 通过*可以导入模块中所有的export变量
- 但是通常情况下我们需要给*起一个别名,方便后续的使用(代码如下)
// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";
if (flag) {
console.log('小明是天才, 哈哈哈');
console.log(sum(20, 30));
}
// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";
console.log(num1);
console.log(height);
// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";
console.log(mul(30, 50));
const p = new Person();
p.run()
// 4.导入 export default中的内容
import addr from "./aaa.js";
addr('你好啊');
// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.height);
- export
- 当在
index.html
里<script src="aaa.js" type="module"></script>
设置type="module"
则定义aaa.js为一个模块(闭包)。 - 想用其它模块的变量则可以使用
export
- 当在
- export default
- 某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名
aaa.js
var name = '小明'
var age = 18
var flag = true
function sum(num1, num2) {
return num1 + num2
}
if (flag) {
console.log(sum(20, 30));
}
// 1.导出方式一:
export {
flag, sum
}
// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88
// 3.导出函数/类
export function mul(num1, num2) {
return num1 * num2
}
export class Person {
run() {
console.log('在奔跑');
}
}
// 5.export default
// const address = '北京市'
// export {
// address
// }
// export const address = '北京市'
//只能有一个default*2*
// const address = '北京市'
// export default address
export default function (argument) {
console.log(argument);
}
bbb.js
import {sum} from './aaa.js'
var name = '小红'
var flag = false
console.log(sum(100, 200));
Vue Day 05
十一. Webpack
11.1 Webpack和grunt/gulp的对比
- grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
- webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
问:webpack node npm 的联系
11.2 Webpack安装相关知识
npm init
用于创建package.json
文件--save-dev
是开发时依赖,项目打包后不需要继续使用的(本地的webpack,局部的)- 为什么全局安装后,还需要局部安装呢?
- 在终端直接执行webpack命令,使用的全局安装的webpack
- 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
- 文件和文件夹解析:
- dist文件夹:用于存放之后打包的文件(distribution)
- src文件夹:用于存放我们写的源文件
- main.js:项目的入口文件。具体内容查看下面详情。
- mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。
- index.html:浏览器打开展示的首页html
- package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)
一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题,所以通常一个项目,都有自己局部的webpack。
- 在
package.json
中的script
中定义"build":"webpack"
就可以使用npm run build
相当于在终端使用webpack
npm run server
同理
- 在任何终端执行
webpack
都是全局的,但如果在package.json
中配置脚本script
则优先在本地找webpack
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
- 直接用webpack 则是命令
./node_module/.bin/webpack 名
来执行 - 安装本地固定版本生产依赖命令:
npm install webpack@版本号 --save-dev
11.3 Webpack中核心之一:loader
11.3.1 打包css、less、js、image等图片需要下载相应的loader和手动配置
- 在对图片处理中,options中添加上如下选项:
- img:文件要打包到的文件夹
- name:获取图片原来的名字,放在该位置
- hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
- ext:使用图片原来的扩展名
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
// 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
limit: 13000,
name: 'img/[name].[hash:8].[ext]'
},
}
]
},
11.3.2 ES6语法的处理
将ES6转化为ES5:因为很多浏览器不能识别ES6
- webpack中,我们直接使用babel对应的loader
npm 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']
}
}
}
]
}
11.4 使用webpack配置Vue
npm i vue --save
- 不加
-dev
是因为整个项目都需要用Vue,运行时也需要依赖,故不需要开发依赖 - 注:因为我们后续是在实际项目中也会使用vue的,所以并不是开发时依赖
- 不加
npm run build
出现Error
-
ERROR:[Vue warn]: You are using the runtime-only build of Vue where the template compiler is rotavailable. Either pre-compile the templates into render functions,or use the compiler-includc( found in <Root>)
-
原因:打包Vue会出现两个情况:
- 一、runtime-only->代码中,不可以又任何的template,挂载的app的vue实例
<div id = 'app'></div>
相当于template
- 二、runtime-complier->代码中可以又template,因为有compiler可以用于编译template。
解决方案:
修改webpack配置
- 一、runtime-only->代码中,不可以又任何的template,挂载的app的vue实例
-
resolve: {
//alias:别名的意思
alias: {
//指向一个具体的文件夹vue.esm.js报含runtime-complier
'vue$ ': 'vue/dist/vue.esm.js'
}
},
11.5 创建Vue时template和el的关系
1.新的形式
import Vue from 'vue'
//这个形式也可以,不用 const app =new Vue(...)
new Vue({
el: '#app',
data:{
message:'hello'
}
})
2.template
需求:
- 如果希望将data中的数据显示在界面中,就必须是修改index.html
- 如果自定义了的件,也必须修改index.html来使用组件
- 但是html模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?
- 使用template
–
- 使用template
- 在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容
- 这里,我们可以将html中的div元素中的{{message}}内容删掉,只保留一个基本的id为div的元素
- 但是如果我依然希望在其中显示{{message}}的内容,应该怎么处理呢?
- 我们可以再定义一个template属性,代码如下:
new Vue({
el: '#app' ,
template: '<div id="app">{message}</div>',
data: {
message: 'leng'
}
})
1.el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等,而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。
2.将template模板中的内容进行抽离,会分成三部分书写:template、script、style,结构变得非常清晰。
11.6 Vue的终极使用方案
- 方案一:
const App = {
template: `
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
message: 'Hello Webpack',
name: 'leng'
}
},
methods: {
btnClick() {
}
}
}
new Vue({
el: '#app',
template: '<App/>',
components: {
App
}
})
- 方案二:
创建app.js
export default {
template: `
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
message: 'Hello Webpack',
name: 'leng'
}
},
methods: {
btnClick() {
}
}
main.js
//引用vue文件下的app.js
import App from './vue/app.js'
- 但是模板和js代码并没有分离
改为以下代码
创建文件App.vue
<template>
<div>
<h2 class="title">{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
<Cpn/>
</div>
</template>
<script>
import Cpn from './Cpn'
export default {
name: "App",
components: {
Cpn
},
data() {
return {
message: 'Hello Webpack',
name: 'leng'
}
},
methods: {
btnClick() {
}
}
}
</script>
<!--可以定义样式-->
<style scoped>
.title {
color: green;
}
</style>
main.js
// import App from './vue/app'
import App from './vue/App.vue'
- 配置对应loader
npm install vue-loader vue-template-compiler --save-dev
//webpack.config.js
{
test: /\.vue$/,
use: ['vue-loader']
}
- 会出现ERROR:plugin缺少插件:在package.json中配置
"vue-loader":^13.0.0
到指定版本13.0.0 - package.json配置改变要重新加载:
npm install
- 组件化开发:
- 创建
Cpn.vue
- 创建
import Cpn from './Cpn.js'
- 如果想省略
.js
需要更改webpack.config.js的配置,如果使用脚手架就不用这种操作
- 如果想省略
resolve: {
// alias: 别名
//如果想省略`.js`需要更改webpack.config.js的配置
extensions: ['.js', '.css', '.vue'],
//配置路径与该内容点无关
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
Cpn.vue
<template>
<div>
<h2>我是cpn组件的标题</h2>
<p>我是cpn组件的内容, 哈哈哈</p>
<h2>{{name}}</h2>
</div>
</template>
<script>
export default {
name: "Cpn",
data() {
return {
name: 'CPN组件的name'
}
}
}
</script>
<style scoped>
</style>
App.vue
<template>
<div>
<h2 class="title">{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
<Cpn/>
</div>
</template>
<script>
import Cpn from './Cpn'
export default {
name: "App",
components: {
Cpn
},
data() {
return {
message: 'Hello Webpack',
name: 'leng'
}
},
methods: {
btnClick() {
}
}
}
</script>
<!--可以定义样式-->
<style scoped>
.title {
color: green;
}
</style>
SPA(simple page web application)单页面富应用
11.6 webpack-plugin
- loader和plugin区别
- loader主要用于转换某些类型的模块,它是一个转换器。
- plugin是插件,它是对webpack本身的扩展,是一个扩展器。
- plugin的使用过程:
- 步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
- 步骤二:在
webpack.config.js
中的plugins中配置插件。
–
- 添加版权协议
- 插件名字叫BannerPlugin,属于webpack自带的插件。
- 不用安装其它
const path = require( ' path')
const webpack = require( 'webpack')
module.exports = {
//...
plugins: [
new webpack.BannerPlugin('最终版权归***所有')
]
- 重新打包程序
- 打包html的plugin
- 自动生成一个index.html文件(可以指定模板来生成)
- 将打包的js文件,自动通过script标签插入到body中
npm install html-webpack-plugin --save-dev
(@3.2.0)- 配置文件
- 重新打包
- 使用插件,修改webpack.config.js文件中plugins部分的内容如下:
- 这里的template表示根据什么模板来生成index.html
- 另外,我们需要删除之前在output中添加的publicPath属性
- 否则插入的script标签中的src可能会有问题
plugins: [
new webpack.BannerPlugin('最终版权归***所有'),
new HtmlWebpackPlugin({
template: 'index.html'
}),
]
- js压缩的Plugin
- 在项目发布之前,我们必然需要对js等文件进行压缩处理
- 这里,我们就对打包的js文件进行压缩
- 我们使用一个第三方的插件
uglifyjs-webpack-plugin
,并且版本号指定1.1.1
,和CLI2
保持一致 npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
const path = require( ' path ')
const webpack = require( 'webpack ' )
const uglifyJsPlugin = require( 'uglifyjs-webpack-plugin ')
module.exports = {
plugins: [
new webpack.BannerPlugin('最终版权归***所有')
//开发阶段一般不需要丑化,运行阶段才需要
new uglifyJsPlugin()
]
}
- 搭建本地服务器
- 本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
npm install --save-dev webpack-dev-server@2.9.1
(对应脚手架2使用的版本)npm install --save-dev webpack-dev-server@3.10.3
(对应webpack@4.42.0使用的版本)
- devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
- contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
- port:端口号
- inline:页面实时刷新
- historyApiFallback:在SPA页面中,依赖HTML5的history模式
- 可以再配置另外一个scripts:
--open
参数表示直接打开浏览器- 在脚本中配置
"dev" : "webpack-dev-server --open"
- 在脚本中配置
- 终端:
webpack-dev-server
跑不起来,是因为server是局部安装的,这条命令是访问全局- 用
./node_modules/.bin/webpack-dev-server
- 也可以在脚本中配置
"dev":"webpack-dev-server"
- 用
plugins: [
new webpack.BannerPlugin('最终版权归aaa所有'),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new UglifyjsWebpackPlugin()
],
devServer: {
//用于服务的当前文件夹
contentBase: './dist',
inline: true
}
11.7 webpack配置文件分离
webpack.config.js
中有些配置是开发环境需要,有些配置是运行环境需要,所以我们可以进行配置分离,高效使用代码。- 创建好
base.config.js``dev.config.js``prod.config.js
文件后需要安装npm install webpack-merge --save-dev
将base.config.js
和prod.config.js
合并在一起- 报错下载
webpack-merge@4.1.5
- 报错下载
-
创建
base.config.js
文件- 将
webpack.config.js
中的文件全部复制到base.config.js
文件 - 将基本文件(公共文件)保留,注释掉运行环境所需要的东西
const path = require('path') const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, '../dist'), filename: 'bundle.js', // publicPath: 'dist/' }, module: { rules: [ { test: /\.css$/, // css-loader只负责将css文件进行加载 // style-loader负责将样式添加到DOM中 // 使用多个loader时, 是从右向左 use: [ 'style-loader', 'css-loader' ] }, { 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 }] }, { test: /\.(png|jpg|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { // 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式. // 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载. limit: 13000, name: 'img/[name].[hash:8].[ext]' }, } ] }, { test: /\.js$/, // exclude: 排除 // include: 包含 exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }, { test: /\.vue$/, use: ['vue-loader'] } ] }, resolve: { // alias: 别名 extensions: ['.js', '.css', '.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ new webpack.BannerPlugin('最终版权归aaa所有'), new HtmlWebpackPlugin({ template: 'index.html' }) ] }
- 将
-
创建
dev.config.js
文件- 开发模式 模块的文件
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin') const webpackMerge = require('webpack-merge') const baseConfig = require('./base.config') module.exports = webpackMerge(baseConfig, { plugins: [ new UglifyjsWebpackPlugin() ] })
-
创建
prod.config.js
文件- 将
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin') const webpackMerge = require('webpack-merge') //导入 const baseConfig = require('./base.config') //将base 和 prod进行了合并 module.exports = webpackMerge(baseConfig, { plugins: [ new UglifyjsWebpackPlugin() ] })
-
需要配置文件
- 创建好
base.config.js``dev.config.js``prod.config.js
文件后需要安装npm install webpack-merge --save-dev
将base.config.js
和prod.config.js
合并在一起。 - 配置代码
const webpackMerge = require('webpack-merge')
- 创建好
-
ERROR_01:
- 运行npm run build 报错
- 未找到配置文件
webpack.config.js
- 解决办法:配置脚本文件
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", //增添 --config ./文件路径 "build": "webpack --config ./build/prod.config.js", "dev": "webpack-dev-server --open --config ./build/dev.config.js" },
-
ERROR_02:
- 打包成功后没有打包到dist文件夹下,而在build
- 原因:
path:path.resolve(__dirname,'dist')
指的是当前所在目录的dist文件夹,base在build,故就在build文件夹中创建了打包文件dist文件 - 解决办法:
path:path.resolve(__dirname,'../dist')
指当前的上一层目录进行创建
-
以上内容,脚手架会自动配置
Vue Day 06
十二. Vue CLI - 脚手架
12.1 什么是Vue CLI-安装 Vue CLI
-
使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。
-
如果每个项目都要手动完成这些工作,那无以效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
-
什么是CLI?
- CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架.
- Vue CLI是一个官方发布 vue.js 项目脚手架
- 使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
-
使用CLI的前提
- 安装 NodeJS
- 安装NPM(是Node Package Manager)
- cnpm安装
由于国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。
你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
npm install -g cnpm --registry=https://registry.npm.taobao.org
这样就可以使用 cnpm 命令来安装模块了:cnpm install [name]也可以使用代理registry:
npm config set registry https://registry.npm.taobao.org
后可以直接使用npm- 检查 Node和npm版本:
node -v; npm -v;
- Webpack的全局安装:
npm install webpack -g
- 安装Vue脚手架
npm install -g @vue/cli
vue --version
检查是否安装成功- 注意:安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
- Vue CLI2初始化项目
vue init webpack my-project
- Vue CLI3初始化项目
vue create my-project
-
拉取 VueCLI 2.x 模板 (如果要用旧版本就需要npm以下内容)
npm install -g @vue/cli-init // `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同 vue init webpack my-project
-
Vue/cli安装不成功
C:\Users\12987\AppData\Roaming\npm-cache删除npm-cache
或者cmd:npm clean cache -force
然后重新安装npm
12.2 VueCLI2 初始化项目过程
-
vue init webpack 项目名称
=>vue init webpack vuecli2test
- vue全家桶:VueCore+Vue-router+Vuex
? Author Noerrorist <1298711060@qq.com>
默认选取git中的信息,可以在gitconfig中更改- ESLint:ES-Lint就是js代码进行限制:如果代码编写不规范,编译器就会报错
- 如果
? Use ESLint to lint your code? No
你选择为yes - Pick an ESLint preset:
Standard
Airbnb
none
- 如果
- unit test (单元测试)、No
Setup e2e tests with Nightwatch? (Y/n)
- e2e表示 end to end 即端到端测试
- Nightwatch
安装Nightwatch,是一个利用selenium或webdriver或phantomjs等进行自动化测试的框架
- 软件测试工程师
-
VueCLI2的文件的目录结构
- 读文件:build和config
- 先读package.json
"scripts": {--progress监听进度 // "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "build": "node build/build.js" },
- npm run build 执行的是哪个文件再去找。
- node是c++开发的,内含V8引擎所以执行解析代码快,直接跳过字节码转换到二进制代码
-
如何关掉ESlint:
- config->index.js->useESLint:false
12.3 runtime-compiler 和 runtime-only 的区别
- 如果在之后的开发中,依然使用template,就需要选择Runtime-Compiler
- 如果之后的开发中,使用的是.vue文件夹开发,那么可以选择Runtime-only
将runtime-compiler中的main.js改掉一样可以变成runtime-only
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
// const cpn = {
// template: '<div>{{message}}</div>',
// data() {
// return {
// message: '我是组件message'
// }
// }
// }
new Vue({
el: '#app',
render: function (createElement) {
// 1.普通用法: createElement('标签', {标签的属性}, [''])
//将el:'#app'给替换了,即也将index.html中的div替换成了h2标签
// return createElement('h2',
// {class: 'box'},
// ['Hello World', createElement('button', ['按钮'])])
// 2.传入组件对象:
return createElement(App)
}
})
// runtime-compiler(v1)
// template -> ast -> render -> vdom -> UI
// runtime-only(v2)(1.性能更高 2.下面的代码量更少)
// render -> vdom -> UI
12.4 npm run build 和 npm run dev 底层原理
- npm run build 底层原理
- npm run dev 底层原理
12.5 修改配置webpack.base.conf.js 起别名
resolve: {
extensions: ['.js ', '.vue ', '.json ' ],
alias: {
'@': resolve ( 'src '),
'pages' : resolve( 'src/pages '),' common' : resolve( 'src / common '),
'components': resolve( 'src/ components ' ),
'network ': resolve( 'src/network ')
}
},
12.6 VueCLI3 初始化项目过程
-
vue-cli 3 与 2 版本有很大区别
- vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
- vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
- vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
- 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
-
? Where do you prefer placing config for Babel, ESLint,etc.? (Use arrow keys).
- In dedicated config files //独立的配置文件(建议)
- In package.json //放在package.json
-
? Save this as a preset for future projects? (y/N) //是否新建一个选项作为工程
- 如何删除:更改目录C:\Users\12987下的
.vuerc
文件部分内容注释掉
{ { "useTaobaoRegistry": false, "presets": { // "leng": { // "useConfigFiles": true, // "plugins": { // "@vue/cli-plugin-babel": {} // }, // "vueVersion": "3" } } }
- 如何删除:更改目录C:\Users\12987下的
Git 存储库“d:\Desktop\My Files\RecentlyStudy\VUE\Vue.js\demo\Day06\ClassCode\LearnVuejs06\testvuecli3”中存在大量活动更改,将仅启用部分 Git 功能。 是否要将“node_modules”添加到 .gitignore?
package-lock.json: For performance reasons, document symbols have been limited to 5000 items.
Use setting ‘json.maxItemsComputed’ to configure the limit.
12.6.1 配置文件的查看和修改
- 隐藏的webpack配置在哪
- 更改配置:
vue.config.js
- 文件被标绿原因:修改了文件 且没有 git
- 解决方案:
- git status
- git add .
- git status
- git commit -m ‘修改配置文件’
- 启动服务器配置UI:
vue ui
12.7 箭头函数的使用和
箭头函数的基本使用
<body>
<script>
// 箭头函数: 也是一种定义函数的方式
// 1.定义函数的方式: function
const aaa = function () {
}
// 2.对象字面量中定义函数
const obj = {
bbb() {
}
}
// 3.ES6中的箭头函数
// const ccc = (参数列表) => {
//
// }
const ccc = () => {
}
</script>
</body>
箭头函数的返回值
<body>
<script>
// 1.参数问题:
// 1.1.放入两个参数
const sum = (num1, num2) => {
return num1 + num2
}
// 1.2.放入一个参数
const power = num => {
return num * num
}
// 2.函数中
// 2.1.函数代码块中有多行代码时
const test = () => {
// 1.打印Hello World
console.log('Hello World');
// 2.打印Hello Vuejs
console.log('Hello Vuejs');
}
// 2.2.函数代码块中只有一行代码
// const mul = (num1, num2) => {
// return num1 + num2
// }
const mul = (num1, num2) => num1 * num2
console.log(mul(20, 30));
// const demo = () => {
// console.log('Hello Demo');
// }
const demo = () => console.log('Hello Demo')
console.log(demo()); // undefined
</script>
</body>
箭头函数this的指向
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- this对象的指向是可变的,但在箭头函数中,它是固定的
//箭头函数中的this总时指向函数定义生效时所在的对象({id:52})
//console输出函数为52
function foo(){
setTimeout(()=>{
console.log('id',this,id);
},100)
}
var id=21;
foo.call({id:52});
箭头函数中this的使用
<body>
<script>
// 什么时候使用箭头
// setTimeout(function () {
// console.log(this);
// }, 1000)
//
// setTimeout(() => {
// console.log(this);
// }, 1000)
//以上打印出都是window
// 问题: 箭头函数中的this是如何查找?
// 答案: 向外层作用域中, 一层层查找this, 直到有this的定义.
// const obj = {
// aaa() {
// setTimeout(function () {
// console.log(this); // window
// })
//
// setTimeout(() => {
// console.log(this); // obj对象
// })
// }
// }
//
// obj.aaa()
const obj = {
aaa() {
setTimeout(function () {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // window
})
})
setTimeout(() => {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // obj
})
})
}
}
obj.aaa()
</script>
</body>