【新鲜出炉,持续更新】Vue教程
视频源
Vue中的MVVM
View层
- 视图层
- 在前端开发中,通常就是DOM层
- 主要作用是给用户展示各种信息
Model层
- 数据层
- 数据可能是固定的死数据,更可能是来自服务器的,从网络上请求下来的数据
ViewModel层【核心】
- 视图模型层
- 是 View 和 Model 沟通的桥梁
- 其实就是Vue实例
- 一方面,它实现了
Data Binding(数据绑定)
,将 Model 的改变实时的反映到了 View 中 - 另一方面,它实现了
DOM Listener(DOM 监听)
,当DOM发生一些事件(如点击、滚动等)时,可以监听到这些事件,并在需要的情况下改变对应的 Data
<body>
<!-- ------------- 此处为 View ------------- -->
<div id="app">
<h2>当前计数:{{counter}}</h2>
<button v-on:click="add">+</button>
<button v-on:click="sub">-</button>
</div>
<!-- --------------------------------------- -->
<script src="vue.js"></script>
<script>
// ---- 此处为 Model ---- //
const obj = {
counter: 0
}
// ------------------- //
const app = new Vue({ // ⬅此处为 ViewModel
el: "#app",
data: obj,
methods: {
add: function () {
this.counter++;
},
sub: function () {
this.counter--;
}
}
})
</script>
</body>
Vue示例中的options
el
- 类型:string | HTMLElement
- 作用:决定之后Vue实例会管理哪个DOM
data
- 类型:Object | Function(组件中data必须是一个函数)
- 作用:Vue实例对应的数据对象
methods
- 类型:{[key: string]: Function}
- 作用:定义Vue的方法,可以在其他地方调用,也可以在指令中调用
Vue的生命周期
插值操作
Mustache语法
mustache:胡须
<div id="app">
<h2>{{message}}</h2> //⬅Mustache语法
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
</script>
v-once 指令
该指令表示元素和组件只渲染一次,不会随着数据的改变而改变。
该指令后面不需要跟任何表达式。
<div id="app">
<h2>{{message}}</h2>
<h2 v-once>{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
app.message = "我修改了message!"
</script>
v-html 指令
该指令会解析渲染后面跟着的string。
该指令后面往往会跟上一个string。
<div id="app">
<h2 v-html="url"></h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
url: '<a href="https://www.bilibili.com">Bilibili干杯!</a>'
}
})
</script>
v-text 指令
与Mustache类似,但是会覆盖掉innerText内容哦。
<div id="app">
<h2>{{message}},嘎嘎</h2>
<h2 v-text="message">,嘎嘎</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
</script>
v-pre 指令
该指令用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
<div id="app">
<h2>{{message}}</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
</script>
v-cloak 指令
cloak:斗篷
可以通过为该指令提供相应的样式,来对未编译就显示出来的Mustache语法进行修饰。
原理就是,在编译完成后,div标签中的v-cloak会消失。
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="app">
<h2>{{message}}</h2>
<h2 v-cloak>{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
setTimeout(function (){
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
}, 1000);
</script>
</body>
绑定属性 v-bind
前面我们介绍的是将数据插入进模板的内容中。其实我们除了需要动态决定内容,有时也需要动态地来绑定元素的属性。
例如:
- 动态绑定a元素的href属性
- 动态绑定img元素的src属性
这时,就可以使用v-bind
指令:
- 作用:动态绑定属性
- 缩写:
:
v-bind 的基本使用
<div id="app">
<img v-bind:src="imgURL">
<!-- 语法糖写法 -->
<img :src="imgURL">
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
imgURL: "https://gagalab.tech/images/avatar.png"
},
})
</script>
v-bind 动态绑定class(对象语法)
<style>
.red{
color: red;
}
.grow{
font-size: 50px;
}
</style>
<body>
<div id="app">
<h2 :class="{red: isRed, grow: isGrow}">{{message}}</h2>
<button v-on:click="turnRed">变红</button>
<button v-on:click="grow">变大</button>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
isRed: false,
isGrow: false,
},
methods: {
turnRed: function (){
this.isRed = !this.isRed;
},
grow: function (){
this.isGrow = !this.isGrow;
}
}
})
</script>
</body>
如果嫌HTML代码冗长,也可以将class的值放入methods或computed中:
...
<h2 :class="getClasses()">{{message}}</h2>
...
methods: {
getClasses: function (){
return {red: this.isRed, grow: this.isGrow};
}
}
...
v-bind 动态绑定class(数组语法)
<style>
.red{
color: red;
}
.grow{
font-size: 50px;
}
</style>
<body>
<div id="app">
<h2 :class="[class1, class2]">{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
class1: "red",
class2: "grow"
}
})
</script>
</body>
同样的,如果嫌HTML代码冗长,也可以将class的值放入methods或computed中。
v-bind 动态绑定style(对象语法)
在我们在后续开发中,肯定要使用模板,当我们想要改变模板的样式时,就可以使用v-bind来动态绑定style
<div id="app">
<h2 :style="{color: messageColor, fontSize: messageFontSize+'px'}">{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
messageColor: "red",
messageFontSize: 50
},
})
</script>
效果如上。
v-bind 动态绑定style(数组语法)
<div id="app">
<h2 :style="[messageColor, messageFontSize]">{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
messageColor: {color: 'red'},
messageFontSize: {fontSize: '50px'}
},
})
</script>
效果如上。
计算属性 computed
就像下面的例子,如果在开发中,我们反复使用像第二行的代码,就会使代码的可读性变差,也显得冗长。这时就可以使用计算属性,像第三行一样,来使代码更加简洁清晰。
计算属性的基本使用
<div id="app">
<h2>{{firstName}} {{lastName}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "Leslie",
lastName: "Cheung"
},
computed: {
fullName: function (){
return this.firstName + ' ' + this.lastName;
}
}
})
</script>
计算属性的复杂操作
<div id="app">
<h2>书的总价为 {{totalPrice}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
books: [
{id: 0, price: 93},
{id: 1, price: 65},
{id: 2, price: 123},
{id: 3, price: 89}
]
},
//⬇ methods一般用动词命名,computed用名词
computed: {
totalPrice: function (){
let totalPrice = 0;
for (let i=0; i<this.books.length; i++){
totalPrice += this.books[i].price;
}
return totalPrice;
}
}
})
</script>
计算属性的getter和setter
<div id="app">
<h2>{{fullName}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "Leslie",
lastName: "Cheung"
},
computed: {
//计算属性一般没有set方法,因为它是只读属性
fullName: {
get: function (){
return this.firstName + ' ' + this.lastName;
},
// set: function (value){
// let name = value.split(' ');
// this.firstName = name[0];
// this.lastName = name[1];
// }
}
//以上写法等价于(这也就是在Mustache语法中fullName不加()的原因):
// fullName: function (){
// return this.firstName + ' ' + this.lastName;
// }
}
})
</script>
计算属性和methods的对比
其实,我们使用methods也可以实现我们上述的功能,那么为什么我们要使用计算属性呢?
原因:计算属性会进行缓存,如果使用多次,计算属性只会调用一次,而methods会调用多次。
看了下面的例子,你就懂啦!
<div id="app">
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: "Leslie",
lastName: "Cheung"
},
computed: {
fullName: function (){
console.log("Invoking fullName");
return this.firstName + ' ' + this.lastName;
}
},
methods: {
getFullName: function (){
console.log("Invoking getFullName()")
return this.firstName + ' ' + this.lastName;
}
}
})
</script>
事件监听 v-on
- 作用:绑定事件监听器
- 缩写:
@
v-on 的基本使用
v-on
指令已经很熟悉啦,在生命周期那一节用到的计数器中,就有使用。
<div id="app">
<h2>当前计数:{{counter}}</h2>
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
counter: 0
},
})
</script>
聪明如你,肯定也知道当@click
后面跟的表达式过于复杂的话,可以封装成函数,放进methods中调用。
v-on 的参数问题
- 当methods中定义的方法不需要额外的参数时,
@click
后面的方法可以不加()
<div id="app">
<button @click="onClick">按钮</button>
<!--等价于-->
<button @click="onClick">按钮</button>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
methods: {
onClick(){
...
}
}
})
</script>
-
当methods中定义的方法需要额外的参数时,
- 老老实实传入参数:
<div id="app"> <button @click="onClick(message)">按钮</button> </div> <script src="../vue.js"></script> <script> const app = new Vue({ el: "#app", data: { message: "Click!", }, methods: { onClick(message){ console.log(message); } } }) </script>
- 写了
()
,但不传入参数:用上面的例子的话,会打印undefined
- 连
()
都不写了:用上面的例子的话,会打印event(点击事件)
那么问题来了,我们要如何既传参,又获取event
呢?
此时我们可以通过$event
来传入事件:
<div id="app">
<button @click="onClick(message, $event)">按钮</button>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "Click!",
},
methods: {
onClick(message, event){
console.log("传入的message:" + message);
console.log("点击事件:" + event);
}
}
})
</script>
v-on 的修饰符
Vue提供了一些修饰符来方便我们处理事件:
.stop
:调用event.stopPropagation()
,阻止冒泡.prevent
:调用event.preventDefault()
,阻止默认行为.{keyCode | keyAlias}
:只有当特定键盘按键被触发时才会触发回调- keyCode:键代码
- keyAlias:键别名
.native
:监听组件根元素的原生事件.once
:该事件只触发一次回调
.stop 阻止冒泡
原理:.stop
调用event.stopPropagation()
<div id="app">
<div @click="onClickDiv">
<button @click="onClickBtn">按钮</button>
</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
methods: {
onClickBtn(){
console.log("Button is clicked!");
},
onClickDiv(){
console.log("Div is clicked!");
}
}
})
</script>
可见,当我们点击按钮时,onClickDiv也被触发了,为了防止发生这样的情况,我们可以使用.stop
修饰符:
<div id="app" @click="onClickDiv">
<button @click.stop="onClickBtn">按钮</button>
</div>
.prevent 阻止默认行为
原理:.prevent
调用event.preventDefault()
<div id="app">
<form action="action">
<input type="submit" value="提交" @click="onClickSubmit">
</form>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
methods: {
onClickSubmit(){
console.log("Submit!");
}
}
})
</script>
当我们点击页面中的提交按钮时,它会因为action="action"
跳转至另一个页面,如果我们想手动让它进行跳转,就可以使用.prevent
修饰符来prevent掉默认的行为。
<form action="action">
<input type="submit" value="提交" @click.prevent="onClickSubmit">
</form>
.{keyCode | keyAlias} 键修饰符
<div id="app">
<input type="text" @keyup.enter="onKeyUp">
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
methods: {
onKeyUp(){
console.log("Key Up!");
}
}
})
</script>
上面的代码,会在用户输入按任意键的时候打印“Key Up!”,但有时我们需要当用户在按某个(键盘)按键时,才触发某个事件,这时我们可以使用键修饰符:
<input type="text" @keyup.enter="onKeyUp">
此时,只有用户在敲击“Enter”键时,才会打印“Key Up!”。
.native
TODO: .native修饰符待填坑
.once 仅触发一次修饰符
在某些特殊的情况下,如想要一个按钮在被点击时,只触发一次回调,就可以使用.once
修饰符:
<button @click.once="onClick">按钮</button>
条件判断
v-if 的使用
<div id="app">
<h2 v-if="show">我将被显示哦~</h2>
<h2 v-if="notShow">我就不显示啦!</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
show: true,
notShow: false,
},
})
</script>
v-else 的使用
<div id="app">
<h2 v-if="isShow">isShow为true时显示我!</h2>
<h2 v-else>isShow为false时显示我!</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
isShow: true,
},
})
</script>
添加这一句:
app.isShow = false;
将显示:
v-else-if 的使用
<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>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
score: 87
},
})
</script>
不过还是不建议向上面这样写,使用computed属性会更好哦~
案例 - 切换登录方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<span v-if="doUseUsername">
<label for="username">用户名</label>
<input type="text" id="username" placeholder="用户名">
</span>
<span v-else>
<label for="email">邮箱</label>
<input type="email" id="email" placeholder="邮箱">
</span>
<button @click="doUseUsername = !doUseUsername">切换登录方式</button>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
doUseUsername: true
},
})
</script>
</body>
</html>
Vue的虚拟DOM
做到这里,其实是有一个小问题的。例如,当用户在用户名输入框输入了“Jack”后,点击切换登录方式的按钮,我们可以发现“Jack”仍在输入框中。
有没有发现不对,上面的代码中我们明明创建了两个完全不同的input,为什么用户的输入会留在另一个输入框中呢?
这里我们就需要引入**Vue的虚拟DOM(Virtual DOM)**的概念。虚拟DOM的实现,使得我们可以在不直接操作DOM元素的情况下,只操作数据就能够重新渲染页面。为了提高浏览器的性能,Vue在进行虚拟DOM渲染时,会尽可能地复用已经存在的元素,而不是重新创建新的元素。
在本案例中,当doUseUsername
被切换为false
时,Vue实际上是直接使用了第一个<input>
,所以“Jack”会被保留。
那么我们该如何避免这样的情况的发生呢?此时可以在<input>
中添加一个属性:key
。当两个<input>
中的key
的值不同时,Vue就不会复用input元素啦!如下:
<span v-if="doUseUsername">
<label for="username">用户名</label>
<input type="text" id="username" placeholder="用户名" key="username">
</span>
<span v-else>
<label for="email">邮箱</label>
<input type="email" id="email" placeholder="邮箱" key="email">
</span>
v-show的使用
与v-if
的用法是一样的,也用于决定一个元素是否被渲染。
v-if 与 v-show 的对比
既然v-if
与v-show
的作用相同,那么我们在开发中该如何选择呢?
我们编写下面的代码:
<div id="app">
<h2 v-if="false">{{message}}</h2>
<h2 v-show="false">{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
</script>
不出所料,页面上应该什么都没有显示。但是我们打开控制台,看看页面的 Elements,如下:
可以看到:
v-if="false"
时,DOM中根本就没有该元素v-show="false"
时,仅仅是将在该元素中增加了display: none;
所以,在开发中,我们应这样选择:
- 选择 v-show:当元素需要频繁地在显示和隐藏中切换
- 选择 v-if:当元素只需要切换一次
循环遍历
v-for 的使用
遍历数组
<div id="app">
<!--01 - 获取数组元素值-->
<ul>
<li v-for="item in people">{{item}}</li>
</ul>
<!--02 - 获取数组下标-->
<ul>
<li v-for="(item, index) in people">{{index + 1}}. {{item}}</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
people: ["Jack", "Rose", "Tom", "Jerry"]
},
})
</script>
遍历对象
<div id="app">
<!--01 - 获取对象的value-->
<ul>
<li v-for="value in person">{{value}}</li>
</ul>
<!--02 - 获取对象的key-->
<ul>
<li v-for="(value, key) in person">{{key}}: {{value}}</li>
</ul>
<!--03 - 获取对象的index-->
<ul>
<li v-for="(value, key, index) in person">{{index + 1}}. {{key}}: {{value}}</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
person: {
name: "Jack",
sex: "male",
age: 18,
}
},
})
</script>
📌 添加 key 属性的作用
官方推荐我们在使用v-for
时,给对应的元素或组件添加 key 属性::key=""
。
原因如下:
正如上文(还记得登录方式切换案例中的小问题吗?)所述,Vue(和React都)实现了一套虚拟 DOM 结构,使得我们可以不直接操作 DOM 元素,只操作数据便可以重新渲染页面,而隐藏在背后的原理便是Vue高效的Diff 算法。
Vue 和 React 的虚拟 DOM 的 Diff 算法大致相同,其核心是基于两个简单的假设:
- 两个相同的组件产生类似的DOM结构;两个不同的组件产生不同的DOM结构
- 同一层级的一组节点可以通过唯一的id进行区分
基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n3)降到了O(n)。
针对假设中组件层级的说法,我们来看React’s diff algorithm中的一张图就可以明白:
当页面数据发生变化时,Diff 算法只会比较同一层级的节点:
- 如果节点类型相同,则重新设置该节点的属性,从而实现节点更新
- 如果节点类型不同,则直接删掉旧的节点,创建并插入新的节点,然后不会再比较这个节点以后的子节点了
举个例子:
如上图,我们希望在B和C的中间插入一个F,Diff 算法默认执行起来是这样的:
我们可以发现,Diff 算法将C更新为F,D更新为C,E更新为D,最后插入了E,这效率也太低了吧!
所以官方才推荐我们使用 key 属性来作为每个节点的唯一标识,这样 Diff 算法就可以轻松地找到正确的位置来插入新的节点。
【总结】key的作用:
key 的作用主要是为了高效得更新虚拟 DOM。在Vue中,过渡切换相同标签名的元素时,应该使用key属性,目的是为了让Vue可以区分它们,否则Vue指挥替换其内部属性,而不会触发过渡效果。
key 的选取
我们来实践在 v-for 时添加绑定 key 属性。下面这个例子实现了上文所说的在B的后面插入F:
<div id="app">
<ul>
<li v-for="item in array">{{item}}</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
array: ["A", "B", "C", "D", "E"]
},
})
</script>
我们在控制台输入
app.array.splice(2, 0, "F");
以达到在B的后面插入F的目的。
那么问题来了,我们应该选谁来作为 key呢?
先说答案,在下面这个例子中,在array
数组中的元素不重复的情况下,应该选用item
来充当 key。
<div id="app">
<ul>
<li v-for="item in array" :key="item">{{item}}</li>
</ul>
</div>
为什么不用index
作为 key 呢?
如果我们用index
来作为 key,那么想一想我们在B的后面添加F后,index
如何变化?那还用说,肯定是F的下标为2,C的下标为3,以此类推。因为index
永远应该是连续的,不可能是“0162345”,所以用index
来作为 key 将起不到区分节点的作用。因此,在本例中,我们选用item
来作为 key,当然也是在array
数组中的元素不重复的情况下。
可响应式的数组方法
我们先看看什么是响应式(来自官方文档):
Vue 最独特的特性之一,是其非侵入性的响应式系统。
数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。
那什么叫可响应式的数组方法?先看下面的例子:
<div id="app">
<button @click="insert">插入元素</button>
<ul>
<li v-for="item in array">{{item}}</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
array: ["A", "B", "C", "D", "E"]
},
methods: {
insert() {
app.array.push("New Item");
}
}
})
</script>
你看!其实Array.push()
方法就是可响应式的数组方法,当我们点击 button,向array
中压入新元素,视图就自己更新了。下面我们修改一下insert()
方法:
methods: {
insert() {
app.array[app.array.length] = "New Item"; //在array的后面插入"New Item"
}
}
此时,我们点击 button,页面没有发生任何变化。但我们打开控制台,输出array
,可以看到array
确确实实发生了变化,但是视图并没有更新。这就说明,有些对数组的操作,并不是可响应式的。
下面是可响应式的数组方法:
- Array.prototype.push()
- 语法:
arr.push(element1, ..., elementN)
- 语法:
- Array.prototype.pop()
- 语法:
arr.pop()
- 语法:
- Array.prototype.shift():从数组中删除第一个元素,并返回该元素的值。
- 语法:
arr.shift()
- 语法:
- Array.prototype.unshift():将一个或多个元素添加到数组的开头,并返回该数组的新长度。
- 语法:
arr.unshift(element1, ..., elementN)
- 语法:
- Array.prototype.splice():通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
- 语法:
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
(没法理解的话,不用理解哈哈哈哈,直接看说明就好) - 参数说明:
start
:传要修改的开始位置(从0计数)- 超出了数组的长度时,从数组末尾开始添加内容
- 负值时,从数组末位开始的第几位(从-1计数)
- 负数的绝对值大于数组的长度时,表示开始位置为第0位。
deleteCount
(可选):传要删除的元素个数- 被省略或大于
start
之后的元素的总数时,在start
后面的元素都将被删除 - 0或负数时,不移除元素
- 被省略或大于
item1, item2, ...
(可选):传要添加的元素;被省略,则将只进行删除操作
- 操作说明:array.splice(在第几个元素的后面)
- 删除元素:第二个参数传要删除的元素个数,不传则删除后面所有元素
- 插入元素:第二个参数传0,第三个参数传要添加的元素
- 替换元素:可以理解为先删除元素,再插入新元素
- 语法:
- Array.prototype.sort()
- 语法:
arr.sort([compareFunction])
:用原地算法对数组的元素进行排序,并返回数组。 - 参数说明:
compareFunction
(可选):用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。- 注意:如果要对数字进行排序,则必须传参!
- 操作说明:
- 升序(数字数组)
numbers.sort((a, b) => a - b);
- 降序(数字数组):
numbers.sort((a, b) => b - a);
- 升序(数字数组)
- 语法:
- Array.prototype.reverse():将数组中元素的位置颠倒,并返回该数组。
- 语法:
arr.reverse()
- 语法:
表单绑定 v-model
v-model 的基本使用
Vue中使用v-model
指令来实现表单元素和数据的双向绑定。
什么是双向绑定呢?我们来看下面的例子,我们在<input
中添加了v-model="message"
,这样就实现了双向绑定:
<div id="app">
<input type="text" v-model="message">
<h2>{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
</script>
本例中的双向绑定实际上就是:
-
input元素中
value
的值向h2元素message
中绑定 -
h2元素中
message
的值向input元素中value
绑定
当然,v-model 除了可以用在 input 元素中,同样也可以用在 textarea 元素中哦。
v-model 的原理
下面我们试试手动实现 v-model
第一步:input 元素中value
的值向 h2 元素message
中绑定【v-on 指令给 input 元素绑定 input 事件】
...
<input type="text" @input="inputVal">
...
methods: {
inputVal(event){
this.message = event.target.value;
}
...
这样看着是不是有点麻烦,还记得我们之前学过的$event
嘛?我们现在对上面的代码进行下简化:
...
<input type="text" @input="message = $event.target.value;">
...
第二步:h2 元素中message
的值向 input 元素中value
绑定【v-bind 绑定 value 属性】
...
<input type="text" @input="message = $event.target.value;" :value="message">
...
这样就实现了和 v-model 相同的功能。
总结起来:
v-model 其实是一个语法糖,它本质上包含了两个操作:
- v-bind 绑定 value 属性
- v-on 指令给当前元素绑定 input 事件
v-model 结合 radio类型
<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="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
sex: "男"
},
})
</script>
注意:为了让两个 radio 互斥,我们以往使用的是添加name="sex"
,但是这里用了v-model="sex"
就不需要使用 name 属性啦。
v-model 结合 checkbox类型
<div id="app">
<!--单选框-->
<div>
<label for="license">同意
<input id="license" type="checkbox" v-model="isAgree">
</label>
<button :disabled="!isAgree">下一步</button>
</div>
<!--多选框-->
<div>
<label for="swimming">
<input id="swimming" type="checkbox" value="游泳" v-model="hobbies">游泳
</label>
<label for="soccer">
<input id="soccer" type="checkbox" value="足球" v-model="hobbies">足球
</label>
<label for="basketBall">
<input id="basketBall" type="checkbox" value="篮球" v-model="hobbies">篮球
</label>
<label for="running">
<input id="running" type="checkbox" value="跑步" v-model="hobbies">跑步
</label>
<span>我的爱好:{{hobbies}}</span>
</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
isAgree: false,
hobbies: []
},
})
</script>
v-model 结合 select 元素
在 select 元素中添加 v-model。
<div id="app">
<!--选择一个-->
<div style="margin-bottom: 20px">
<select v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="葡萄">葡萄</option>
<option value="草莓">草莓</option>
</select>
</div>
<!--选择多个-->
<div>
<select name="fruits" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="葡萄">葡萄</option>
<option value="草莓">草莓</option>
</select>
<span>我选择了:{{fruits}}</span>
</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
fruit: '葡萄',
fruits: []
},
})
</script>
值绑定
其实在开发中,我们对于 input 元素的value
通常不是固定的,需要动态绑定。以“v-model 结合 checkbox 类型”中举的例子为例,我们在data
中,传入需要用户勾选的爱好,这时使用值绑定就可以动态的显示在页面中啦:
<div id="app">
<label v-for="item in givenHobbies" :for="item">
<input type="checkbox" :id="item" :value="item" v-model="hobbies">{{item}}
</label>
<span>我的爱好:{{hobbies}}</span>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
givenHobbies: ["游泳", "足球", "篮球", "跑步"],
hobbies: []
},
})
</script>
v-model 的修饰符
.lazy
修饰符:让数据在输入框失去焦点或用户敲击回车时,才会更新.number
修饰符:让输入框中的内容自动转为数字类型(默认情况下为字符串类型).trim
修饰符:过滤内容左右两边的空格
下面以lazy修饰符为例,来看下如何使用 v-model 修饰符:
<div id="app">
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好"
},
})
</script>
Vue 组件
Vue 的组件化思想
组件化是 Vue.js 中的重要思想:
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何应用都可以被抽象成一颗组件树(如下图)
- 它让代码方便组织和管理,并且提升了扩展性
使用组件的步骤
使用组件分为三个步骤:
- 创建组件构造器
- 调用
Vue.extend()
方法创建一个组件构造器 - 同时传入
template
来作为自定义组件的模板(下例中模板为要显示的 HTML 代码;但是这种写法在 Vue 2.x 的文档中几乎已经被语法糖代替啦)
- 调用
- 注册组件:调用
Vue.component("注册组件的标签名", 组件的构造器)
方法 - 使用组件:在 Vue 实例的作用范围内使用组件,否则不生效
下面我们按照上述步骤,来实际使用一下 Vue 组件。
Vue 组件的基本使用
<div id="app">
<!--3. 使用组件-->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../vue.js"></script>
<script>
// 1. 创建组件构造器对象
const cpnConstructor = Vue.extend({
template: `
<div>
<h2>文章标题</h2>
<h5>作者</h5>
<p>文章内容,balablabala...</p>
<hr>
</div>
`
})
// 2. 注册组件
Vue.component("my-cpn", cpnConstructor);
const app = new Vue({
el: "#app",
})
</script>
全局组件 和 局部组件
全局组件注册方法
其实,在上面的例子中,我们注册的组件就叫做全局组件。
Vue.component("my-cpn", cpnConstructor);
这样注册的组件可以在任意 Vue 实例下都可以生效(当然,脱离了 Vue 实例自然无法生效):
<div id="app">
</div>
<div id="anotherApp">
<my-cpn></my-cpn>
</div>
<script src="../vue.js"></script>
<script>
const cpnConstructor = Vue.extend({
template: `<div><h2>文章标题</h2><h5>作者</h5><p>文章内容,balablabala...</p><hr></div>`
})
// 注册全局组件
Vue.component("my-cpn", cpnConstructor);
const app = new Vue({
el: "#app",
})
const anotherApp = new Vue({
el: "#anotherApp",
})
</script>
局部组件注册方法
那么我们该如何注册局部组件呢?
我们可以先想想,什么是局部组件。局部组件要求只在id="app"
的标签内部才能够生效,因此可以想到,我们可以在名为app
的 Vue 实例内部来注册:
<div id="app">
<my-cpn></my-cpn>
</div>
<script src="../vue.js"></script>
<script>
const cpnConstructor = Vue.extend({
template: `<div><h2>文章标题</h2><h5>作者</h5><p>文章内容,balablabala...</p><hr></div>`
})
const app = new Vue({
el: "#app",
// 注册局部组件
components: {
myCpn: cpnConstructor
}
})
</script>
此时如果我们在id="anotherApp"
的标签中使用<myCpn>
,将不会生效。
注意:
- **如果在注册组件时使用了驼峰命名法,在使用时要使用短橫线分隔命名。**如组件命名时为
myCpn
,在使用时要用<my-cpn>
- 注册局部组件是
components
,注册全局组件是component
父子组件
在前面我们学习了组件树,所以组件和组件之间是存在层级关系的。其中,很重要的就是父子组件的关系,也就是说在父组件中使用子组件。
创建方法,其实就是在父组件中添加components
属性,注册子组件;然后在template
属性中使用它:
<div id="app">
<father-cpn></father-cpn>
</div>
<script src="../vue.js"></script>
<script>
// 子组件
const sonCpnC = Vue.extend({
template: `<div><h4>文章标题</h4><h5>作者</h5><p>文章内容,balablabala...</p><hr></div>`
})
// 父组件
const fatherCpnC = Vue.extend({
template: `
<div>
<h2>文章列表</h2>
<son-cpn></son-cpn>
<son-cpn></son-cpn>
</div>`,
components: {
sonCpn: sonCpnC
}
})
const app = new Vue({
el: "#app",
components: {
fatherCpn: fatherCpnC
}
})
</script>
注意:
-
**子组件要在父组件前面声明。**这样在父组件中注册子组件时,Vue 才能找到子组件呀。
-
**另外,子组件如果在父组件的层级中,子组件将会失效。**这是因为,当 Vue 发现
<son-cpn>
时,会去app
的components中找sonCpn
,发现没有这个组件;然后又去全局组件中找,发现也没有;所以就报错了。<div id="app"> <father-cpn></father-cpn> <son-cpn></son-cpn> </div>
组件注册的语法糖
Vue为了简化注册组件的过程,提供了注册组件的语法糖。
主要是省去了调用Vue.extend()
的步骤,去直接使用一个包含template
属性的对象。
<div id="app">
<gloabl-cpn></gloabl-cpn>
<part-cpn></part-cpn>
</div>
<script src="../vue.js"></script>
<script>
// 注册全局组件的语法糖
Vue.component('gloabl-cpn', {
template: `<div><h4>我是全局组件</h4><p>全局组件内容,balablabala...</p><hr></div>`
})
const app = new Vue({
el: "#app",
// 注册局部组件
components: {
partCpn: {
template: `<div><h4>我是局部组件</h4><p>局部组件内容,balablabala...</p><hr></div>`
}
}
})
</script>
组件模板的分离写法
你可能感觉到了,在上面的例子中,我们在对template
赋值时,写了很多 HTML 代码。显然在 JavaScript 语法中,写 HTML 代码是很头痛的,所以我们可以将template
抽离出来,直接写进 script 标签外面。
下面给出了两种方法:
- 使用
<script type="text/x-template"
- 使用
<template>
<div id="app">
<script-temp-cpn></script-temp-cpn>
<temp-temp-cpn></temp-temp-cpn>
</div>
<!--1. 使用script标签-->
<script type="text/x-template" id="scriptTempId">
<div>
<h4>我是 script 标签创建的组件</h4>
<p>组件内容,balablabala...</p>
<hr>
</div>
</script>
<!--2. 使用template标签-->
<template id="tempTempId">
<div>
<h4>我是 template 标签创建的组件</h4>
<p>组件内容,balablabala...</p>
<hr>
</div>
</template>
<script src="../vue.js"></script>
<script>
Vue.component('script-temp-cpn', {
template: "#scriptTempId"
});
const app = new Vue({
el: "#app",
components: {
tempTempCpn: {
template: "#tempTempId"
}
}
})
</script>
注意:在 template 标签内只能有一个根元素,所以我们最好在 <template>
的最外层包裹一层 <div>
。
组件数据的存放
讨论
显然,在开发中我们组件中的数据不可能是固定的,所以我们需要将 Vue 实例中的数据绑定在组件中。
那么,组件可以直接访问 Vue 实例中的数据吗?我们来试一下!
<div id="app">
<my-cpn></my-cpn>
</div>
<template id="tempId">
<div>
<h4>我是组件</h4>
<p>组件内容:{{content}}</p>
<hr>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
content: "balabalabala...",
},
components: {
myCpn: {
template: "#tempId"
}
}
})
</script>
可见,Vue 没有找到content
,所以组件不能直接访问 Vue 实例的数据。
其实,也不应该能够直接被访问。因为,如果组件的数据也存进 Vue 实例的话,Vue 实例中的数据将会变得非常繁杂。
结论:Vue 组件应该有一个地方来存放自己的数据。
组件数据的存放
那么我没要想一个好位置,来存放属于组件的数据:那肯定是存在组件的实例中,对不对!是的,组件对象也有一个data
(而且还有 methods 属性呢)。
不过,要注意的是:
- 组件的
data
必须是一个函数 - 而且,这个函数返回的是一个对象,这个对象的内部保存着组件的数据
<div id="app">
<my-cpn></my-cpn>
</div>
<template id="tempId">
<div>
<h4>我是组件</h4>
<p>组件内容:{{content}}</p>
<hr>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: {
myCpn: {
template: "#tempId",
data() {
return {
content: "balabalabala...",
}
}
}
}
})
</script>
你也许会困惑,为什么组件的data
必须是一个函数?
组件的 data 是函数的原因
这是一个很有趣的问题!
还记得,我们那个计数器的例子嘛。我们先把它封装成一个组件,然后对他进行多次调用看看。
<div id="app">
<counter-cpn></counter-cpn>
<counter-cpn></counter-cpn>
</div>
<template id="counterTemp">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="counter--">-</button>
<button @click="counter++">+</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: {
counterCpn: {
template: "#counterTemp",
data() {
return {
counter: 0
}
}
}
}
})
</script>
我们可以看到,两个组件的 counter
实际上是独立的。很奇妙对不对,这就是 data
是函数的功劳。
data() {
return {
counter: 0
}
}
对于这个组件来说,不管它被使用了几次,因为data
返回的总是一个新创建的对象,所以组件之间的数据是相互独立、互不干扰的。
data: {
counter: 0
}
反过来,如果 data
还是像 Vue 实例中那样,是一个对象实例,那么每个组件使用的数据其实都是来自同一个 data
对象实例,这样就会出现混乱。
父子组件的通信
在开发中,我们往往需要将一些数据从上层传递到下层。
比如,在一个页面中,我们先从服务器请求到很多数据。其中一部分数据,我们是要在页面中最大组件的子组件中显示的。这个时候,我们不应该再让子组件再次发生请求,而是要让大组件(父组件)直接传递数据给小组件(子组件)。这里就涉及到了,父子组件的通信。
那么如何进行父子组件的通信呢?Vue 官方提供了两种方法:
- 通过
props
向子组件传递数据 - 通过事件向父组件发送消息
下面我们把 Vue 实例当作父组件,来举个例子。在真实的开发中,Vue 实例和子组件的通信适合父组件和子组件的通信过程是一样的。
父向子传数据 - props
当子组件需要向父组件中接收数据时,我们可以在注册子组件时添加 props
属性,来获取需要的数据。
** props
的值有两种:**
- 字符串数组,数组中的字符串就是接收时的名称
- 对象,对象可以限制类型,设置默认值和必选值
- 支持验证很多类型:String / Number / Boolean / Array / Object / Date / Function / Symbol / 自定义类型
具体流程:
- 在子组件中,使用
props
来注册子组件的 properties - 在父组件使用的子组件标签中,通过
v-bind
动态绑定刚刚注册的 properties,properties 的值为父组件要传的数据名 - 在子组件的模板中,以 properties 为名使用获取的数据
<div id="app">
<h2>父组件</h2>
<div>数据:{{movies}},{{message}}</div>
<hr>
<my-cpn :child-movies="movies" :child-message="message"></my-cpn>
</div>
<template id="myCpnTempId">
<div>
<h3>子组件</h3>
<ul>
<li v-for="item in childMovies">{{item}}</li>
</ul>
<h3>{{childMessage}}</h3>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
movies: ["海王", "星际穿越", "盗梦空间"],
message: "以上电影正在热映!"
},
components: {
myCpn: {
template: "#myCpnTempId",
// 1. props为字符串数组
// props: ["childMovies", "childMessage"]
// 2. props为对象
props: {
// 2.1 限制类型
// childMovies: Array,
// childMessage: String,
// 2.2 限制类型,设置默认值和必传值
childMovies: {
type: Array,
default() {
return [];
},
required: true
},
childMessage: {
type: String,
default: "!",
}
}
}
}
})
</script>
注意:
-
HTML 的属性名应该是 kebab-case 式命名的 ,所以尽管
props
中写的是childMovie
,我们在<my-cpn>
中也应该写v-bind:child-movie
。 -
当
props
的类型为 Object/Array时,默认值应返回一个(工厂)函数,否则会报错
子向父发消息 - $emit
当我们需要子组件向父组件发送消息时,我们要使用自定义事件来完成。
自定义事件的流程:
- 在子组件中,通过
$emit()
来触发事件 - 在父组件中,通过
v-on
来监听子组件的事件
具体流程:
- 在子组件的模板中,添加事件,来携带消息
- 在子组件的
methods
中,注册事件,使用$emit()
来向父组件发送事件$emit()
的第一个参数为下一步要用的监听事件名;第二个参数为要传递的参数
- 在父组件的子组件标签中,使用
v-on
来添加事件监听,名为$emit()
的第一个参数,并写一个监听到事件后会被触发的事件名 - 在父组件的
methods
中,添加该事件,收到的参数就是$emit()
的第二个参数
<div id="app">
<my-cpn @child-btn-click="showItem"></my-cpn>
<h2>父组件</h2>
<div>用户点击了:{{categoryItem}}</div>
</div>
<template id="myCpnTempId">
<div>
<h3>子组件</h3>
<button v-for="item in categories" @click="onChildBtnClick(item)">{{item}}</button>
<hr>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
categoryItem: ""
},
methods: {
showItem(item) {
this.categoryItem = item;
}
},
components: {
myCpn: {
template: "#myCpnTempId",
data() {
return {
categories: ["手机数码","电脑办公", "家用家电"]
}
},
methods: {
onChildBtnClick(item){
this.$emit('child-btn-click', item);
}
}
}
}
})
</script>
注意:
** $emit
的第一个参数,应是 kebab-case 式命名的,因为 v-on
后面接的值只能是 kebab-case 命名的。**
父子组件的访问
在开发中,我们其实不仅要在父子组件之间传递数据、发送消息,有时还需要在父子组件之间互相访问各自的数据和方法。
父访问子组件 - $children
使用方法很简单,在父组件的方法中,直接使用 this.$children[x].方法名
就可以获取到相应的子组件对象了。
<div id="app">
<my-cpn></my-cpn >
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<button @click="clickBtn">按钮</button>
</div>
<template id="cpnTempId">
<h3>我是子组件</h3>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
methods: {
clickBtn() {
// console.log(this.$children);
this.$children[0].logMessage();
console.log(this.$children[0].message);
}
},
components: {
myCpn: {
template: "#cpnTempId",
data() {
return {
message: "我是子组件的message"
}
},
methods: {
logMessage(){
console.log("我是子组件的方法logMessage(),我被调用了");
}
}
}
}
})
</script>
点击按钮后,显示
这就表明,父组件获取到了子组件的数据和方法。
我们执行 console.log(this.$children)
的结果如下。
可见,$children
实际上是一个包含所有子组件的数组,所以在使用时记得表明要使用的子组件的下标。
那么问题也来了,既然我们要通过下标来访问子组件,当用户动态地在页面中添加了子组件的话,我们要访问的子组件的下标肯定也会发生变化。所以这对我们来说,是个坏消息。因此,在实际的开发中,我们通常使用 $refs
来访问子组件。
父访问子组件 - $refs
为了解决上一个方法的一些弊端,我们要先给父组件中的子组件标签上添加 ref="组件名"
,然后和 $children
一样,直接使用 this.$refs.组件名
就可以获取到相应的子组件对象了。
<div id="app">
<my-cpn ref="myCpn1"></my-cpn >
<my-cpn ref="myCpn2"></my-cpn>
<my-cpn ref="myCpn3"></my-cpn>
<button @click="clickBtn">按钮</button>
</div>
<template id="cpnTempId">
<h3>我是子组件</h3>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
methods: {
clickBtn() {
console.log(this.$refs.myCpn1.message);
this.$refs.myCpn1.logMessage();
}
},
components: {
myCpn: {
template: "#cpnTempId",
data() {
return {
message: "我是子组件的message"
}
},
methods: {
logMessage(){
console.log("我是子组件的方法logMessage(),我被调用了");
}
}
}
}
})
</script>
注意:
- 在父组件中的子组件标签上添加的属性名为
ref
,没有s
哦 - 如果多个组件的
ref
名一样,则后面的组件会覆盖前面的组件。所以在开发中尽量使用不同的ref
名哦
子访问父 - $parent 和 子访问根 - $root
<div id="app">
<h2>我是根组件</h2>
<father-cpn></father-cpn >
</div>
<template id="fatherCpnTempId">
<div>
<h3>我是父组件</h3>
<child-cpn></child-cpn>
</div>
</template>
<template id="childCpnTempId">
<div>
<h4>我是子组件</h4>
<button @click="clickBtn">按钮</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "我是根组件的message"
},
methods: {
rootLogMessage() {
console.log("我是根组件的方法logMessage(),我被调用了");
}
},
components: {
fatherCpn: {
template: "#fatherCpnTempId",
data() {
return {
message: "我是父组件的message"
}
},
methods: {
parentLogMessage(){
console.log("我是父组件的方法logMessage(),我被调用了");
}
},
components: {
childCpn: {
template: "#childCpnTempId",
methods: {
clickBtn() {
// 1. $parent访问父组件
console.log(this.$parent.message);
this.$parent.parentLogMessage();
// 2. $root访问跟组件
console.log(this.$root.message);
this.$root.rootLogMessage();
}
},
}
}
}
}
})
</script>
点击按钮,输出如下:
在上面的例子中,我们
- 使用
$parent
访问到了子组件的父组件的数据和方法, - 使用
$root
访问到了子组件的根组件的数据和方法。
不过要注意的是,跟组件返回的不是 Vue Component,而是一个 Vue 实例。我们执行 console.log(this.$root)
可以看一下。
组件的插槽 slot
事实上,到目前为止,我们组件的模板都是固定的。但是在开发中,针对不同的应用场景,我们需要我们的组件要根据情况,结构发生一些变化。如下面的这种情况:
上面显示的是我们模拟的文章列表,此时每篇文章都是有作者的。但是如果我的第二篇文章时转载的,我想把作者这个位置的数据换成一个链接,该怎么办呢?如果我们直接修改组件模板,那么必然所有文章的作者都变成了一个链接,所以现在该怎么办呢?
此时我们就可以用上插槽了,我们只需要对HTML的部分进行修改,在模板中将要变化的位置换成 <slot>
标签,然后在使用组件时,在组件标签内部添加你想加的HTML结构就可以了。
<div id="app">
<my-cpn><h4>作者:XXX</h4></my-cpn>
<my-cpn><a href="www.xxx.com">原文链接</a></my-cpn>
<my-cpn><h4>作者:XXX</h4></my-cpn>
</div>
<template id="cpnTempId">
<div>
<h3>标题</h3>
<slot></slot>
<p>文章内容, balabalabala...</p>
<hr>
</div>
</template>
是不是很简单!同时,我们还可以给 slot 添加一个默认值,来表示每篇文章的中间默认是显示作者:
<div id="app">
<my-cpn></my-cpn>
<my-cpn><a href="www.xxx.com">原文链接</a></my-cpn>
<my-cpn></my-cpn>
</div>
<template id="cpnTempId">
<div>
<h3>标题</h3>
<slot><h4>作者:XXX</h4></slot>
<p>文章内容, balabalabala...</p>
<hr>
</div>
</template>
具名插槽
现在我们的需求又变了,我希望每篇文章的结构都是可变的。也就是说,上部(标题区域)可以是文字也可以是链接,中部(作者区域)可以是文字也可以是链接,下部同理。这时该怎么办?聪明的你,一定可以想到,只需要将模板写成三个 <slot>
,在使用时组件中间添加结构就可以啦!我们来试试看:
<div id="app">
<my-cpn><h3>标题</h3></my-cpn>
</div>
<template id="cpnTempId">
<div>
<slot></slot>
<slot></slot>
<slot></slot>
<hr>
</div>
</template>
欸?事情并没有能够如我们所愿,其实原因也很简单,Vue 也不知道你这个 <h3>标题</h3>
应该插进哪个插槽。因此我们只需要给每个 <slot>
一个名字就可以啦。
这时我们就要使用具名插槽 <slot name="">
了。我们献给每一个 <slot>
添加一个 name 属性,然后再使用组件时,将要插入的结构上表明 slot="?"
即可。
<div id="app">
<my-cpn>
<h3 slot="top">标题</h3>
<h4 slot="middle">作者:XXX</h4>
<p slot="bottom">文章内容,balabalabala...</p>
</my-cpn>
</div>
<template id="cpnTempId">
<div>
<slot name="top"></slot>
<slot name="middle"></slot>
<slot name="bottom"></slot>
<hr>
</div>
</template>
作用域插槽
现在我们有这样一个页面:
我们的需求是,将第二个 movieList 的格式改成“海王 - 海贼王 - 海尔兄弟”这个样子,那该怎么办呢?
我们先来分析一下,我们知道既然它是两个组件,那么 movieList 肯定是组件的数据。当我们想要修改第二个组件的格式时,肯定就需要在第二个组件中间使用 movieList。那么问题来了,如果我们像这样直接使用 movies
数组,其实是在访问 Vue 实例中的数据。
<body>
<div id="app">
<my-cpn></my-cpn>
<my-cpn>
<!--这时错误的!-->
<div>{{movies.join(' - ')}}</div>
</my-cpn>
</div>
<template id="cpnTempId">
<div>
<slot>
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components:{
myCpn: {
template: "#cpnTempId",
data() {
return {
movies: ['海王', '海贼王', '海尔兄弟']
}
}
}
}
})
</script>
所以,我们要使用作用域插槽来在使用标签时获取到组件的数据。
具体步骤:
- 在
<slot>
内动态绑定一个属性,属性的值为要传的数据 - 在使用组件时,在组件中间的
<template>
中添加属性slot-scope
,并设置一个值 - 在需要用数据的地方,使用
第二步设置的值.第一步动态绑定的属性名
就可以获取到数据了
<div id="app">
<my-cpn></my-cpn>
<my-cpn>
<template slot-scope="slot">
<div>{{slot.data.join(' - ')}}</div>
</template>
</my-cpn>
</div>
<template id="cpnTempId">
<div>
<slot :data="movies">
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</slot>
</div>
</template>
模块化
为什么要模块化
工程中,肯定会有很多个js文件,这时会不可避免地出现全局变量重名的问题。举个例子的话,我们在一个js文件中定义了一个变量为 true ,然后我们想在另一个文件中使用这个变量。但很不巧的是另一个在他的js文件里也声明了这个变量,恰好是false。这时如果他的文件在我们的文件之前被引入,我取到的这个变量就不是true了,这就出现了问题。
这个问题可以通过把每个js文件封装成一个匿名函数,然后直接调用它来解决。但是这就导致了,我无法复用其他文件中的变量和方法。
为了解决这个问题,最早其实是将,要导出(复用)的数据作为返回值,返回给js文件中的全局变量。只要保证文件之间的这个变量不重名就可以了。这就是模块化的雏形。
而现在我们可以使用模块化的思想,更简便的完成上面的功能。
模块化的规范有:Common JS、AMD、CMD、ES6的module。
正常,浏览器是用不了Common JS、AMD和CMD的,因为没有底层技术的支持。但它支持ES6的语法。而如果我们用了Webpack,就能给他们做一个底层的支持。
Webpack
官方的解释:
本质上,webpack是一个现代的JavaScript应用的静态 模块 打包 工具。
模块:上文已述。
打包:导入和导出是模块化的核心,所以模块之间的关系是非常复杂的,所以webpack可以把各种资源模块打包成一个或多个包(Bundle)。并且在打包的过程中,可以将比如说,less转成css,typescript转成JavaScript,还包括图片压缩等等,让这些本来无法被浏览器识别的文件,被识别。
与 grunt/gulp 的对比
-
grunt/gulp的核心是task,被称为前端自动化任务管理工具,更加强调的是前端流程的自动化,模块化不是它的核心
-
webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能。
什么时候用 grunt/gulp ?
- 工程模块以来非常简单,甚至没有用到模块化的概念
- 只需要进行简单的合并、压缩
js 文件的打包
Webpack 打包命令:
webpack ./src/main.js -o dist/bundle.js --mode development
但是生成的文件有些奇怪:
按理来说,应该在 dist
文件夹下生成 bundle.js
文件。目前不知道如何解决。
引用打包后的文件
Vue CLI
CLI 是什么
CLI(Command-Line Interface)命令行界面,俗称脚手架。
使用 vue-cli 可以快速搭建 Vue开发环境以及对应的 webpack 配置。