全书代码:https://github.com/icarusion/vue-book
chapter1初识Vue.js
- MVVM模式:全称model-view-viewmodel,Vue在设计上也遵循MVVM模式。View和ViewModel之间通过双向绑定建立联系,当视图View发生变化,视图模型ViewModel也会变化,反之亦然。
chapter2.数据绑定和第一个Vue应用
1.Vue实例
- 通过构造函数Vue就可以创建一个Vue实例。
let app = new Vue({
// 选项
// 1.el:指定一个页面中已经存在的DOM元素来挂载Vue实例
// 挂载成功后可以通过app.$el来访问该元素
el: '#app',
})
2.Vue的数据绑定
- 单向数据绑定:当页面上的数据发生改变,data对象里的对应的属性值不会发生变化。
<body>
<div id="app">
<input type="text" v-bind:value='name'></input>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
name: 'test',
}
}
})
</script>
</body>
- 双向数据绑定
<body>
<div id="app">
<input type="text" v-model:value='name'></input>
<h1>Hello,{{name}}</h1>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
name: '',
}
}
})
</script>
</body>
3.生命周期
- Vue的常用生命周期钩子如下:
- created:实例创建完成后调用,这个阶段完成了数据的观测等,但是尚未挂载。
- mounted:el挂载到实例上后调用。此时$el可以使用
- beforeDestroy:实例销毁之前调用
<div id="app">
</div>
<script>
new Vue({
el: '#app',
data() {
return {
a: 100,
}
},
created() {
// 100, 此时data中的数据完成观测
console.log(this.a)
// el尚未挂载到实例上,undefined
console.log(this.$el)
},
mounted() {
// 打印div元素对象
console.log(this.$el)
},
})
</script>
4.插值语法
- 插值语法使用双大括号。
<body>
<!-- 实时显示当前的时间示例 -->
<div id="app">
<h1>今天时间是{{date}}</h1>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
date: new Date()
}
},
mounted() {
let that = this
this.timer = setInterval(function() {
that.date = new Date()
}, 1000)
},
beforeDestroy() {
if (this.timer) {
// 清除定时器
clearInterval(this.timer)
}
},
})
</script>
</body>
5.过滤器
- 功能:在插值表达式中添加一个管道符|对数据进行过滤。
- 通过filters选项来设置过滤器
- 过滤器可以接受参数
- 过滤器可以串联
<body>
<!-- 实时显示当前的时间示例 -->
<div id="app">
<h1>今天时间是{{date | formateDate}} </h1>
</div>
<script>
let vueTemp
new Vue({
el: '#app',
data() {
return {
date: new Date()
}
},
mounted() {
let that = this
this.timer = setInterval(function() {
that.date = new Date()
}, 1000)
},
beforeDestroy() {
if (this.timer) {
// 清除定时器
clearInterval(this.timer)
}
},
beforeCreate() {
vueTemp = this
},
filters: {
formateDate(value) {
// 过滤器中的this对象是window对象
console.log("this:", this)
const date = new Date(value)
let year = date.getFullYear()
let month = vueTemp.format(date.getMonth())
let day = vueTemp.format(date.getDay())
let hour = vueTemp.format(date.getHours())
let minutes = vueTemp.format(date.getMinutes())
let seconds = vueTemp.format(date.getSeconds())
return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds
}
},
methods: {
format(value) {
return value < 10 ? '0' + value : value;
}
},
})
</script>
</body>
6.指令
- 指令带有前缀
v-
。 - Vue中内置了许多实用的指令
- v-bind指令:动态更新HTML元素上的属性
- v-on指令用来绑定事件监听器
<body> <div id="app"> <p v-if='show'>这是一段文本</p> <button v-on:click='show = !show'>点击按钮隐藏/显示文本</button> <p>show的值为{{show}}</p> </div> <script> new Vue({ el: '#app', data() { return { show: true } } }) </script> </body>
- v-html:输出HTML,而不是纯文本
<body> <div id="app"> <span v-html='link'></span> </div> <script> new Vue({ el: '#app', data() { return { link: '<a href="#">这是一个a标签</a>' } }, }) </script> </body>
- v-pre:跳过元素和它的子元素的编译过程
<body> <div id="app"> <div v-pre> <span>{{这里的内容不会被编译}},将原样输出</span> </div> </div> <script> new Vue({ el: '#app', data() { return { } }, }) </script> </body>
7.语法糖
- v-bind:可以简写成
:
- v-on:可以简写成
@
chapter3.计算属性和监视属性
1.计算属性
- 计算属性以函数的形式写在Vue实例内的computed选项内,最终返回计算的结果。
- 每一个计算属性都包含一个getter和一个setter。当读取计算属性的值时就会执行getter方法,当修改计算属性的值时就会执行setter方法。
<body>
<div id="app">
你好:<input type="text" v-model='fullName'></input>
<h1>姓:{{firstName}}</h1>
<h1>名:{{lastName}}</h1>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
firstName: '张',
lastName: '小云',
}
},
computed: {
fullName: {
get() {
return this.firstName + ',' + this.lastName
},
set(newValue, oldValue) {
nameArr = newValue.split(',')
this.firstName = nameArr[0]
this.lastName = nameArr[nameArr.length - 1]
}
}
}
})
</script>
</body>
- 计算属性可以缓存:通常使用计算属性实现的功能使用methods选项中的方法也可以实现。计算属性可以缓存,一个计算属性所依赖的数据发生变化时他才会重新执行get方法取值。所以只要依赖的数据不发生变化,计算属性就不会更新。
2.监视属性
- 监视属性:当被监视的属性发生变化时,回调函数自动被调用,进行相关操作。
- 监视属性的两种写法
- 在
new Vue({})
时传入watch选项 - 通过$watch方法监视
- 在
- watch监听的属性的回调函数有两个参数可以使用:第一个是新值,第二个是旧值。
watch: {
isShow: {
// 初始化时让handler调用一下
immediate:true,
// 当监视的isShow属性发生变化时调用handler
handler(newValue, oldValue) {
},
// Vue中的watch默认不监视对象内部值的改变,只检测对象自身的改变
// 配置deep:true就可以检测对象内部值的改变
deep: true
}
}
// 监视属性的简写形式
// 当只有handler一个配置项时可以简写
watch: {
isShow(newValue, oldValue) {
}
}
chapter4.v-bind及class与style的绑定
1.绑定class的几种方式
- 对象语法
<body>
<div id="app">
<!-- 渲染后的结果为<div class="active"></div> -->
<div :class="{'active': isActive}"></div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
isActive: true,
}
}
})
</script>
</body>
- 数组语法
<body>
<div id="app">
<!-- 渲染后的结果为<div class="width-height bg-color"></div> -->
<div :class="[wh, bgcolor]"></div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
wh: 'width-height',
bgcolor: 'bg-color',
}
}
})
</script>
</body>
- 数组语法中使用对象语法
2.绑定内联样式
- 对象语法:注意CSS属性名使用驼峰命名或者短横分割命名
<body>
<div id="app">
<!-- <p style="font-size: 24px; color: red;">测试文本</p> -->
<p :style="{'fontSize': fontSize + 'px', 'color': color}">测试文本</p>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
fontSize: 24,
color: 'red'
}
}
})
</script>
</body>
<!--上述示例不方便阅读和维护-->
<body>
<div id="app">
<!-- <p style="font-size: 24px; color: red;">测试文本</p> -->
<p :style="styles">测试文本</p>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
styles: {
fontSize: 24 + 'px',
color: 'red'
}
}
}
})
</script>
</body>
- 数组语法
chapters5.内置指令
1.基本指令
- v-cloak:解决初始化慢导致页面闪动的问题,和
display:none;
一起使用。比如说网速较慢,在页面上显示{{xxx}}
,直到Vue创建实例、编译模板时DOM才会被替换,这个过程是有闪动的。
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="app">
<!--意外情况不会显示{{message}}-->
<h1 v-cloak>{{message}}</h1>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
message: 'Hello',
}
}
})
</script>
</body>
- v-once指令:定义v-once指令的元素或者组件只渲染一次,包括元素或者组件的所有子节点。首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。
<body>
<div id="app">
<h1 v-once>{{message}}</h1>
<!-- 点击按钮,message的值不会发生变化,因为h1元素不再重新渲染 -->
<button @click="message = 'Hi'">点击改变message的值</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
message: 'Hello',
}
}
})
</script>
</body>
2.条件渲染指令
- v-if、v-else、v-else-if
<body>
<div id="root">
<template v-if="type === 'userName'">
<label>用户名:</label>
<!-- 增加key属性表示不复用input元素 -->
<input placeholder="输入用户名" key="name-input"></input>
</template>
<template v-else="type === 'email'">
<label>邮箱:</label>
<input placeholder="输入邮箱" key="email-input"></input>
</template>
<button @click='changeType'>切换输入类型</button></button>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
type: 'userName',
}
},
methods: {
changeType() {
this.type = this.type === 'userName' ? 'email' : 'userName'
}
},
})
</script>
</body>
- v-show指令:改变元素的css属性display。
<body>
<div id="root">
<!--渲染后的结果如下-->
<!-- <h1 style="display: none;">Hello,测试v-show</h1> -->
<h1 v-show='isShow'>Hello,测试v-show</h1>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
isShow: false
}
}
})
</script>
</body>
- v-if和v-show的选择: v-if适用于条件不经常改变的场景,因为它切换开销相对大。v-show适用于频繁切换条件。
3.列表渲染指令v-for
- v-for和v-if一样,可以用在内置标签template上,对多个元素进行渲染。
<body>
<div id="root">
<ul>
<template v-for="(book, index) in books">
<li>索引:{{index}}</li>
<li>书名:{{book.name}}</li>
<li>作者:{{book.author}}</li>
</template>
</ul>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
books: [{
name: '唐生川籍',
author: '张三'
}, {
name: '武林秘籍',
author: '李四'
}, {
name: '大话西游',
author: '王五'
}]
}
}
})
</script>
</body>
- 对象的属性和数组都可以使用v-for遍历。
4.数组更新
- Vue包含了一组观察数组变化的方法,使用它们改变数组可以触发试图更新。
- 通过以下方式变动数组,Vue是检测不到的,也不会触发视图更新。
- 通过索引直接对数组中的元素赋值。解决方式为使用Vue内置的set方法
- 修改数组长度:xxx.length = xxxx。解决方式为使用数组的splice方法。
5.过滤和排序
可以使用计算属性返回过滤或者排序后的数组。
<body>
<div id="root">
<ul>
<!-- 遍历书名中包含秘籍的书 -->
<template v-for="(book, index) in filterBooks">
<li>索引:{{index}}</li>
<li>书名:{{book.name}}</li>
<li>作者:{{book.author}}</li>
</template>
</ul>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
books: [{
name: '唐生秘籍',
author: '张三'
}, {
name: '武林秘籍',
author: '李四'
}, {
name: '大话西游',
author: '王五'
}, {
name: '魔域秘籍',
author: '赵六'
}]
}
},
computed: {
filterBooks() {
return this.books.filter(function(book) {
// return book.name.indexOf("秘籍") != -1
return book.name.match(/秘籍/)
})
}
}
})
</script>
</body>
6.事件与事件处理方法
- 通过
@事件名
给元素绑定事件,事件处理方法定义在methods选项中。
<body>
<div id="root">
<h1>按钮点击的次数:{{count}}</h1>
<button @click="addOne()">不带参的事件处理方法</button>
<!-- Vue中的特殊变量$event用于访问原生DOM事件 -->
<button @click="addTen(10, $event)">带参的事件处理方法, 默认会将原生事件对象传入</button>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
count: 0
}
},
methods: {
addOne() {
this.count++
},
addTen(count, event) {
// PointEvent事件对象
console.log("event:", event)
this.count += count
}
},
})
</script>
</body>
- 事件修饰符:修饰符可以串联,一个事件上使用多个修饰符
- .stop:阻止事件冒泡
- .prevent:阻止事件的默认行为
- .capture:添加事件侦听器时使用事件捕获模式。改变事件捕获的方向
- .self:只当事件在该元素本身而不是子元素触发时才会触发事件回调
- .once:事件处理回调只触发一次,组件同样适用
- .native:表示监听地是原生DOM事件
- @keyup.13:按键修饰符,@keyup表示监听键盘事件,只要当按下的键的keyCode为13时才调用对应的事件回调方法。
chapters6.表单与v-model
1.基本用法
- Vue提供了v-model指令,用于在表单类元素上双向绑定数据。表单类元素例如单选、多选、下拉选择、输入框等。
- 单选按钮的组合使用:
<div id="root">
<input type="radio" value="html-lang" id="html" v-model="pickedOption"></input>
<!-- <label> 标签的 for 属性应当与相关元素的 id 属性相同。 -->
<label for="html">HTML</label>
<input type="radio" value="css-lang" id="css" v-model="pickedOption"></input>
<label for="css">CSS</label>
<input type="radio" value="js-lang" id="js" v-model="pickedOption"></input>
<label for="css">JS</label>
<h1>你的选择是:{{pickedOption}}</h1>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
pickedOption: ''
}
}
})
</script>
- 复选框的组合使用
<div id="root">
<input type="checkbox" value="html-lang" v-model:value='checkedOption' id="html"></input>
<label for="html">HTML</label>
<input type="checkbox" value="js-lang" v-model:value='checkedOption' id="js"></input>
<label for="js">JS</label>
<input type="checkbox" value="css-lang" v-model:value='checkedOption' id="css"></input>
<label for="css">CSS</label>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
checkedOption: ['html-lang', 'css-lang']
}
}
})
</script>
- 下拉框
- 单选
<div id="root"> <select v-model="selected"> <option value="html">HTML</option> <option>CSS</option> <option>JS</option> </select> </div> <script> new Vue({ el: '#root', data() { return { selected: 'CSS' } } }) </script>
- 多选
<div id="root"> <select v-model="selected" multiple> <option value="html">HTML</option> <option>CSS</option> <option>JS</option> </select> </div> <script> new Vue({ el: '#root', data() { return { selected: ['CSS', 'JS'] } } }) </script>
2.绑定值
3.v-model的修饰符
- .lazy:让数据在失去焦点或者回车时才会更新。
<div id="root">
<!-- 当输入框离开焦点或者按下回车,才会更新value -->
<input type="text" v-model:value.lazy='value' @change='printValue'></input>
<span>{{value}}</span>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
value: '',
}
},
methods: {
printValue() {
console.log("value:", this.value)
}
},
})
</script>
- .number:使用这个修饰符可以将输入转化为Number类型。
<div id="root">
<input type="number" v-model='value1'></input>
<!-- string -->
<h1>{{typeof(value1)}}</h1>
<input type="number" v-model.number='value2'></input>
<!-- number -->
<h1>{{typeof(value2)}}</h1>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
value1: '12',
value2: '12'
}
},
})
</script>
- .trim:自动过滤输入的首尾空格
<div id="root">
<input type="text" v-model.trim='message' @change='printMess'></input>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
message: ''
}
},
methods: {
printMess() {
// 输入' 1234',不过滤首尾空格的话原样输出
console.log("mess:", this.message)
}
},
})
</script>
chapters7.组件详解
1.组件的注册
- 全局注册
Vue.component("my-component", {
template: "<div>子组件的内容</div>",
});
<!--在一个父组件中使用这个子组件-->
<div id="app">
<my-component></my-component>
</div>
- 局部注册:使用components选项局部注册组件
<div id="root">
<my-component></my-component>
</div>
<script>
let childComponent = {
template: '<div>这是局部注册的子组件的内容</div>',
// data、name、methods等选项
}
new Vue({
el: '#root',
components: {
'my-component': childComponent,
}
})
</script>
2.使用props配置项传递数据
- props选项一般用于父组件向子组件传递数据,可作为组件间通信的一种方式之一。
- 数组写法:注意当在props配置项中属性名称使用驼峰命名,则在DOM模板中使用时,应该使用短横线分隔命名。
<div id="root">
<!-- 父组件向子组件传递数据 -->
<my-component :first-name='firstName' :last-name='lastName'></my-component>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
firstName: '张',
lastName: '小三'
}
},
components: {
'my-component': {
template: '<div>{{firstName}}-{{lastName}}</div>',
// 接收从父组件中传递过来的数据
props: ['firstName', 'lastName']
},
}
})
</script>
- 对象写法:对象写法可以对props中的属性进行验证。
<div id="root">
<my-component :first-name='firstName' :last-name='lastName'></my-component>
<!-- 不传递则使用默认值 -->
<my-component></my-component>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
firstName: '张',
lastName: '小三'
}
},
components: {
'my-component': {
template: '<div>{{firstName}}-{{lastName}}</div>',
props: {
firstName: {
required: true,
default: '李',
type: String
},
lastName: {
required: true,
default: '二狗',
type: String
}
}
}
}
})
</script>
- props中接收数组类型的数据
3.单向数据流
- 父组件传递初始值给子组件,子组件的data中再声明一个数据将其保存起来,然后就可以再自己的作用域内随意使用和修改。
- props作为需要被转变的值传入,使用计算属性。这样在子组件内使用计算属性,不直接使用props。
4.组件间通信
- 子组件给父组件传递数据一般使用自定义事件。子组件使用
$emit()
来触发事件,父组件使用$on()
来监听子组件上的事件。- 实现方式1
<!--子组件给父组件传递数据的案例--> <!--父组件中--> <template> <div id="app"> <!--父组件中监听increase事件--> <my-component @increase="handleIncrease"/> <h1>total:{{total}}</h1> </div> </template> <script> import MyComponent from '@/views/MyComponent' export default { name: 'App', components: { MyComponent }, data() { return { total: 0 } }, methods: { // count即为子组件向父组件传递的数据 handleIncrease(count) { console.log("count:", count) this.total = count } } } </script> <!--子组件中--> <template> <div> <button @click="increaseCount">增加10</button> </div> </template> <script> export default { name: 'MyComponent', data() { return { count: 0 } }, methods: { increaseCount() { this.count += 10 // 子组件通过事件给父组件传递数据count this.$emit('increase', this.count) } } } </script>
- 上述案例可以使用v-model实现
<template> <div id="app"> <my-component v-model='total'/> <h1>total:{{total}}</h1> </div> </template> <script> import MyComponent from '@/views/MyComponent' export default { name: 'App', components: { MyComponent }, data() { return { total: 0 } }, methods: { // count即为子组件向父组件传递的数据 handleIncrease(count) { console.log("count:", count) this.total = count } } } </script> <template> <div> <button @click="increaseCount">增加10</button> </div> </template> <script> export default { name: 'MyComponent', data() { return { count: 0 } }, methods: { increaseCount() { this.count += 10 // 子组件通过事件给父组件传递数据count this.$emit('input', this.count) } } } </script>
- 通过中央事件总线实现任意组件间的通信,包括父子组件、兄弟组件、跨级组件。
- 安装全局事件总线
<!--在main.js中安装--> new Vue({ router, store, render: h => h(App), beforeCreate() { // 全局事件总线就是一个Vue实例 Vue.prototype.$bus = this }, }).$mount('#app')
- 提供数据使用
this.$bus.$emit('事件名',数据)
- 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
this.$bus.$on('事件名', 回调)
- 事件的解绑:
this.$off('事件名',回调)
,一般在beforeDestroy
钩子中调用。 - 完整示例
<template> <div id="app"> <h1>来自另一个组件的内容:{{message}}</h1> <my-component/> </div> </template> <script> import MyComponent from '@/views/MyComponent' export default { name: "App", data() { return { message: "", }; }, components: { MyComponent, }, mounted() { // 监听passMess事件,接收数据 this.$bus.$on('passMess', (msg) => { this.message = msg }) } }; </script> <!--另一个组件中--> <template> <div> <button @click="handleClick">传递事件</button> </div> </template> <script> export default { name: 'MyComponent', methods: { handleClick() { // 发送数据 this.$bus.$emit('passMess', '一个组件向另一个组件传递的数据') } } } </script>
- 通过父链可以实现祖辈、子孙组件间的通信。避免使用这种方式通信,因为父子组件间紧耦合。
- 通过
this.$children
访问子组件,并且可以通过递归来访问子组件下的子组件 - 通过
this.$parent
访问父组件,并且可以通过递归来访问父组件下的父组件
// 例如在子组件中直接修改父组件中的数据message this.$parent.message = "来自子组件的数据"
- 通过
- 通过子组件索引可以方便实现父子组件间通信
- 用属性ref为子组件指定一个索引名称
- 父组件中使用
this.$refs
来访问指定名称的子组件。
<!--父组件-->
<template>
<div id="app">
<button @click="handleRef">获取子组件中的数据</button>
<h1>来自另一个组件的内容:{{message}}</h1>
<my-component ref="child"/>
</div>
</template>
<script>
import MyComponent from '@/views/MyComponent'
export default {
name: "App",
data() {
return {
message: "",
};
},
components: {
MyComponent,
},
methods: {
handleRef() {
this.message = this.$refs.child.message
}
}
};
</script>
<!--子组件-->
<template>
<div>
</div>
</template>
<script>
export default {
name: 'MyComponent',
data() {
return {
message: '子组件中的数据'
}
}
}
</script>
- 通过插槽可以实现父子组件间通信
5.使用slot(插槽)分发内容
- 作用域:父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。
<!--message就是一个slot,他绑定的是父组件中的数据-->
<my-component>{{message}}</my-component>
- slot的用法
- 默认插槽(单个slot):在父组件模板里,插入在子组件标签内的所有内容将替代子组件的
标签及它的内容。
<!--父组件--> <template> <div id="app"> <my-component> <p>这段文本将插入在子组件内</p> <p>替换掉子组件中的slot标签及它的内容</p> </my-component> </div> </template> <script> import MyComponent from '@/views/MyComponent' export default { name: "App", components: { MyComponent, }, }; </script> <!--子组件--> <template> <div> <slot> <p>如果父组件中没有插入内容,则默认出现这段文本</p> </slot> </div> </template> <script> export default { name: 'MyComponent', } </script>
- 具名插槽:给slot元素指定name属性
<!--父组件--> <template> <div id="app"> <my-component> <p slot="namedSlot">test1</p> <p>test2</p> </my-component> </div> </template> <script> import MyComponent from '@/views/MyComponent' export default { name: "App", components: { MyComponent, }, }; </script> <!--子组件--> <template> <div> <div class="box1"> <!-- 具名插槽 --> <slot name="namedSlot"> </slot> </div> <div class="box2"> <slot></slot> </div> </div> </template> <script> export default { name: "MyComponent", }; </script> <!--渲染后的部分结果为:--> <div><div class="box1"><p>test1</p></div><div class="box2"><p>test2</p></div></div>
- 作用域插槽:使用一个可以复用的模板替换已经渲染的元素。可以用来父子组件间通信
<!--子组件给父组件传递数据示例--> <!--父组件部分代码--> <my-component> <!-- template标签内可以通过临时变量props访问子组件插槽上的数据 --> <template scope="props"> <p >{{props.msg}}</p> </template> </my-component> <!--子组件部分代码--> <template> <div> <div class="box1"> <slot :msg='msg' ></slot> </div> </div> </template>
- 默认插槽(单个slot):在父组件模板里,插入在子组件标签内的所有内容将替代子组件的
- slot的访问:通过
$slots
访问
6.组件的高级用法
- 递归组件:给组件设置name选项,组件在他的模板内就可以递归调用他自己。
- 内联模板:组件的模板一般都是在template选项内定义的。内联模板的功能就是在使用组件时,给组件标签使用
inline-template
特性,组件就会把它的内容当作模板,而不会当作内容分发。 - 动态组件:component元素用来动态的挂载不同的组件,is属性选择要挂载的组件。
<!--动态改变currentView的值就可以动态挂载组件-->
<template>
<div id="app">
<component :is="currentView"></component>
<button @click="changeToView('A')">切换到组件A</button>
<button @click="changeToView('B')">切换到组件B</button>
<button @click="changeToView('C')">切换到组件C</button>
</div>
</template>
<script>
export default {
name: "App",
components: {
componentsA: {
template: '<div>组件A渲染显示</div>'
},
componentsB: {
template: '<div>组件B渲染显示</div>'
},
componentsC: {
template: '<div>组件C渲染显示</div>'
},
},
data() {
return {
currentView: 'componentsB',
}
},
methods: {
changeToView(viewName) {
this.currentView = 'components' + viewName
}
}
};
</script>
- 异步组件
- $nextTick: 在下一次DOM更新结束后执行其指定的回调。
- 语法:this.$nextTick(回调函数)
- 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行操作。
<template>
<div id="app">
<!-- 获取div的文本内容 -->
<div id="div" v-if="showDiv">div的文本内容</div>
<button @click="getContent">获取div的文本内容</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
showDiv: false,
};
},
methods: {
getContent() {
this.showDiv = true;
// 等DOM更新完成再获取Div的文本
this.$nextTick(function () {
let text = document.getElementById("div").innerHTML;
console.log(text);
});
},
},
};
</script>
- 手动挂载实例
- 使用Vue.extend()方法和$mount()方法
chapters8. 自定义指令
- 自定义指令的注册方法
- 全局注册
Vue.directive('指令名', { 指令选项 })
- 局部注册
const app = new Vue({ el: '#app', directives: { 指令名: { // 指令选项 } } })
- 自定义指令的选项:就是一些钩子函数
- bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
- inserted: 被绑定元素插入父节点时调用
- update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
- unbind:只调用一次,指令与元素解绑时调用。
- 钩子函数中常用的参数
- el:指令所绑定的元素,可以用来直接操作DOM
- binding:一个对象,包含以下属性。
- name:指令名,不包含v-前缀
- value:指令的绑定值
- oldValue:指令绑定的前一个值
- expression:指令绑定值的字符串形式
- arg:传给指令的参数。例如
v-on:click
,arg的值就是click - modifiers:一个包含指令修饰符的对象
- vnode:Vue编译生成的虚拟节点
- oldVnode:上一个虚拟节点
- 示例:自定义指令v-focus,页面挂载完毕让input输入框自动获得焦点
<template>
<div id="app">
<input type="text" v-focus:once.prevent="1 + 1"></input>
</div>
</template>
<script>
export default {
name: "App",
directives: {
focus: {
inserted(el, binding, vnode) {
// 和指令绑定的input元素
console.log("el:", el)
// 指令名focus
console.log("name:", binding.name)
// 2,指令的绑定值
console.log("value:", binding.value)
// 1 + 1,指令绑定值的字符串形式
console.log("expression:", binding.expression)
// once,传给指令的参数
console.log("arg:", binding.arg)
// {prevent:true},一个包含指令修饰符的对象
console.log("modifiers:", binding.modifiers)
el.focus()
}
}
}
};
</script>
chapters9.Render函数
vue1.x和vue2.x的区别是:vue2.x使用了虚拟DOM来更新DOM节点,提升渲染性能。
1.虚拟DOM
- 虚拟DOM是一个轻量级的JS对象,在状态发生变化时,虚拟DOM会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。
- 虚拟DOM通过一种VNode类(虚拟节点)来表示的。每个DOM元素或者组件都对应一个VNode对象。
- 虚拟节点VNode的主要类型:
- TextVNode:文本节点
- ElementVNode:普通元素节点
- ComponentVNode:组件节点
- EmptyVNode:没有内容的注释节点
- CloneVNode:克隆节点,可以是以上任意类型的节点,唯一的区别就是isCloned属性为true
2.Render函数
Render函数通过createElement参数来创建虚拟DOM
new Vue({
router,
store,
// h就是createElement参数
render: (h) => return h(App),
}).$mount('#app')
3.createElement参数
- createElement构成了虚拟DOM的模板,他有三个参数:
- 一个HTML标签、组件选项、或者一个函数,String/Object/Function类型。这个参数必选
- 一个对应属性的数据对象,Object(对象)类型。这个参数可选
- 子节点,String或者Array类型。这个参数可选
chapters10.使用webpack
webpack是一个模块打包工具。
1.webpack基础配置
- 安装webpack
npm install webpack --save-dev
- 安装webpack-dev-server
npm install webpack-dev-server --save-dev
- webpack本质就是一个js文件,例如webpack.config.js
chapters11.插件
1.插件的注册
- 注册插件需要使用
install
方法,第一个参数是Vue构造器,第二个参数是一个可选的选项对象。 - 使用插件:
Vue.use(插件名)
2.前端路由
- 前端路由:即由前端维护一个路由规则。其实现方式有两种:
- 利用url的hash:url中包含有#
- History模式:url中没有#,以/分割。这种模式需要服务端支持。
3.vue-router的基本用法
- 安装
vue-router
插件
npm install --save vue-router
- 加载插件:在main.js中
import Router from 'vue-router'
Vue.use(Router)
- 路由跳转:路由跳转有两种方式。
- 使用
<router-link></router-link>
标签,它会被渲染成一个标签。其常用的属性如下:- 标签的to属性指定需要跳转的路径。
- tag属性:使用replace属性不会留下History记录,所以导航后不能使用后退键返回上一个页面。
<router-link to="/test">test</router-link>
- 编程式跳转:使用router实例的方法
- this.$router.push()方法
<template> <div> <button @click="changeView">点击按钮跳转到Test</button> <router-view/> </div> </template> <script> export default { name: 'App', methods: { changeView() { this.$router.push('/test') } }, } </script>
- this.$router.replace()方法:类似于router-link标签的replace功能,他不会向history添加新记录,而是替换掉当前的history。
- this.$router.go()方法
- 使用
4.导航钩子的使用
- vue-router中的导航钩子beforeEach和afterEach,分别在路由即将改变前和改变后触发,设置网页的标题可以在beforeEach钩子中完成。
- 网页标题的修改
// to:即将进入的目标的路由对象(一个route对象) // from:当前导航即将离开的路由对象 // next:调用这个方法,才能进入下一个钩子 router.beforeEach((to, from, next) => { window.document.title = to.meta.title // 放行 next() })
- 某些页面需要校验是否登录,如果登录了就可以访问,否则跳转到登录页
router.beforeEach((to, from, next) => { if (window.localStorage.getItem("token")) { // 放行 next() } else { // 跳转到登录页 next('/login') } })
5.状态管理和vuex
- 状态管理和使用场景:在实际业务中有跨组件共享数据的需求。vuex就是用来统一管理组件状态的,它定义了一系列规范来使用和操作数据。
- vuex的基本用法
- 安装vuex:
npm install --save vuex
- 加载插件:
import Vuex from 'vuex' Vue.use(Vuex)
- 数据保存在vuex的state属性中,任意组件内可以通过
$store.state.xxx
来获取。 - 在组件内,来自store的数据只能读取,不能手动改变,改变store中数据的唯一方式就是显示的提交mutations。
<template> <div> <h1>count的值为:{{count}}</h1> <button @click="addCount">加1</button> <button @click="decreaseCount">减1</button> <button @click="changeCount">改变count的值</button> </div> </template> <script> export default { name: 'Test', data() { return { message: 'Test' } }, methods: { addCount() { // 执行store下mutations中的方法 this.$store.commit('increment') }, decreaseCount() { this.$store.commit('decrement') }, changeCount() { this.$store.commit({ type: 'changeCount', operator: '-', count: 10 }) } }, computed: { count(){ return this.$store.state.count } } } </script> <!--store--> export default createStore({ state: { count: 0 }, getters: {}, mutations: { increment(state) { console.log("state:", state) state.count++ }, decrement(state) { state.count-- }, changeCount(state, params) { if (params.operator === '+') { state.count += params.count } else if (params.operator === '-') { state.count -= params.count } } }, actions: {}, modules: {} })
- getters:当state中的数据需要经过加工后再使用时,可以使用getters加工
<!--store--> state: { list: [1, 3, 6, 4, 8, 0] }, getters: { filterList: (state) => { return state.list.filter((item) => { return item < 6 }) } }, <!--其他组件中--> computed: { list() { return this.$store.getters.filterList } }
- actions:提交mutation,并且可以异步操作业务逻辑。 actions在组件内通过
$store.dispatch
触发。
<!--store配置--> import { createStore } from 'vuex' export default createStore({ state: { count: 0, }, getters: { }, mutations: { increment(state) { console.log("state:", state) state.count++ }, }, actions: { asncIncrease(context) { return new Promise((resolve) => { setTimeout(() => { context.commit('increment') resolve() }, 1000) }) } }, modules: {} }) <!--组件中--> <template> <div> <h1>count的值为:{{count}}</h1> <button @click="asncIncrease">加1</button> </div> </template> <script> export default { name: 'Test', computed: { count() { return this.$store.state.count } }, methods: { asncIncrease() { this.$store.dispatch('asncIncrease').then(() => { console.log(this.$store.state.count) }) } }, } </script>
- modules:将store分割到不同的模块,每个模块都拥有自己的state、getters、mutations、actions
- 总结:涉及到改变数据的,使用mutations。存在业务逻辑的,使用actions。
- 安装vuex:
6.中央事件总线插件的开发
环境:vue2
- src目录下新建vue-bus目录,vue-bus目录下新建index.js,其内容如下:
const install = function(Vue) {
const bus = new Vue({
methods: {
emit(event, ...args) {
this.$emit(event, ...args)
},
on(event, callback) {
this.$on(event, callback)
},
off(event, callback) {
this.$off(event, callback)
}
},
})
Vue.prototype.$bus = bus
}
export default install
- main.js中加载插件
import VueBus from './vue-bus'
Vue.use(VueBus)
- 测试
- 父组件中
<template> <div id="app"> <Counter :number="number"></Counter> </div> </template> <script> import Counter from '@/views/Counter' export default { name: "App", data() { return { number: 0, } }, methods: { handleAdd(value) { this.number += value } }, components: { Counter }, created() { // 监听add事件,事件回调为handleAdd this.$bus.on('add', this.handleAdd) }, beforeDestroy() { // 事件解绑, 指定了事件和回调表示只移除这个回调的监听器 this.$bus.off('add', this.handleAdd) } }; </script>
- 子组件中
<template> <div> <!-- number的初始值来自父组件 --> <h1>number的值:{{number}}</h1> <button @click="addByOne">增加number的值</button> </div> </template> <script> export default { name: 'Counter', props: { number: { type: Number, required: true } }, methods: { addByOne() { // 通过事件总线的方式,子组件给父组件传递数据 this.$bus.emit('add', 10) }, } } </script>