# 起步
# hello Vue.js
<div id="app">
<h2>{{message}}</h2>
<h1>{{name}}</h1>
</div>
<div>{{message}}</div>
<!-- 这里不绑定元素id 显示不出来 -->
2
3
4
5
6
7
<script src="/js/vue.js"></script>
// let(变量)/const(常量)
// 编程范式: 声明式编程
const app = new Vue({
el: '#app', // 用于挂载要管理的元素
data: { // 定义数据
message: 'hello js!',
name: 'love you'
}
})
</script>
2
3
4
5
6
7
8
9
10
11
代码是可以做到响应式的
# 命令式编程
原生js的做法
1.创建div元素,设置id属性
2.定义一个变量叫message
3.将message变量放在前面的div元素中显示
4.修改message的数据: 今天天气不错!
5.将修改后的数据再次替换到div元素
# Vue列表展示
<ul>
<!-- v-for遍历 -->
<li v-for="item in movies">{{item}}</li>
</ul>
2
3
4
const app = new Vue({
el: '#app',
data: {
movies: ['星际穿越', '大话西游', '少年派', '盗梦空间']
}
})
2
3
4
5
6
# 计数器小案例
<div id="app">
<h2>当前计数: {{counter}}</h2>
<!--<button v-on:click="counter++">+</button>-->
<!--<button v-on:click="counter--;">-</button>-->
<button v-on:click="add">+</button>
<button v-on:click="sub">-</button>
<!--下面是语法糖写法-->
<!--<button @click="sub">-</button>-->
</div>
2
3
4
5
6
7
8
9
const obj = {
counter: 0,
message: 'abc'
}
const app = new Vue({
el: '#app',
data: obj,
methods: {
add: function () {
console.log('add被执行');
this.counter++
},
sub: function () {
console.log('sub被执行');
this.counter--
}
},
beforeCreate: function () {
},
created: function () {
console.log('created');
},
mounted: function () {
console.log('mounted');
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# MVVM
Model
View
View Model
`Model层(数据层)`
数据可能是固定的死数据,也肯呢个是来自服务器,从网络上请求下来的数据。在计数器的案例中,就是后面抽取出来的obj,当然,里面的数据可能没有这么简单。
`View层(视图层)`
在前端开发中,通常就是DOM层。主要的作用是给用户展示各种信息。
`VueModel层(视图模型层)`
视图模型层是View和Model沟通的桥梁。
一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中
另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。
计数器的MVVM
# Vue的生命周期
# 插值操作
# Mustache语法
Mustache语法
两个花括号, 里面写message
花括号message花括号, Vue.js
mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式
花括号firstName + lastName花括号
花括号firstName + ' ' + lastName花括号
花括号firstName花括号 花括号lastName花括号
花括号counter * 2花括号
# v-once
禁止实时改变数据
<div id="app">
<h2>{{message}}</h2>
<h2 v-once>{{message}}</h2>
</div>
2
3
4
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
2
3
4
5
6
# v-html
将url链接插入到页面中
<div id="app">
<h2>{{url}}</h2>
<h2 v-html="url"></h2>
</div>
2
3
4
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
url: '<a href="http://www.baidu.com">百度一下</a>'
}
})
2
3
4
5
6
7
# v-text
将文本插入到页面中
<h2>{{message}}, Vue</h2>
<h2 v-text="message">, Vue</h2>
<!--渲染 你好啊,覆盖掉了上面的你好啊,Vue,不常用-->
2
3
data: {
message: '你好啊'
}
2
3
# v-pre
阻止渲染mustache语法
<h2 v-pre>{{message}}</h2>
<!--渲染 {{message}}-->
2
# v-cloak
在vue解析之前, div中有一个属性v-cloak 在vue解析之后, div中没有一个属性v-cloak
# 动态绑定属性
# v-bind基本使用
<div id="app">
<!-- 错误的做法: 这里不可以使用mustache语法-->
<!--<img src="{{imgURL}}" alt="">-->
<!-- 正确的做法: 使用v-bind指令 -->
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHref">百度一下</a>
<!--语法糖的写法-->
<img :src="imgURL" alt="">
<a :href="aHref">百度一下</a>
</div>
2
3
4
5
6
7
8
9
10
11
12
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'
}
2
3
4
5
# v-bind绑定class(对象)
.active {
color: red;
}
2
3
<div id="app">
<!--原始写法-->
<!--<h2 class="active">{{message}}</h2>-->
<!--<h2 :class="active">{{message}}</h2>-->
<!--v-bind写法-->
<!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>-->
<!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>-->
<!--可以控制类名的显示和隐藏-->
<!--v-bind最终写法-->
<h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>
<!--把isActive和isLine写在这里因为下面定义了变量-->
<!--当然不止定义变量可以加class类,也可以手动些class,现在h2就有三个class-->
<h2 class="title" v-bind:class="getClasses()">{{message}}</h2>
<!--如果后面绑定的属性太长可以封装成一个函数写在methods里面,但是class要写函数的名字()-->
<button v-on:click="btnClick">按钮</button>
</div>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data: {
message: '你好啊',
isActive: true,
isLine: true
},
methods: {
btnClick: function () {
this.isActive = !this.isActive
},
getClasses: function () {
return {active: this.isActive, line: this.isLine}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# v-bind绑定class(数组)
<h2 class="title" :class="[active, line]">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
2
data: {
message: '你好啊',
active: 'aaaaaa',
line: 'bbbbbbb'
},
methods: {
getClasses: function () {
return [this.active, this.line]
}
}
2
3
4
5
6
7
8
9
10
# v-bind绑定style(对象)
<!--<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>
2
3
4
5
6
7
8
9
10
data: {
message: '你好啊',
finalSize: 100,
finalColor: 'red',
},
methods: {
getStyles: function () {
return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor}
}
}
2
3
4
5
6
7
8
9
10
# v-bind绑定style(数组)
<h2 :style="[baseStyle, baseStyle1]">{{message}}</h2>
data: {
message: '你好啊',
baseStyle: {backgroundColor: 'red'},
baseStyle1: {fontSize: '100px'},
}
2
3
4
5
# 计算属性
# 基本操作
<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<!--methods里面的写法-->
<h2>{{getFullName()}}</h2>
<!--computed里面的写法-->
<h2>{{fullName}}</h2>
2
3
4
5
6
7
8
data: {
firstName: 'Lebron',
lastName: 'James'
},
// computed: 计算属性()
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
计算属性和方法属性都是可以进行渲染的
# 复杂操作
<h2>总价格: {{totalPrice}}</h2>
<!--页面渲染总价格:409 改变books里面数据页面会动态改变-->
2
data: {
books: [
{id: 110, name: 'TypeScript', price: 119},
{id: 111, name: 'python', price: 105},
{id: 112, name: 'c++', price: 98},
{id: 113, name: 'java', price: 87},
]
},
computed: {
totalPrice: function () {
let result = 0
for (let i=0; i < this.books.length; i++) {
result += this.books[i].price
}
return result
2
3
4
5
6
7
8
9
10
11
12
13
14
15
计算属性绑定和methods属性绑定的区别就是计算属性在{{}}里面直接写计算属性的方法名字,methods属性需要写方法名字+(),下面有说到为什么计算属性只写一个方法名字.
# setter和getter
<h2>{{fullName}}</h2>
data: {
firstName: 'Kobe',
lastName: 'Bryant'
},
computed: {
//之前的写法
// fullName: function () {
// return this.firstName + ' ' + this.lastName
// }
// 计算属性一般是没有set方法, 是只读属性.
//使用的时候一般使用的get方法,所以直接忽略了get:function(){},直接写属性名:function(){}
//完整的计算属性
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
可以手动设置set属性
# 计算属性和methods属性区别
计算属性和methods属性区别
计算属性内部有缓存,可以提高效率
methods属性里面的方法,如果dom页面写了多个重复结构,methods属性里面的方法会调用多次
相反,计算属性里面的方法只会被调用一次,降低了损耗.如果有方法和函数要写,尽量写到计算属性里面,举个例子,如果有for循环的需求,methods属性会调用多次,计算属性只会调用一次.
# 事件监听
# v-on基本使用
<h2>{{counter}}</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>
2
3
4
5
6
7
8
9
10
data: {
counter: 0
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
2
3
4
5
6
7
8
9
10
11
# v-on的参数
<!--1.事件调用的方法没有参数-->
<button @click="btn1Click()">按钮1</button>
<button @click="btn1Click">按钮1</button>
<!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
<button @click="btn2Click">按钮2</button>
<!--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->
<!-- 在调用方式, 如何手动的获取到浏览器参数的event对象: $event-->
<button @click="btn3Click(abc, $event)">按钮3</button>
2
3
4
5
6
7
8
9
10
data: {
message: '你好啊',
abc: 123
},
methods: {
btn1Click() {
console.log("btn1Click");
},
btn2Click(event) {
console.log('--------', event);
},
btn3Click(abc, event) {
console.log('++++++++', abc, event);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# v-on的修饰符
在某些情况下,我们拿到event的目的可能是进行一些事件处理。 Vue提供了修饰符来帮助我们方便的处理一些事件
v-on的修饰符
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
.native - 监听组件根元素的原生事件。
.once - 只触发一次回调。
# 条件判断
# v-if基本使用
<!-- v-if一般后面写布尔值 -->
<h2 v-if="isShow">
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
{{message}}
</h2>
2
3
4
5
6
7
8
9
data: {
message: '你好啊',
isShow: true
}
2
3
4
# v-if 和 v-else
<h2 v-if="isShow">
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
{{message}}
</h2>
<h1 v-else>isShow为false时, 显示我</h1>
</div>
2
3
4
5
6
7
8
9
10
data: {
message: '你好啊',
isShow: true
}
2
3
4
# v-else-if
<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>
2
3
4
5
6
data: {
score: 99
//只需要改变score值页面就能渲染分数标准
},
computed: {
result() {
let showMessage = '';
if (this.score >= 90) {
showMessage = '优秀'
} else if (this.score >= 80) {
showMessage = '良好'
}
return showMessage
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 登录小案例
<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>
2
3
4
5
6
7
8
9
10
11
data: {
isUser: true
}
2
3
用户点击切换类型就可以从账号登录切换到邮箱登录,可是为什么要给input输入框里面写key呢?其实就是如果在账号登录情况下输入了账号,点击切换类型到邮箱.那么input输入框里面的数据不会清空.key就是可以作为唯一的标识来使用
# v-show
v-show与v-if的区别
<!--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>
2
3
4
5
当需要在显示与隐藏之间切换很频繁时,使用v-show
当只有一次切换时,通过使用v-if
data: {
message: '你好啊',
isShow: true
}
2
3
4
# 循环遍历
# v-for(数组)
<!--1.在遍历的过程中,没有使用索引值(下标值)-->
<ul>
<li v-for="item in names">{{item}}</li>
</ul>
<!--2.在遍历的过程中, 获取索引值-->
<ul>
<li v-for="(item, index) in names">
{{index+1}}.{{item}}
</li>
</ul>
2
3
4
5
6
7
8
9
10
11
data: {
names: ['why', 'kobe', 'james', 'curry']
}
2
3
# v-for(对象)
<!--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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data: {
info: {
name: 'why',
age: 18,
height: 1.88
}
}
2
3
4
5
6
7
# key
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<!--遍历出来ABCDE-->
<!-- key要和关键标识一样 要一一对应 -->
2
3
4
5
data: {
letters: ['A', 'B', 'C', 'D', 'E']
}
})
2
3
4
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。 为什么需要这个key属性呢,这个其实和Vue的虚拟DOM的Diff算法有关系。
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点,我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。即把C更新成F,D更新成C,E更新成D,最后再插入E,很没有效率,所以我们需要使用key来给每个节点做一个唯一标识.Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
key的作用主要是为了高效的更新虚拟DOM
# 数组的响应式方法
//push方法(同unshift一样可以添加多个)
this.push('aaa')
this.push('aaaa', 'bbbb', 'cccc')
//同理还有pop,shift,onshift,splice,sort,reverse
//都可以进行响应式操作dom
//还有Vue中的一个方法set也可以实现响应式
//set(要修改的对象, 索引值, 修改后的值)
Vue.set(this, 0, 'a')//往数组最前面插入a
//通过索引值修改数组中的元素 不是响应式
this[0] = 'aa';
//可以在数组的第0位插入aa,但是页面不是响应式更新
2
3
4
5
6
7
8
9
10
11
12
13
# 小练习
点击数组的每一位,当点击到哪一位哪一位的数据颜色变红
.active {
color: red;
}
2
3
<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>
2
3
4
5
6
7
8
9
10
11
12
data: {
movies: ['海王', '海贼王', '加勒比海盗', '海尔兄弟'],
currentIndex: 0//谁显示红色
},
methods: {
liClick(index) {
this.currentIndex = index
}
}
2
3
4
5
6
7
8
9
# 购物车案例
<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>
<h2>总价格: {{totalPrice | showPrice}}</h2>
</div>
<h2 v-else>购物车为空</h2>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
for (let i = 0; i < this.books.length; i++) {
totalPrice += this.books[i].price * this.books[i].count
}
return totalPrice
// for (let i in/of this.books)
// reduce
}
},
filters: {
showPrice(price) {
return '¥' + price.toFixed(2)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 高阶函数(数组方法)
# 编程范式
编程范式分为命令式编程和声明式编程
编程范式: 面向对象编程(第一公民:对象) 函数式编程(第一公民:函数)
# 数组小例子
1.取出所有小于100的数字
2.将所有小于100的数字进行转化: 全部*2
3.将所有*2后的数字相加,得到最终的结果
普通写法
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:将所有new2Nums数字相加,得到最终的结果
let total = 0
for (let n of new2Nums) {
total += n
}
console.log(total);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
高阶写法
//1.filter函数的使用(过滤)
const nums = [10, 20, 111, 222, 444, 40, 50];
let newNums = nums.filter(function (n) {
return n < 100
})
// console.log(newNums);10, 20, 40, 50
//filter中的回调函数有一个要求
//必须返回一个boolean值
//当返回true时, 函数内部会自动将这次回调的n加入到新的数组中
//当返回false时, 函数内部会过滤掉这次的n
// 2.map函数的使用
let new2Nums = newNums.map(function (n) {
return n * 2
})
console.log(new2Nums);// 20, 40, 80, 100
// 3.reduce函数的使用
// reduce作用:对数组中所有的内容进行汇总(相加)
// reduce第一个参数.表示上次回调的数值,或者初始值
//第二个参数表示正在处理的数组元素
let total = new2Nums.reduce(function (preValue, n) {
return preValue + n //0+20
}, 0)
console.log(total);//240
//第一次: preValue 0 n 20
//第二次: preValue 20 n 40
//第三次: preValue 60 n 80
//第四次: preValue 140 n 100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
简写(连写)
const nums = [10, 20, 111, 222, 444, 40, 50]
let total = nums.filter(function (n) {
return n < 100//10,20,40,50
}).map(function (n) {
return n * 2 //20,40,80,100
}).reduce(function (prevValue, n) {
return prevValue + n
}, 0)
console.log(total);
2
3
4
5
6
7
8
9
箭头函数写法
let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n);
console.log(total);
2
# v-model
# v-model基本使用
<input type="text" v-model="message">
<!-- 在v-model里面直接添加变量 绑定 -->
{{message}}
2
3
data: {
message: '你好啊'
}
2
3
Vue中使用v-model
指令来实现表单元素
和数据的双向绑定
。当我们在输入框输入内容时,因为input中的v-model
绑定了message,所以会实时
将输入的内容传递给message,message发生改变。
当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变.v-model也用于textarea
元素
# v-model原理
<!-- v-model其实就是两个属性的拼接 value 和事件对象 -->
<!--v-model => v-bind:value v-on:input-->
<!--<input type="text" :value="message" @input="valueChange">-->
<!--直接写在行间也可以-->
<input type="text" :value="message" @input="message = $event.target.value">
<h2>{{message}}</h2>
2
3
4
5
6
7
8
v-model本质上包含了两个操作. 1.v-bind绑定一个value属性 2.v-on指令给当前元素绑定input事件
data: {
message: '你好啊'
},
methods: {
valueChange(event) {
this.message = event.target.value;
//获取实时的input的value值
}
}
2
3
4
5
6
7
8
9
# v-model(radio)
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">男
</label>
<!-- 两个都加上name=sex才能互斥(单选),
但是两个v-model也可以实现互斥,所以选择删除掉name属性 -->
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
</label>
<h2>您选择的性别是: {{sex}}</h2>
2
3
4
5
6
7
8
9
data: {
message: '你好啊',
sex: '女'
}
2
3
4
# v-model(checkbox)
<!--checkbox单选框-->
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<h2>您选择的是: {{isAgree}}</h2>
<button :disabled="!isAgree">下一步</button>
<!--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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data: {
message: '你好啊',
isAgree: false, // 单选框
hobbies: [], // 多选框,
originHobbies: ['篮球', '足球', '乒乓球', '羽毛球',
'台球', '高尔夫球']
}
2
3
4
5
6
7
# v-model(select)
<!--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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data: {
message: '你好啊',
fruit: '香蕉',
fruits: []
}
2
3
4
5
# 值绑定
就是动态的给value赋值,前面的表单案例value中的值,都是在定义input的时候直接给定的,真实开发中,这些input的值可能是从网络获取或定义在data中的.可以通过v-bind:value动态的给value绑定值,值绑定顾名思义就是v-bind在input中的应用.
# v-model修饰符
<!-- 不会调用的那么频繁 -->
<!--1.修饰符: lazy-->
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>
<!-- 必须输入数字 默认情况下是string类型-->
<!--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>
2
3
4
5
6
7
8
9
10
11
12
13
14
data: {
message: '你好啊',
age: 0,
name: ''
}
})
var age = 0
age = '1111'
age = '222'
2
3
4
5
6
7
8
9
10
# 组件化开发
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
我们将一个完整的页面分成很多个组件。 每个组件都用于实现页面的一个功能块。 而每一个组件又可以进行细分。
# Vue的组件化思想
# 注册组件
组件的使用分成三个步骤:1.创建组件构造器2.注册组件3.使用组件。
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈哈</p>
<p>我是内容, 呵呵呵呵</p>
</div>`
})
// 2.注册组件 (组件的标签名,组件构造器)
Vue.component('my-cpn', cpnC)
el: '#app',
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--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>
<my-cpn></my-cpn>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 注册步骤
注册组件步骤解析
1.Vue.extend() 调用Vue.extend()创建的是一个组件构造器。 通常在创建组件构造器时,传入template代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。
2.Vue.component() 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
3.组件必须挂载在某个Vue实例下,否则它不会生效(这里指写在页面上的app标签外)
# 全局组件局部组件
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
2
3
4
5
6
7
8
9
// 在开发中一般只用到一个Vue实例,一般只用全局组件
// 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'
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
当我们通过调用Vue.component()注册组件时,组件的注册是全局的
这意味着该组件可以在任意Vue示例下使用。
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
# 父组件子组件
// 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: cpnC1
}
})
// root 根组件 爷爷组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<cpn2></cpn2>
<!--<cpn1></cpn1>-->
<!-- cpn1会报错 -->
2
3
# 注册组件语法糖
// 1.旧的创建组件构造器
//const cpn1 = Vue.extend()
// 2.新的注册组件(全局组件)
//主要是省去了调用Vue.extend()的步骤
//可以直接使用一个对象来代替
//第一个参数组件名,第二个组件模板
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>
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<cpn1></cpn1>
<cpn2></cpn2>
<!-- 全局组件和局部组件都在Vue实例上都可以在页面显示-->
2
3
# 组件模板分离
1.script标签,注意:类型必须是text/x-template
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</script>
2
3
4
5
6
2.template标签
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
2
3
4
5
6
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'//绑定模板id
})
2
3
4
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<!--多次调用组件名也可以渲染多次-->
2
3
4
# 组件中的数据存放
组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也应该有属性自己的数据data,在组件中,写{{}}语法不能访问Vue实例对象的data
即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。所以组件内也有一个自己的data(此处为函数)
Vue.component('cpn', {
template: '#cpn',
data() {
return {
title: 'abc'
}
}
})
2
3
4
5
6
7
8
<template id="cpn">
<div>
<h2>{{title}}</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
2
3
4
5
6
渲染多次cpn(组件),此时的h2 title已经变成了指定的abc
# 为什么data是函数
const obj = {
counter: 0
}
Vue.component('cpn', {
template: '#cpn',
// data() {
// return {
// counter: 0
// }
// },
// 相互影响/如果是函数就互不影响
data() {
return obj;
// return counter++;
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template id="cpn">
<div>
<h2>当前计数: {{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
2
3
4
5
6
7
8
9
10
11
12
13
首先,如果不是一个函数,Vue直接就会报错。 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
因为对象是一个引用地址,而函数有自己作用域(可以这么理解),此例中模板的data函数的会创建三个相同的函数,各自执行自己的作用(域),对象的话都执行的同一个地址
# 组件的父传子
// 父传子: props
//子组件
const cpn = {
template: '#cpn',
// props: ['cmovies', 'cmessage'],
// covies就相当于一个变量
props: {
// 1.类型限制
// cmovies: Array,
// cmessage: String,
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String,
default: 'aaaaaaaa',
//在没传的情况下cmessage的情况下aaa
required: true
//用cpn时cmessage是必传的,不传会报错
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
},
data() {
return {}
},
methods: {}
}
//父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!--父组件-->
<div id="app">
<!--<cpn v-bind:cmovies="movies"></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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 组件中的props
子组件的props
props: {
cInfo: {
type: Object,
default() {
return {}
}
},
childMyMessage: {
type: String,
default: ''
}
}
2
3
4
5
6
7
8
9
10
11
12
父组件的data
data: {
info: {
name: 'why',
age: 18,
height: 1.88
},
message: 'aaaaaa'
},
2
3
4
5
6
7
8
在父组件里面的子组件,绑定的时候会不支持驼峰式写法,要用-来拼接 但是在脚手架里面是支持直接写子组件中的props属性里面的属性的
<!-- v-bind不支持驼峰 -->
<cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
2
除了数组之外,props也可以使用对象,当需要对props进行类型等验证时,就需要对象写法,支持验证的数据类型有String
Number``Boolean``Array``Object``Date``Function``Symbol
# 组件的子传父
// 1.子组件
const cpn = {
template: '#cpn',
data() {
return {
// 分类
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 发射事件: 自定义事件
// 准备往父组件传入item 发送一个事件,父组件监听item-click事件
// 父组件处理item-click事件(处理事件写在父组件内)
this.$emit('item-click', item)
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!--父组件模板-->
<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>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 父子组件通信案例
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);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
父组件内的子组件
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
<!-- 如果组件标签中间没有内容可以写成单标签 -->
</div>
2
3
4
5
6
7
子组件
<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>
2
3
4
5
6
7
8
9
10
11
12
也可以用watch实现监听,替换掉上部代码的methods里面的所有, watch跟data(){},template,props,methods一样,都是组件对象的option.
watch: {
dnumber1(newValue) {
this.dnumber2 = newValue * 100;
this.$emit('num1change', newValue);
},
dnumber2(newValue) {
this.number1 = newValue / 100;
this.$emit('num2change', newValue);
}
}
2
3
4
5
6
7
8
9
10
# 父访问子children,refs
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'
// 在组件上面添加ref属性,就可以通过$refs来调用该属性
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
children的缺陷和refs的区别
通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
$refs和ref指令通常是一起使用的。 首先,我们通过ref给某一个子组件绑定一个特定的ID。 其次,通过this.$refs.ID就可以访问到该组件了
# 子访问父parent,root
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);
//访问Vue实例
console.log(this.$root.message);
//访问Vue实例上的message
}
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--爷爷组件-->
<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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 组件化高级
# slot 插槽
移动开发中,几乎每个页面都有导航栏。
导航栏我们必然会封装成一个插件,比如nav-bar组件。
一旦有了这个组件,我们就可以在多个页面中复用了。如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。
但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
是搜索框,还是文字,还是菜单。由调用者自己来决定。
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
2
3
4
5
6
7
8
9
10
11
1.插槽的基本使用
2.插槽的默认值 button
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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 具名插槽的使用
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
2
3
4
5
6
7
8
9
10
11
<!-- 导航类 -->
<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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 组件编译作用域
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
//父组件内可以展示
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
//子组件内不能展示
}
}
},
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<cpn v-show="isShow"></cpn>
<!--子组件在父组件内调用,子组件原本不能展示,在此可以展示
因为子组件在父组件的域-->
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容, 哈哈哈</p>
<button v-show="isShow">按钮</button>
<!--不能展示button,因为在自己的域已经写死了button false-->
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 插槽案例
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
}
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<!--列表展示-->
<cpn></cpn>
<!-- 不想以列表展示 -->
<cpn>
<!--目的是获取子组件中的pLanguages-->
<!-- 固定写法 slot引用下面模板的插槽 .data就是传过来的data,data就是pleanguages-->
<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>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 前端模块化
# 实现雏形
项目组长给小红和小明分配了一个任务,小明和小红都在写代码.小明写了aaa.js,小红写了bbb.js, 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
})()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//小红bbb.js模块化代码
var moduleB = (function () {
var obj = {}
var name = '小红'
var flag = false
console.log(name);
obj.flag = flag
return obj
})()
2
3
4
5
6
7
8
9
10
11
12
13
之所以会这样写因为如果有一天,如果小明再写了一个mmm.js文件,小红也写了一个nnn.js文件,他们有都有两个js文件.
结果项目组长想拿来小明和小红的代码,自己写一个main.js文件将他们进行归纳展示,结果发现出现了文件多导致的变量命名冲突的问题. 所以小明和小红将他的aaa/bbb.js文件通过模块化的方式去写,而他们的第二个js文件想使用第一个js文件中的变量或者函数,小明的js文件可以这样写
;(function () {
// 1.想使用flag
if (moduleA.flag) {
console.log('小明是天才, 哈哈哈');
}
// 2.使用sum函数
console.log(moduleA.sum(40, 50));
})()
2
3
4
5
6
7
8
9
小红的可以这样写
(function () {
console.log(moduleB.flag);
})()
2
3
这样使用匿名函数一来是不会存在命名冲突的问题,二来是通过模块化的方式,将自己的第一个js文件封装成一个moduleA和moduleB对象,把变量和函数都赋给obj.xxx返回,用moduleA和moduleB来接收,在自己的第二个js文件中使用moduleA/B.变量/函数
来进行调用,从而实现模块化的思想,如果项目组长想归纳代码在主页index展示,就可以在主要这样引入
<script src="main.js"></script>//归纳的代码
<script src="aaa.js"></script>//小明1
<script src="mmm.js"></script>//小明2
<script src="bbb.js"></script>//小红1
<script src="nnn.js"></script>//小红2
2
3
4
5
文件目录结构
按照引入顺序打印
# CommonJS
常见的模块化规范: CommonJS、AMD、CMD,也有ES6的Modules,AMD和CMD不常用,常用的就Conmmon.JS和ES6的模块化.
使用Common.JS
导出module.exports,导入require
//在小明的aaa.js里面使用
//commonjs写法 webpack会自动编译
module.exports = {
flag: flag,
sum: sum
//将小明的flag变量和sum函数暴露出来
}
2
3
4
5
6
7
//在小明的第二个js文件中使用
var aaa = require('./aaa.js')//导入第一个js文件
var flag = aaa.flag;//将aaa的flag变量赋值给第二个js的flag
var sum = aaa.sum;//函数
//对象解构写法
//var {flag, sum} = require('./aaa.js')
sum(20, 30)//调用sum函数
2
3
4
5
6
7
8
9
# ES6的模块化
跟上面的案例类似,小明写了aaa.js文件,小红写了bbb.js文件,小明最后还写了自己的第二个js文件,最终小红的bbb.js文件想使用aaa.js文件中的变量函数等,小红的bbb.js也想使用自己写的aaa.js文件的变量方法...
小明的第一个js文件(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('在奔跑');
}
}
// 另一个js文件中不想用该js中定义的函数变量名,
//可以进行自定义
// 5.export default
// const address = '北京市'
// export {
// address
// }
// export const address = '北京市'
// const address = '北京市'
//
export default
address
// 开发中只能使用一个 export default
export default function (argument) {
console.log(argument);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
此时小红想用小明aaa.js里面的sum函数,运用上面讲到的方法(模块化雏形,commonJS),会显得特别麻烦,所以衍生了一种新的写法(ES6模块化)
import {sum} from './aaa.js'
var name = '小红'
var flag = false
console.log(sum(100, 200));
2
3
4
5
6
此时小明写的第二个js文件(mmm.js)也需要用到第一个文件中的方法
// 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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
首页index展示
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>
2
3
给script标签写上类型module后,它会识别成一个模板,上面的例子显然易见就是三个模块,因为三个模块都指向不同的房间,所以写了类型module就不会引起命名冲突问题.
扩展: 上面的代码也可以像下面这样写
左边的代码也可以像右边这样写
export default,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名
# webpack
# 安装
全局安装
npm install [email protected] -g
局部安装(后续才需要)
npm install [email protected] --save-dev
--save-dev`是开发时依赖,项目打包后不需要继续使用的。
为什么全局安装后,还需要局部安装呢? 在终端直接执行webpack命令,使用的全局安装的webpack 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
# 起步
dist文件夹:用于存放之后打包的文件
src文件夹:用于存放我们写的源文件
main.js:项目的入口文件。
mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。
index.html:浏览器打开展示的首页html
现在要把info.js的文件和mathUtils.js的文件通过模块化的方式引入到main.js文件,,将main.js文件会打包到dist文件夹下,通过首页index.html引入打包后的js文件 mathUtils.js文件中的代码
function add(num1, num2) {
return num1 + num2
}
function mul(num1, num2) {
return num1 * num2
}
module.exports = {
add,
mul
}
2
3
4
5
6
7
8
9
10
11
12
info.js文件中的代码
export const name = 'why';
export const age = 18;
export const height = 1.88;
2
3
main.js文件中的代码
// 1.使用commonjs的模块化规范
const {add, mul} = require('./mathUtils.js')
console.log(add(20, 30));
console.log(mul(20, 30));
// 2.使用ES6的模块化的规范
import {name, age, height} from "./info";
console.log(name);
console.log(age);
console.log(height);
2
3
4
5
6
7
8
9
10
11
12
使用webpack讲main.js打包到指定的文件夹/dist下,重命名为bundle.js,此时由于main.js包含了info.js和mathUtils.js,所以会逐一把main.js进行打包,从而打包到/dist文件夹下.在首页index.html引入bundle.js就等于引入了main.js
<script src="./dist/bundle.js"></script>
# 入口和出口
在项目文件夹下局部安装webpack,因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。 所以通常一个项目,都有自己局部的webpack。
npm install [email protected] --save-dev
安装后项目文件夹会多出来一个node-modules,里面存放了node的各种依赖包,其中就包括了webpack, 在项目下一般会用npm初始化一下文件,这个时候会生成package.json文件,里面存放的webpack的版本号,开发时依赖的版本号
新建一个webpack.config.js文件,配置出口和入口文件
const path = require('path')
//导入node里面全局的包
module.exports = {
//入口
entry: './src/main.js',
//出口
output: {
//路径 动态获取 拼接 __dirname就是当前文件路径
path: path.resolve(__dirname, 'dist'),
//打包后的名字
filename: 'bundle.js'
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
自定义webpack打包命令(package.json)
# css文件
css,需要安装两个依赖,css-loder,style-loder
npm install --save-dev css-loader
npm install --save-dev style-loader
2
如果想使用的话就必须建立相应的css文件js文件主页index文件等 文件夹目录
使用的话将css文件引导到main.js文件就可以使用
//导入依赖css文件
require('./css/normal.css')
2
在webpack.config.js的文件下(刚才配置的入口文件后)编辑
# less文件
npm install --save-dev less-loader
同理,安装less-loader. 在css文件下写less文件,在main.js里面引入,在webpack.config.js文件下配置
{
test: /\.less$/,
use: [{
loader: "style-loader",
}, {
loader: "css-loader"
}, {
loader: "less-loader",
}]
},
{
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']
}
}
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
less同理css文件还有图片文件,babel转es5文件一样,都是以上的基本配置,建立对应文件夹(参考上部文件夹格式)下载相应的依赖包,匹配文件名字,使用loader依赖包.最后项目终端下npm run build进行打包构建,webpack的学习还有很多,如果想深入请移步官方中文文档webpack中文文档 (opens new window)
# webpack配置Vue
在项目文件夹下安装Vue
npm install vue --save
此时会在项目文件下node_modules多出vue的配置文件,如果想要在页面使用vue,需要导入引用,在main.js下导入
import Vue from 'vue'
const app = new Vue({
el:'#app',
data: {
message:'hello webpack'
}
})
2
3
4
5
6
7
8
在index.html下创建#app的div,这样在main.js下才能成功挂载,
<div id = "app">
<h2>{{message}}</h2>
</div>
2
3
打包后发现在页面并没有显示出hello webpack,出现报错.
Vue构建发布的时候构建了两个版本,runtime-only(不可以有任何的template),runtime-complier(可以有template,因为有compiler可以用于编译template)
此例子中div就相当于app的模板,这个错误说的是我们使用的是runtime-only版本的Vue,所以会报错,解决此问题的话可以在webpack.config.js下加一条命令,跟入口出口module一个级别
import Vue from 'vue',具体指向文件夹路径去找,找到vue,dist,里面就是发布的所有版本,找到vue.esm.js,里面就包含了runtime-complier,默认的话指向的vue.runtime.js,就是runtime-only版本的Vue.
在webpack.config.js中加了配置后,hello webpack会在页面显示出来
# template和el的关系
可以删掉const app,直接new一个实例.new Vue({}),同时有el,也有template的话,Vue内部会自动把template里面写的模板内容会复制到el挂载的app上面(覆盖),也就是说,在index.html里面可以只写一个
,,,在new Vue实例对象当中写一个template:里面写想在div里显示的内容,,之前的写法就是把所有的文字内容都写在页面上的#app元素里面,现在不需要写在#app元素里面,只需要在实例对象里写template,会自动替换掉el的app挂载的dom(#app)index.html打包后页面最终效果
# Vue究极使用方案
一般情况下,真实开发中,在index.html中一点代码都不改,就只写一个组件的名字
,同上面一样.好处就是再也不用更改html代码,如果new Vue实例中的代码多了,可以再次进行抽取. 在实例对象上面新建一个组件,把实例对象里的所有都复制到组件里面去此时App组件就是一个对象,就可以在src下创建一个vue文件夹,新建一个app.js文件,把const App(组件)的东西都写在app.js文件里,
目录结构
app.js文件
main.js文件
这样的话main.js文件看起来更简洁.但是app.js文件中的内容显得也有点冗余,没有把模板和方法等进行分离,所以此时给src下创建一个App.vue文件,将app.js文件中的代码复制粘贴到vue组件文件当中 app.js的template粘贴到template下,data和methods粘贴到script下,最下面还有个style样式,可以在template里面定义元素的class类,在style里面进行更改样式
最后结尾就用不到app.js文件了,要引入App.vue文件
.vue文件在打包的时候会报错,因为babel-loader没有识别,所以同样也需要安装依赖.(vue文件加载和vue文件编译)
npm install --save-dev vue-loader vue-template-compiler
安装完毕后配置webpack的loader,在rules:[]里面新增一条
{
test: /\.vue$/,
use:['vue-loader']
}
2
3
4
这样就可以打包编译成功了.
如果还想在页面使用(嵌套)其他组件,就可以在vue文件夹下新建一个Cpn组件,Cpn.vue
这样就形成了一个组件树,其实在实际开发中也是这样,可能只有一个App.vue组件是大的一个组件(页面),然后在App.vue文件里面嵌套这很多很多其他的组件,最后组成一个组件树(组件化开发)
# Vue CLI
如果你只是简单写几个Vue的demo程序, 那么你不需要Vue CLI. 如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI
使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。 如果每个项目都要手动完成这些工作,那无以效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
# CLI2
CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架. Vue CLI是一个官方发布 vue.js 项目脚手架 使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
脚手架的使用安装前提是必须要安装node
,安装webpack
安装:
npm install -g @vue/cli
Vue CLI2初始化项目
vue init webpack my-project
Vue CLI3初始化项目
vue create my-project
Vue-CLI2安装过程
目录解构详解
# Eslint
搭建脚手架的时候可以选择Eslint进行安装,他是一个进行代码规范的工具 不想使用的话可以在脚手架目录里的config/index.js的useEslint:false,进行关闭
# Vue-CLI3
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中
目录结构
图形化界面
启动:项目文件夹下vue ui,配置等文件都可以在图形化界面修改