Vue
git仓库地址:https://gitee.com/codekitty_lee/vue-study
B站视频地址:https://www.bilibili.com/video/BV15741177Eh
Vue的生命周期
生命周期:一个事物从诞生到消亡的整个过程
Vue的生命周期函数
- beforeCreate 创建前
- created 创建后
- beforeMount 挂载前
- mounted 挂载后
- beforeUpdate 更新前
- updated 更新后
- beforeDestory 销毁前
- destory 销毁后
指令(插值操作)
v-once指令的使用
<div id="app">
<h2 v-once>{{message}}</h2>
</div>
只会在第一次渲染时展示数据,当数据发生改变时,页面不会发生变化
v-html指令的使用
当返回的数据为一个标签的字符串,此时需要将字符串解析成页面元素
<div id="app">
<h2 v-html="url"></h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: "hello world",
url: '<a href="http://www.baidu.com">百度一下</a>'
},
methods: {}
})
</script>
v-text指令的使用
与mustache指令很像,但一般用mustache,因为v-text在拼接字符串的时候不够灵活
<div id="app">
<h2 v-text="message"></h2>
</div>
v-pre指令的使用
在需要mustache指令不解析直接放到页面的时候可以使用此指令
<div id="app">
<h2 v-pre>{{message}}</h2>
</div>
v-cloak指令的使用
页面渲染时会先渲染原始数据,在执行到Vue代码时才会渲染动态数据,可以使用v-cloak指令,并且在css中进行设置,让这段时间内页面数据进行相应显示(一般是隐藏),真实开发中一般比较少用
<style>
[v-cloak]{
display: none;
}
</style>
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
setTimeout(function(){
const app = new Vue({
el: '#app',
data: {
message: "hello world"
},
methods: {}
})
},1000)
</script>
v-bind指令
v-bind基本使用
在需要在元素的属性赋予相应值时,不能直接使用mustache指令,需要使用v-bind进行绑定
<div id="app">
<img v-bind:src="imgUrl" alt="">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: "hello world",
imgUrl: "https://img11.360buyimg.com/pop/s590x470_jfs/t1/184696/38/12293/98820/60dff17bE72e204db/07ee09a21f35514b.jpg.webp"
},
methods: {}
})
</script>
v-bind的语法糖写法
<div id="app">
<img :src="imgUrl" alt="">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: "hello world",
imgUrl: "https://img11.360buyimg.com/pop/s590x470_jfs/t1/184696/38/12293/98820/60dff17bE72e204db/07ee09a21f35514b.jpg.webp"
},
methods: {}
})
</script>
通过v-bind动态绑定class属性
通过v-bind绑定class,通过boolean来判断是否使用此class,点击按钮设置或取消class属性
<style>
.active {
color: red;
}
.line {
line-height: 10px;
}
</style>
<div id="app">
<h2 v-bind:class="{active: isActive , line: isLine}">{{message}}</h2>
<button @click="buttonClick">按钮</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: "hello world",
isActive: true,
isLine: true
},
methods: {
buttonClick: function(){
this.isActive = !this.isActive;
}
}
})
</script>
通过v-bind动态绑定class对象语法
<div id="app"> <h2 v-bind:class="getClasses()">{{message}}</h2> <button @click="buttonClick">按钮</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: "hello world", isActive: true, isLine: true }, methods: { buttonClick: function(){ this.isActive = !this.isActive; }, getClasses: function(){ return {active: this.isActive , line: this.isLine} } } }) </script>
通过v-bind动态绑定class数组语法(使用较少)
<div id="app"> <h2 :class="['active','isLine']">{{message}}</h2> <button @click="buttonClick">按钮</button> </div>
作业,动态修改列表的class
点击列表中的一个将其设置为红色,其他设置为黑色,初始化时第一个元素变红
<style> .active { color: red; } </style> <div id="app"> <ul> <h2 v-for="(item,i) in movies" @click="changeClass(i)" :class="{active: isActive==i}"><a>{{item}}</a></h2> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: "hello world", isActive: 0, isLine: true, movies: ['星际穿越','火星救援','信条'], classes: '', index: '' }, methods: { changeClass: function(i){ this.isActive=i } } }) </script>
v-bind动态绑定style,对象语法
<ul> <h2 :style="{key(属性名):value(属性值)}">{{message}}</h2></ul><!-- key不需要加单引号(如果不使用驼峰命名,key中出现减号则需要加单引号),value需要加单引号,如果不加单引号会按照变量处理 --><ul> <h2 :style="{fontSize:'100px'}">{{message}}</h2></ul>
v-bind动态绑定style,数组语法
<div id="app"> <ul> <h2 :style="[style1,style2]">{{message}}</h2> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"> </script> <script> const app = new Vue({ el: '#app', data:{ isActive: 0, message: 'hello world', style1: {backgroundColor:'blue'}, style2: {fontSize:'100px'} } }) </script>
计算属性
计算属性一般没有set方法,即为只读
计算属性的基本使用
<div id="app"> <h2>{{fullName}}</h2> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { firstName: 'cobe', lastName: 'briant' }, computed: { fullName: function(){ return this.firstName + ' ' + this.lastName } } }) </script>
计算属性的复杂操作
<div id="app"> <h2>{{sumPrice}}</h2> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { books: [ {id: 01,name:'linux就该这么学',price:20}, {id: 02,name:'java编程思想',price:80}, {id: 03,name:'数据结构与算法',price:50} ] }, computed: { sumPrice: function(){ let result = 0 for(book of this.books){ result += book.price } return result } } }) </script>
计算属性和methods的对比
在多次调用时,计算属性只会计算一次,而方法会多次调用,所以由于性能考虑使用计算属性
<div id="app"> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { firstName: 'cobe', lastName: 'briant' }, methods: { getFullName: function(){ console.log("methods获取全名"); return this.firstName + ' ' + this.lastName } }, computed: { fullName:{ get: function(){ console.log("计算属性获取全名"); return this.firstName + ' ' + this.lastName } } } }) </script>
事件监听
v-on的基本使用
<div id="app"> <h2>{{counter}}</h2> <!-- 正常写法 方法名加不加括号都可以,如果需要传参则需要加括号(在事件监听中,mustache语法则不可以省略)--> <button v-on:click="plus">加</button> <!-- 简写 --> <button @click="minus">减</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { counter: 0 }, methods: { plus(){ this.counter++ }, minus(){ this.counter-- } } }) </script>
v-on参数传递
如果没有传递参数,但该方法需要传参,vue会默认传递一个event参数,传递两个以上的参数中的event要使用$event
<div id="app"> <button v-on:click="plus(message,$event)">按钮</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { counter: 0, message: "message" }, methods: { plus(arg1,event){ console.log(arg1); console.log(event); } } }) </script>
v-on修饰符的使用
.stop 阻止事件冒泡
<div id="app" @click="divClick"> <button @click.stop="buttonClick">按钮</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { counter: 0, message: "message" }, methods: { buttonClick(){ console.log("buttonClick") }, divClick(){ console.log("divClick") } } }) </script>
.prevent 阻止默认事件(在表单提交中会优先提交表单,如需想自己定义方法处理则需要使用此修饰符,修饰符可以连续使用多个)
<div id="app" @click="divClick"> <form action="query"> <input type="submit" value="提交" @click.prevent.stop="submitClick" /> </form> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { counter: 0, message: "message" }, methods: { submitClick(){ console.log("submitClick") } } }) </script>
.keyup .keydown 键盘按下/抬起时触发,可以使用@keyup.enter来监听enter的抬起事件,不使用.enter默认监听键盘所有按键
<div id="app" @click="divClick"> <input type="text" @keydown="keyUp"> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { counter: 0, message: "message" }, methods: { keyUp(){ console.log("keyUp") } } }) </script>
.once 只能点击一次的修饰符,会同时触发父标签的事件,如果同时使用.stop修饰符,第二次以后会同时触发父标签的事件
<div id="app" @click="divClick"> <button @click.stop.once="buttonClick">once按钮</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el:'#app', data: { counter: 0, message: "message" }, methods: { divClick(){ console.log("divClick") }, keyUp(){ console.log("keyUp") } } }) </script>
v-if v-else-if v-else的使用
v-else-if的使用和v-if相同,都需要放入boolean类型的值
<div id="app"> <h1 v-if="isShow">{{message}}</h1> <h1 v-else>else</h1> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: "hello world", isShow: true }, methods: {} }) </script>
登陆方式切换案例
在这个案例中会出现点击切换登陆方式时,当前输入的内容会保留,这是由于Vue的虚拟dom机制,在页面元素发生改变时,Vue会尽可能的复用之前已有的元素,而不是创建新元素,所以点击切换登陆方式前后使用的是同一个input标签,所以标签内容还是之前的标签内容,可以使用key来进行内容的保留或清除,相同保留,不同清除
<div id="app"> <div v-if="emailLogin"> 邮箱<input type="text" name="" id="" key="email"/> 密码<input type="text" name="" id=""/> </div> <div v-else> 账号<input type="text" name="" id="" key="username"/> 密码<input type="text" name="" id=""/> </div> <button @click="emailLogin = !emailLogin">切换登陆方式</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { emailLogin: true } }) </script>
v-show和v-if的区别
v-show在不显示时会渲染进页面,display设置为none,而v-if不会渲染进页面
在开发中,如果需要频繁切换时,使用v-show,不频繁切换使用v-if
循环遍历
v-for遍历数组
<div id="app"> <ul> <li v-for="(item,i) in movies">{{i+1}} {{item}}</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { movies:['星际穿越','火星救援','信条'] }, methods: {} }) </script>
v-for遍历对象
<div id="app"> <ul> <li v-for="(value,key,index) in info">{{key}} {{value}}</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { info: { name: 'who', age: 18, sex: 'male' } }, methods: {} }) </script>
v-for使用过程添加
在这里使用v-bind绑定key,可以使插入中间数据时效率提高,因为如果不绑定key时,默认会按数组插入,在插入的同时,会遍历后面元素,而绑定key之后,底层实现转换为链表,插入效率更高,所以在经常需要变更的时候,绑定key可以使效率更高,末尾插入绑定或者不绑定效果差别不大
<div id="app"> <ul> <li v-for="(item,i) in movies" :key="item">{{i+1}} {{item}}</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { movies:['星际穿越','火星救援','信条','复仇者联盟'] }, methods: {} }) </script>
v-for响应式
<div id="app"> <ul> <li v-for="(item,i) in movies">{{i+1}} {{item}}</li> </ul> <input type="button" @click="changeBtn" value="change"/> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { movies:['星际穿越','火星救援','信条','复仇者联盟'] }, methods: { changeBtn(){ //响应式方法: //页面会改变 // pop() 从最后删除 // shift() 从前面删除 // unshift() 在最前面添加元素 可以添加多个 // splice() 插入元素/删除元素/替换元素 //第一个参数代表删除或替换的位置, //第二个参数代表要删除或替换的个数 //第三个参数及之后代表替换的值 // sort() 排序 // reverse() 反转,将数组内容进行反转 this.movies.push('loki') //在最后添加元素,可以添加多个 //vue提供的方法 // Vue.set(this.movies,0,'Wangda and Vision') //非响应式: //页面不会改变 // 通过索引修改数组(vue没有这种监听) // this.movies[0] = 'Wanda and Vision' } } }) </script>
作业:图书购物车,实现购买数量的加减,移除图书
v-model
使用v-model进行双向绑定,在input框内内容发生变化时数据也会发生变化,数据发生变化时也会同时修改input框的内容
v-model的基本使用
<div id="app"> <input type="text" v-model="message" /> {{message}} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: 'hello world' } }) </script>
v-model包含两个操作,v-bind将数据绑定到input框,v-on页面修改时同时修改数据
v-model结合radio
使用v-model绑定时,radio的name可以省去
<div id="app"> <label> <input type="radio" name="sex" v-model="sex" value="male" checked/>男 </label> <label> <input type="radio" name="sex" v-model="sex" value="female"/>女 </label> <h2>{{sex}}</h2> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { sex: '' } }) </script>
v-model结合checkbox
<!-- 单选 --> <div id="app"> <label for="agree"> <input type="checkbox" v-model="isAgree" id="agree"/>同意 </label> <h2>{{isAgree}}</h2> <button :disabled="!isAgree">下一步</button> <!-- 单选 --> <!-- 多选 --> <label> <input type="checkbox" v-model="hobbies" value="唱歌"/>唱歌 <input type="checkbox" v-model="hobbies" value="健身"/>健身 <input type="checkbox" v-model="hobbies" value="摄影"/>摄影 <input type="checkbox" v-model="hobbies" value="玩游戏"/>玩游戏 <input type="checkbox" v-model="hobbies" value="写代码"/>写代码 </label> <h2>{{hobbies}}</h2> </div> <!-- 多选 --> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { hobbies: [], isAgree: false } }) </script>
v-model结合select
<div id="app">
<!-- 单选 -->
<label>
<select name="fruit"v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="梨">梨</option>
<option value="葡萄">葡萄</option>
</select>
</label>
<h2>{{fruit}}</h2>
<!-- 单选 -->
<!-- 多选 -->
<label>
<select name="fruits"v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="梨">梨</option>
<option value="葡萄">葡萄</option>
</select>
</label>
<h2>{{fruits}}</h2>
<!-- 多选 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
fruit: '香蕉',
fruits: []
}
})
</script>
值绑定
v-model的修饰符
lazy修饰符
在失去焦点或者点击回车的时候会进行同步
<div id="app">
<input type="text" v-model.lazy="message" />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: "hello world",
}
})
</script>
number修饰符
使用number修饰符可以指定输入的类型,通常情况下input内的内容会在输入之后转换成string类型
<div id="app">
<input type="text" v-model.number="age" />
<h2>{{age}}-{{typeof age}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
age: 0
}
})
</script>
trim修饰符
使用trim修饰符可以去除字符串前后的空格(中间的空格不会删除)
<div id="app">
<input type="text" v-model.trim="message" />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: "hello world"
}
})
</script>
组件化
组件化是Vue.js中的重要思想,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用,任何的应用都会被抽象成一颗组件树
组件使用的三个步骤:
-
创建组件构造器
Vue.extend(),这种写法在Vue2.x的文档中几乎已经看不到了,会直接使用语法糖
-
注册组件
-
使用组件
<div id="app"> <!-- 使用组件 --> <my-component> </my-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> //创建组件构造器对象 const cpnC = Vue.extend({ template: `<div> <h2>标题</h2> <p>内容</p> </div>` }) //注册组件 Vue.component('my-component',cpnC) const app = new Vue({ el: '#app' }) </script>
全局组件和局部组件
局部组件开发中用得多
<div id="app">
<!-- 使用组件 -->
<my-component>
</my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
//创建组件构造器对象
const cpnC = Vue.extend({
template: `<div>
<h2>标题</h2>
<p>内容</p>
</div>`
})
//注册组件(全局组件)
Vue.component('my-component',cpnC)
const app = new Vue({
el: '#app',
components: {//局部组件
cop: cpnC
}
})
</script>
父组件和子组件
外层组件是内层组件的父组件
<div id="app">
<!-- 使用组件 -->
<cop1></cop1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
//创建组件构造器对象
const cpnC2 = Vue.extend({
template: `<div>
<h2>标题2</h2>
<p>内容2</p>
</div>`
})
const cpnC1 = Vue.extend({
template: `<div>
<h2>标题1</h2>
<p>内容1</p>
<cop2></cop2>
</div>`,
components: {
cop2: cpnC2
}
})
const app = new Vue({
el: '#app',
components: {//局部组件
cop1: cpnC1
}
})
</script>
组件的语法糖
<div id="app"> <!-- 使用组件 --> <cop1></cop1> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> //创建组件构造器对象 const cpnC1 = Vue.extend() const app = new Vue({ el: '#app', components: {//局部组件 cop1: { template: `<div> <h2>标题1</h2> <p>内容1</p> </div>` }//语法糖 } }) </script>
组件模版抽离的写法
抽离模版有两种写法
-
使用script标签并使用
type="text/x-template"
属性 -
使用template标签
两种写法都需要赋予id
<div id="app"> <!-- 使用组件 --> <cop1></cop1> <cop2></cop2> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <!-- 1.script标签 --> <script type="text/x-template" id="comp"> <div> <h2>标题</h2> <p>内容</p> </div> </script> <!-- 2.template标签 --> <template id="comp1"> <div> <h2>标题</h2> <p>内容</p> </div> </template> <script> //创建组件构造器对象 const cpnC1 = Vue.extend() const app = new Vue({ el: '#app', components: {//局部组件 cop1: { template: '#comp' }, cop2: { template: '#comp1' } } }) </script>
组件中的data为什么是函数
在有多个引用时如果都操作同一个对象会出现问题,如果是函数每个引用都会创建一个对象
父子组件之间的通信
父组件向子组件传递数据,需要在子组件使用props,在调用子组件时使用v-bind传递数据,
props中可以使用数组,也可以使用对象类型,使用对象可以指定数据类型和默认值
<div id="app"> <!-- 使用组件 --> <cop1 :cmovies="movies" :cmessage="message"></cop1> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <template id="comp2"> <div> <h2>标题</h2> <p>内容</p> </div> </template> <template id="comp1"> <div> <h2>{{title}}</h2> <p>{{content}}</p> <div>{{cmovies}}</div> <div>{{cmessage}}</div> <comp2></comp2> </div> </template> <script> //创建组件构造器对象 const comp1 = Vue.extend({ template: '#comp1', components: { comp2: { template: '#comp2' } }, data() { return { title: '标题', content: '内容' } }, props: ['cmovies','cmessage'] }) const app = new Vue({ el: '#app', components: {//局部组件 cop1: comp1 }, data: { message: 'hello world', movies: ['星际穿越','信条'] } }) </script>
驼峰标识
Vue父组件给子组件传数据时,不能使用驼峰命名规则,如果需要驼峰转换可以将需要变为大写的字母用-对应小写字母转换
<cop1 :c-movies="movies" :c-message="message"></cop1>props: ['cMovies','cMessage']
子组件向父组件传递数据
子组件向父组件传递数据需要使用this.$emit('自定义方法名',参数)
,在使用组件时,使用v-on来监听组件里面发出的请求
<div id="app"> <comp :ccategories="categories" @item-click="itemClickP"></comp> </div> <template id="comp"> <div> <button v-for="item in ccategories" @click="itemClick(item)">{{item.name}}</button> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const comp = { template: '#comp', props: { ccategories: { default(){ return [ {id:1,name:'热门推荐'}, {id:2,name:'手机数码'}, {id:3,name:'日用百货'}, ] } } }, methods: { itemClick(item){ //子组件发射一个时间 this.$emit('item-click',item) } } } const app = new Vue({ el: '#app', data: { categories: [ {id:1,name:'热门推荐'}, {id:2,name:'手机数码'}, {id:3,name:'日用百货'}, ] }, components: { 'comp': comp }, methods: { itemClickP(item){ console.log(item); } } }) </script>
组件结合双向绑定
<div id="app"> <comp :pnum1="num1" :pnum2="num2" v-on:item-change="itemchange"></comp> </div> <template id="comp"> <div> <h2>dnum1:{{dnum1}}</h2> <h2>num1:{{pnum1}}</h2> <input type="text" v-model="dnum1" v-on:input="change(dnum1)"/> <h2>dnum2:{{dnum2}}</h2> <h2>num2:{{pnum2}}</h2> <input type="text" v-model="dnum2"/> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const comp = { template: '#comp', props: { pnum1: Number, pnum2: Number }, data(){ return{ dnum1: this.pnum1, dnum2: this.pnum2 } }, methods: { change(dnum1){ this.$emit('item-change',dnum1); } } } const app = new Vue({ el: '#app', data: { num1: 1, num2: 2 }, components: { 'comp': comp }, methods: { itemchange(dnum1){ this.num1 = parseInt(dnum1) } } }) </script>
组件结合双向绑定watch实现
watch可以监听属性的改变,使用watch就可以不用使用v-on:input来监听了
watch: { dnum1(newValue){ this.dnum1 = newValue this.$emit('item-change',this.dnum2) }, dnum2(newValue){ this.dnum2 = newValue } }
组件访问-父访问子
可以使用this.$children[]来获得子组件,但一般使用refs来获取子组件,默认refs为空,需要在使用组件时附加ref属性才可以通过this.refs.xxx来获取到
<div id="app"> <comp></comp> <comp></comp> <comp ref="comp1"></comp> <button @click="btnClick">按钮</button> </div> <template id="comp"> <div> 子组件 </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const comp = { template: '#comp', props: { }, methods: { showMessage(){ return 'hello world' } }, watch: { } } const app = new Vue({ el: '#app', data: { }, components: { 'comp': comp }, methods: { btnClick(){ console.log(this.$children[0].showMessage()); console.log(this.$refs.comp1); } } }) </script>
子访问父(不常用)
子组件访问父组件可以直接用this. p a r e n t 来 获 取 , 使 用 t h i s . parent来获取,使用this. parent来获取,使用this.root可以直接访问到跟组件
<div id="app"> <comp></comp> </div> <template id="comp"> <div> 子组件 <button @click="btnClick">按钮</button> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> const comp = { template: '#comp', methods: { btnClick(){ console.log(this.$parent); } } } const app = new Vue({ el: '#app', data: { message: 'hello world' }, components: { 'comp': comp }, methods: { } }) </script>
插槽的基本使用
组件插槽的目的是为了让组件有更强的扩展性
<div id="app"> <!-- 使用组件 --> <my-component> <button>按钮</button> </my-component> <my-component> <h1>插槽</h1> </my-component> <my-component> <input type="text"/> </my-component> </div> <template id="comp"> <div> <h2>标题</h2> <p>内容</p> <!-- 给插槽赋予默认值 --> <slot> <button>按钮</button> </slot> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> //创建组件构造器对象 const cpnC = Vue.extend({ template: '#comp' }) //注册组件(全局组件) Vue.component('my-component',cpnC) const app = new Vue({ el: '#app', components: {//局部组件 cop: cpnC } }) </script>
具名插槽(给插槽起名字)
<div id="app"> <!-- 使用组件 --> <my-component> <button slot="slot3">按钮</button> </my-component> </div> <template id="comp"> <div> <h2>标题</h2> <p>内容</p> <!-- 给插槽赋予默认值 --> <slot name="slot1"> <button>按钮1</button> </slot> <slot name="slot2"> <button>按钮2</button> </slot> <slot name="slot3"> <button>按钮3</button> </slot> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> //创建组件构造器对象 const cpnC = Vue.extend({ template: '#comp' }) //注册组件(全局组件) Vue.component('my-component',cpnC) const app = new Vue({ el: '#app', components: {//局部组件 cop: cpnC } }) </script>
作用域插槽
作用域插槽就是父组件替换插槽的标签,但是内容由子组件提供
<div id="app"> <!-- 使用组件 --> <my-component> <template slot-scope="slot"> <h3 v-for="item in slot.data">{{item}}</h3> </template> </my-component> <my-component> <template slot-scope="slot"> <p v-for="item in slot.data">{{item}}</p> </template> </my-component> </div> <template id="comp"> <div> <slot :data="movies"> <button v-for="item in movies">{{item}}</button> </slot> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> //创建组件构造器对象 const cpnC = Vue.extend({ template: '#comp', data() { return { movies: ['星际穿越','信条','火星救援'] } } }) //注册组件(全局组件) Vue.component('my-component',cpnC) const app = new Vue({ el: '#app', components: {//局部组件 cop: cpnC } }) </script>
前端模块化
ES模块化的导入导出
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> </head><body> <!-- 这里类型要设置为module --> <script src="aaa.js" type="module"></script> <script src="bbb.js" type="module"></script> </body></html>
被导入类
var name = "aaaName"export { name}export var age = 18export class Person{}//导出default可以在导出时自定义属性名,每个类中只能有一个default导出export default name
导入类
import { name } from "./aaa.js"import { age } from "./aaa.js"import Person from "./aaa.js"import abc from "./aaa.js"console.log(abc);console.log(name);console.log(Person);console.log(age);
webpack
webpack是一个现代的JavaScript引用的静态模块打包工具
安装webpack 3.6.0(安装webpack的前提是要安装node.js)
npm install webpack@3.6.0 -g
webpack基本使用
创建一个文件夹作为工作空间
在main.js中写代码的逻辑,mathUtils.js中写ES6、Common.js等语法
01-webpack的起步-dist--bundle.js-src--main.js--mathUtils.js-index.html
编写完成后使用webpack ./src/main.js ./dist/bundle.js
进行打包,打包后的文件放到dist目录下,在index.html中引用即可
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>index</title></head><body><script src="./dist/bundle.js"></script></body></html>
webpack的配置
创建好目录,使用npm init
命令进行初始化,如果项目有依赖需要使用npm install
进行安装依赖
在webpack.config.js中进行打包的配置
const path = require('path')//node内部的对象module.exports = { entry: './src/main.js', output: { //__dirname指向的是当前文件所属的文件夹 path: path.resolve(__dirname,'dist'), filename: 'bundle.js' },}
使用webpack命令进行打包,也可以在package.json中的scripts进行配置
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" },
使用npm run build进行打包,build为自定义的参数
webpack中使用css文件的配置
首先使用npm安装css-loader和style-loader(部分版本不兼容,经实测以下版本兼容)
cnpm install --save-dev css-loader@2.0.2cnpm install --save-dev style-loader@0.23.1
第二步在webpack.config.js进行配置
const path = require('path')module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname,'dist'), filename: 'bundle.js' }, module: { rules: [ { //css-loader只负责加载,不负责解析 //style-loader负责将样式添加到dom中 //webpack在使用多个loader时是从右向左读取 test: /\.css$/, use: ['style-loader','css-loader'] }] }}
第三步在main.js中引用即可
const { add ,mul} = require("./js/mathUtils")console.log(add(20,30));console.log(mul(100,20));//引用normal.cssrequire('./css/normal.css')
webpack中使用less文件的配置
首先需要安装对应的less-loader,这里要安装4.1.0版本的加载器,另外还要安装一个解析less的包
cnpm install --save-dev less-loader@4.1.0 less
第二步在webpack.config.js进行配置,在module->rules下加入如下配置
{ test: /\.less$/, use: [{ loader: 'style-loader' }, { loader: 'css-loader', }, { loader: 'less-loader' }] }
使用npm run build,打开html页面即可
webpack图片文件的处理
首先安装url-loader和file-loader(正常会以base64的方式进行加载,如果超过了设定的限制大小则会以文件的方式进行加载,这时候就需要file-loader,这里的配置都要指定版本,版本不同打包时可能会报错)
cnpm install url-loader@1.1.2 file-loader@3.0.1 --save-dev
第二步在webpack.config.js进行配置,在module->rules下加入如下配置
{ test: /\.(png|jpg|gif|jpeg)$/, use: [{ loader: 'url-loader', options: { //当加载的图片大于限制的limit时以文件方式加载 limit: 8192, //如果以文件方式读取,将打包后的文件放到dist目录下的img文件夹下 //取原文件名.8位的哈希值.原扩展名 name: 'img/[name].[hash:8].[ext]' }, }] }
在css中使用该文件
body { background: url('../img/avatar.jpg')}
使用npm run build打包使用即可
webpack使用bable(将es语法通过babel转换成es5的语法)
首先安装babel的三个配置
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
第二步在webpack.config.js进行配置,在module->rules下加入如下配置
{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } }}
重新打包可以发现打包后的bundle.js已经是es5的语法了
webpack配置Vue
第一步安装vue
cnpm install vue --save
第二步使用import导入使用
import Vue from 'vue'const app = new Vue({ el: '#app', data: { message: 'hello Vue' }})
第三部配置路径,默认指向的路径不能编译template,这里需要这指定位置类似于jre和jdk的区别
resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } }
打包使用即可
Vue的终极使用方案
在实际开发中一般会抽取模版,也就是不在html页面写除了div标签之外的标签,通常是抽取出一个App.vue作为主入口,template标签中写页面模版,script中写逻辑,style标签中写页面布局,如下
<template> <div> <h2>{{message}}</h2> <Cpn/> </div></template><script>import Cpn from './Cpn'export default { name: 'App', data () { return { message: 'hello Vue 2333' } }, methods: { }, components: { Cpn }}</script><style scoped></style>
主页面配置好了,还要在main.js中将页面注册进去,如下
import Vue from 'vue'import App from './vue/App.vue'new Vue({ el: '#app', template: '<App/>', components: { App }})
在App.vue页面可以配置其他组件通过components来进行配置,组件的编写如下
<template> <div> <h2>Cpn组件开始</h2> <h1>{{name}}</h1> <h2>Cpn组件结束</h2> </div></template><script>export default { name: 'Cpn', data () { return { name: 'Cpn组件' } }}</script><style scoped></style>
在使用.vue文件时需要使用vue-loader和vue-template-compiler这两个依赖,两个依赖的版本如下,vue-loader14以后的版本需要安装插件,这里使用13的版本
"vue-loader": "^13.0.0","vue-template-compiler": "^2.5.21"
打包使用即可
webpack的插件
loader和plugin的区别
- loader主要是用于转换某些类型的模块,它是一个转换器
- plugin时插件,它是对webpack本身的扩展,是一个扩展器
plugin的使用:
-
通过npm安装需要使用的插件(一些插件webpack已经内置,不需要安装)
-
在webpack.config.js中配置插件
配置BannerPlugin插件:
bannerPlugin是一个版权插件,由于webpack已经内置,使用时需要在webpack.config.js中配置即可
const webpack = require('webpack')plugins: [ new webpack.BannerPlugin('版权归See所有') ]
打包之后的bundle.js的第一行会有所设置的版权信息
安装配置html-webpack-plugin插件:
该插件的目的是将index.html打包后放到dist目录下
第一步安装插件
cnpm install html-webpack-plugin@3.2.0 --save-dev
第二步修改webpack.config.js配置文件(要删除在ouput下配置的publicPath)
const HtmlWebpackPlugin = require('html-webpack-plugin')plugins: [ new webpack.BannerPlugin('版权归See所有'), new HtmlWebpackPlugin() ]
打包使用即可
uglifyjs-webpack-plugin的使用
js代码压缩的plugin
第一步安装插件
cnpm install uglifyjs-webpack-plugin@1.1.1 --save-dev
第二步在webpack.config.js中进行配置
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')plugins: [ new webpack.BannerPlugin('版权归See所有'), new HtmlWebpackPlugin({ template: 'index.html' }), new UglifyjsWebpackPlugin() ]
打包后可以看到bundle.js内的代码已经被压缩了
搭建本地服务器
搭建本地服务器可以在开发的时候实现热部署,但数据并没有真的打包,因为放在内存要比硬盘读取更快,在完成开发之后需要打包在进行手动打包
首先要安装webpack-dev-server模块
cnpm install webpack-dev-server@2.9.0
第二步在webpack.config.js中进行配置
devServer: { //为哪个文件夹提供本地服务,默认是根目录,这里要填写./dist contentBase: './dist', //实时刷新 inline: true, }
第三部在package.json中进行配置
"dev": "webpack-dev-server"
使用npm run dev即可
webpack配置文件的分离
首先导入配置
npm install webpack-merge@4.1.5 --save-dev
第二步创建一个存放配置文件的文件夹并创建三个配置文件分别用于存放通用配置、开发配置和生产配置
build-base.config.js-dev.config.js-prod.config.js
第三步在开发和生产配置中进行如下配置
//引入刚刚装过的配置const webpackMerge = require('webpack-merge')//引入基础配置const baseConfig = require('./base.config.js')module.exports = webpackMerge(baseConfig, { //这里面放开发或者生产的配置})
第四步在package.json中进行如下配置
//生产打包"build": "webpack --config ./build/prod.config.js",//本地运行"dev": "webpack-dev-server --config ./build/dev.config.js"
npm run 对应的命令即可
Vue-cli脚手架
什么是Vue CLI
如果只是写几个Vue 的Demo,那么不需要Vue CLI
如果在开发大型项目,那么需要,并且必然需要使用Vue CLI
Vue CLI被称为脚手架,全称为Command Line Interface 命令行界面,可以在创建项目时自动生成一些配置
使用前提
- Node.js
- webpack
使用Vue cli
安装vue cli 3
cnpm install -g @vue/cli
安装vue cli 2
cnpm install @vue/cli-init
Vue cli2初始化项目
vue init webpack my-project
1.build文件夹:打包配置的文件夹 1.1 webpack.base.conf.js :打包的核心配置 1.2 build.js:构建生产版本,项目开发完成之后,通过build.js打包(加载base与prod,读取完之后通过webjpack命令对项目进行打包) 1.3 webpack.prod.conf.js:被build.js调用,webpack生产包的一个配置。基础代码都在1.1里面写,1.3是对1.1的扩展与补充 1.4 dev-client.js:热更新的插件,进行对客户端进行重载 1.5 dev-server.js:服务器。(背后的原理是启动一个express框架,这是一个基于node做的一个后端框架,后端框架可以在前端起一个服务) 1.6 vue-loader.conf.js:被base加载, 1.7 utils.js:工具类,公共的配置2.config文件夹:打包的配置,webpack对应的配置 2.1 index.js:可与1.1合并成一个文件,但由于spa想做一个清晰的架构,因此把它们拆分开了3.node_modulues:项目的安装依赖4.src文件夹:开发项目的源码 4.1 App.vue : 入口组件5.static文件夹:静态资源,图片
其他配置
Runtime-compiler和Runtime-only的区别
区别在src下的main.js中
Runtime-compiler :
new Vue({ el: '#app', components: { App }, template: '<App/>'})
执行过程为template -> ast -> render -> vdom -> UI
Runtime-only :
new Vue({ el: '#app', render: h => h(App)})
执行过程为 render -> vdom -> UI
使用runtime-only性能更高,代码量更少
render函数中调用的是createElement函数,可以直接传入组件,在构建项目时会把所有的template都渲染成render函数,所以不需要compiler
Vue cli3初始化项目
vue-cli3的特点:0配置,vue-cli3移除了build和config等目录;提供了可视化配置;移除了static文件夹
vue create mmy-project
在vue-cli3中的main.js写法与之前有些不同,这里没有使用el来获取div,而是使用生命周期函数mount来进行绑定
new Vue({ render: h => h(App),}).$mount('#app')
Vue cli3的配置
可以启动配置服务vue ui通过图形化界面进行配置,也可以在下面文件夹中找到配置
node_modules-@vue--cli-service---lib----Service.js---webpack.config.js
如果需要修改配置,则需要在项目根目录下创建vue.config.js文件,在内部进行配置导出
module.exports = { //配置内容}
箭头函数中的this
箭头函数中的this引用的事最近作用域中的对象,从内到外一层层查找
Vue Router
认识路由
路由是网络工程里面的术语
后端路由: 一个页面对应一个网址,URL发送到服务器,服务器根据正则进行匹配,交给Controller进行处理,处理后生成HTML或数据返回给前端
后端路由的缺点: 整个页面模块是由后端人员来开发的,前端开发人员如果需要开发页面,需要通过后端语言来编写页面代码,而且通常页面数据和逻辑会混在一起
前后端分离: 后端只负责提供数据,不负责任何阶段的内容
前端渲染: 浏览器中显示的网页中的大部分内容,都是由前端写的js代码在浏览器中执行,最终渲染出来的网页
SPA页面
SPA:但页面富应用,整个网页只有一个html页面,其实最主要的特点就是在前后端分离的基础上加了一层前端路由,改变url,页面不进行整体的刷新
URL的hash和HTML5的history
通过浏览器控制台location.hash来修改hash值时,浏览器不会重新加载,当hash改变时,会根据在vue-router的配置进行更新页面
HTML5的history.pushState()也可以改变url而不重新加载,history.back()可以返回之前的url;使用hisotry.replaceState()可以替换当前的url,由于时替换所以不能通过history.back()返回;使用history.go(-2)可以退回到之前的第二个url
认识vue-router
目前三大主流框架都有自己的路由实现
-
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用
-
可以访问其官网网站对其进行学习:https://router.vuejs.org/zh/
vue-router是基于路由和组件的
- 路由用于设定访问路径,将路径和组件映射起来
- 在vue-router的单页面应用中,页面的路径的改变就是组件的切换
vue-router的使用
路由基础配置
首先需要使用npm安装vue-router,如果使用脚手架安装过则不需要再安装
cnpm install vue-router --save
在src目录下创建router文件夹,创建index.js文件进行如下配置
import Vue from 'vue'//1.导入vue-routerimport Router from 'vue-router'//2.使用vue-router插件Vue.use(Router)//导出vue-router配置export default new Router({ //3.配置路由和组件之间的关系 routes: [ ]})
还需要再main.js中传入到Vue实例,如下
import Vue from 'vue'import App from './App'//如果导出的是目录,会自动找到文件夹下的index.js文件import router from './router'new Vue({ el: '#app', //传入Vue实例中 router, render: h => h(App)})
路由映射关系配置
首先需要创建两个组件,并导出,default导出或设置导出的name都可以
export default { name: 'Home', data () { return { } }}
第二步在router文件夹中的index.js导入两个组件
import Vue from 'vue'//1.导入vue-routerimport Router from 'vue-router'//导入两个组件import Home from '@/components/Home'import About from '@/components/About'//2.使用vue-router插件Vue.use(Router)//导出vue-router配置export default new Router({ //3.配置路由和组件之间的关系 routes: [ { path: '/', name: 'home', component: Home }, { path: '/home', name: 'home', component: Home }, { path: '/about', name: 'about', component: About } ]})
第三步,在App.vue中使用<router-link>
和<router-view>
来进行路由的切换
<template> <div id="app"> <img src="./assets/logo.png"> <router-view/> <router-link to="/home">首页</router-link> <router-link to="/about">关于</router-link> </div></template>
其他配置
设置默认路径
{ path: '*', redirect: '/home' }
使用history模式(默认是hash模式,有#
的)
mode: 'history'
router-link补充
之前使用过router-link的to属性,可以跳转到指定的路径
router的tag属性
当需要将router-link渲染成除a标签之外的其他标签时使用此属性
<router-link to="/home" tag="button">首页</router-link><router-link to="/about">关于</router-link>
router的replace属性
在需要禁用返回按钮时可以加上此属性
<router-link to="/home" tag="button" replace>首页</router-link><router-link to="/about">关于</router-link>
router的active-class属性
router-link点击之后会赋予一个默认的class属性router-link-active
,使用active-class可以自定义class的值
可以直接在router-link标签上加上active-class属性
<router-link to="/home" tag="button" replace active-class="active">首页</router-link> <router-link to="/about" tag="button" replace active-class="active">关于</router-link>
也可以在路由导出时(router/index.js)加上linkActiveClass属性
linkActiveClass: 'active'
通过代码跳转路由
通过代码修改路径(这里同一个按钮点击两次会报错)
<template> <div id="app"> <img src="./assets/logo.png"> <router-view/> <!-- <router-link to="/home" tag="button" replace >首页</router-link> <router-link to="/about" tag="button" replace >关于</router-link> --> <button @click="homeClick">首页</button> <button @click="aboutClick">关于</button> </div></template><script>export default { name: 'App', methods: { homeClick(){ // this.$router.push('/home') this.$router.replace('/home') console.log("home"); }, aboutClick(){ // this.$router.push('/about') this.$router.replace('/about') console.log("about"); } }}</script>
动态路由的使用
在访问某个路径时,在路径传入参数,来显示不同的内容,称为动态路由
在配置路由的时候,原本的路径加上/:参数名
{ path: '/user/:userId', name: 'user', component: User }
在router-link的 to中进行如下配置,这里的userId是一个定义好的变量
<router-link :to="'/user/'+userId" tag="button" replace >用户</router-link>
使用的时候在对应vue组件中进行如下配置
<h2>{{$route.params.userId}}</h2>
或定义一个计算属性
computed: { userId(){ return this.$route.params.userId } }
打包文件的解析
打包后的文件会将css文件分离,js文件分为三种,首先是以app开头的js是业务代码,以manifest开头的js是作为底层的支撑,以vendor开头的js是所依赖的包,如vue等
路由的懒加载
原来的写法
routes: [ {//如果不是以上路径,则冲定向到/home path: '*', redirect: '/home' }, { path: '/home/:id', name: 'home', component: Home }, { path: '/home', name: 'home', component: Home }, { path: '/about', name: 'about', component: About }, { path: '/user/:userId', name: 'user', component: User } ]
懒加载的写法,这种写法会在访问到路由的时候加载相关的文件
routes: [ {//如果不是以上路径,则冲定向到/home path: '*', redirect: '/home' }, { path: '/home/:id', name: 'home', component: () => import ('@/components/Home') }, { path: '/home', name: 'home', component: () => import ('@/components/Home') }, { path: '/about', name: 'about', component: () => import ('@/components/About') }, { path: '/user/:userId', name: 'user', component: () => import ('@/components/User') } ]
路由的嵌套
使用嵌套路由,首先需要创建两个子组件,然后在router下的index.js中进行配置
{ path: '/home', name: 'home', children: [ {//设置默认路径 path: '', redirect: 'child1' }, { path: 'child1', component: () => import ('@/components/Child1') }, { path: 'child2', component: () => import ('@/components/Child2') } ], component: () => import ('@/components/Home') },
这里由于配置的是home的子组件,所以要在Home.vue中添加<router-view>
和<router-link>
<template> <div> <h2>这里是首页</h2> <h2>{{$route.params.id}}</h2> <router-link to="/home/child1">吃了点1</router-link> <router-link to="/home/child2">吃了点2</router-link> <router-view></router-view> </div></template>
运行即可
路由参数传递
之前参数的传递是通过params的方式,这次的参数传递通过query,并且作为对象传递
参数传递需要在router-link进行配置
<router-link :to="{path: '/profile', query: {name: 'see' , age: 12}}" tag="button" replace >档案</router-link>
接收参数时在对应页面进行如下配置
<template> <div> <h2>Profile组件</h2> <h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2> </div></template>
button按钮发送请求传参
goProfile() { this.$router.push({ path: '/profile', query: { name: 'sea', age: 13 } }) }
$route
和$router
的区别
所有的组件都继承自Vue的原型
$router
是VueRouter的一个对象,通过Vue.use(VueRouter)和Vu构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由,包含了许多关键的对象和属性。$route
是一个跳转的路由对象,每一个路由都会有一个$route
对象,是一个局部的对象,可以获取对应的name,path,params,query等
vue-router全局导航守卫
在想要跳转页面的同时修改页面title时,可以通过生命周期函数进行修改
create(){ document.title = '首页'}
但这样做坏处在于如果有多个界面,则需要对每个页面进行修改
全局导航守卫跳转(路由钩子函数)
在router/index.js进行配置,给每个组件添加meta->title属性
const router = new Router({ routes: [ {//如果不是以上路径,则冲定向到/home path: '*', redirect: '/home' }, { path: '/home/:id', name: 'home', component: () => import ('@/components/Home') }, { meta: { title: '首页' }, path: '/home', name: 'home', children: [ {//设置默认路径 path: '', redirect: 'child1' }, { path: 'child1', component: () => import ('@/components/Child1') }, { path: 'child2', component: () => import ('@/components/Child2') } ], component: () => import ('@/components/Home') }, { meta: { title: '关于' }, path: '/about', name: 'about', component: () => import ('@/components/About') }, { meta: { title: '用户' }, path: '/user/:userId', name: 'user', component: () => import ('@/components/User') }, { meta: { title: '档案' }, path: '/profile', component: () => import ("@/components/Profile") } ], mode: 'history', linkActiveClass: 'active'})router.beforeEach((to, from, next) => { //获取点击组件的title属性赋值到当前title document.title = to.matched[0].meta.title; next();})
Router的函数
beforeEach(guard: NavigationGuard): Function
beforeResolve(guard: NavigationGuard): Function
afterEach(hook: (to: Route, from: Route) => any): Function
push(location: RawLocation): Promise<Route>
replace(location: RawLocation): Promise<Route>
push(
location: RawLocation,
onComplete?: Function,
onAbort?: ErrorHandler
): void
replace(
location: RawLocation,
onComplete?: Function,
onAbort?: ErrorHandler
): void
go(n: number): void
back(): void
forward(): void
match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route
getMatchedComponents(to?: RawLocation | Route): Component[]
onReady(cb: Function, errorCb?: ErrorHandler): void
onError(cb: ErrorHandler): void
addRoutes(routes: RouteConfig[]): void
addRoute(parent: string, route: RouteConfig): void
addRoute(route: RouteConfig): void
getRoutes(): RouteRecordPublic[]
keep-alive和vue-router
将router-view标签放在keep-alive标签内,可以保证router-view不会被频繁创建和销毁
实现组件跳转出在跳转回之后,子组件保持离开前的路径
data () {
return {
path: '/home/child1'
}
},
activated() {
console.log(this.path);
this.$router.push(this.path)
},
beforeRouteLeave(to, from ,next) {
console.log(this.$route.path)
this.path = this.$route.path
next()
}
keep-alive标签有两个重要的属性
- include 包含
- exclude 不包含
用法:(不要加空格)
<keep-alive exclude="Profile,User" include="Home,About"> <router-view/></keep-alive>
案例:TabBar实现
项目中需要注意的点:
- 这里使用的vue版本如下,可以保证正常运行,如果使用过高的版本可能会报错
"core-js": "^3.6.5","vue": "^2.5.2","vue-router": "^3.0.1","vue-template-compiler": "^2.6.14"
- 在使用插槽时使用了新指令
v-slot
的写法
<template v-slot:item-icon > <div> <img src="@/assets/img/tabbar/Home.png" alt=""> </div></template>
- 要注意模块拆分的思想,不应该把代码写到App.vue中,App.vue只做最基本的引用
- 在开发过程中,每个小模块都放到views中,并且新建文件夹存储,保证该模块的代码只放在这个文件夹
- 脚手架4中自带了alias,使用@作为src目录使用
- 在写样式时,
<style>
标签不应该有scoped
属性,否则可能会导致插槽的样式失效
https://gitee.com/codekitty_lee/vue-study/tree/develop/vue-study04/tabbar
Promise
Promise是ES6中非常重要和好用的特性,它是一个异步变成的一种解决方案
new Promise((resolve,reject)=> { setTimeout(()=>{ reject("error message") },1000)}).then((data)=>{ console.log(data);}).catch((err)=>{ console.log(err);})
Promise的三种状态
- pending 等待状态
- fulfill 满足状态
- reject 拒绝状态
Promise的另一种写法,这里then中传入了两个参数,两个参数都是函数,成功会调前面的函数,失败调后面的函数
new Promise((resolve,reject)=> { setTimeout(()=>{ reject("error message") },1000)}).then((data)=>{ console.log(data);},(err) =>{ console.log(err);})
Promise链式调用并对数据进行处理
//Promise链式调用,并对数据进行处理new Promise((resolve,reject)=> { setTimeout(()=>{ resolve("aaa") },1000)}).then((data)=>{ console.log("hello world") data+="bbb" return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(data) },1000) })}).then((data)=>{ console.log("hello Vue") data+="ccc" return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(data) },1000) })}).then((data)=>{ console.log("hello Java") console.log(data);})//上面方法的简写new Promise((resolve,reject)=> { setTimeout(()=>{ resolve("aaa") },1000)}).then((data)=>{ console.log("hello world") data+="bbb" return Promise.resolve(data);}).then((data)=>{ console.log("hello Vue") data+="ccc" return Promise.resolve(data);}).then((data)=>{ console.log(data);})//继续简写,省略掉Promise.resolvenew Promise((resolve,reject)=> { setTimeout(()=>{ resolve("aaa") },1000)}).then((data)=>{ console.log("hello world") data+="bbb" return data;}).then((data)=>{ console.log("hello Vue") data+="ccc" return data;}).then((data)=>{ console.log(data);}).catch(err =>{//捕获异常,如果在链式调用时出现异常会直接跳到这里 console.log(err)})
Promise的all方法
promise的all方法中一般传入一个数组,数组中是多个函数,多个函数都返回结果之后才会走后面的then方法
Promise.all([ new Promise(resolve => { setTimeout(()=>{ resolve("result1"); },1000) }), new Promise(resolve => { setTimeout(()=>{ resolve("result2"); },1000) })]).then((result)=>{ console.log(result);})
Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理模式
安装Vuex依赖
npm install vuex --save
基本配置
import Vue from 'vue'import Vuex from 'vuex'//1.安装插件Vue.use(Vuex)//2.创建对象const store = new Vuex.Store({ state: { counter: 1000 }, mutations: { }, actions: { }, getters: { }, modules: { }})export default store
注册到main.js中
import Vue from 'vue'import App from './App'import store from './store'Vue.config.productionTip = false/* eslint-disable no-new */new Vue({ el: '#app', store, render: h => h(App)})
使用
<template> <div id="app"> <img src="./assets/logo.png"> <h2>{{$store.state.counter}}</h2> <button @click="$store.state.counter++">plus</button> <button @click="$store.state.counter--">minus</button> <router-view/> </div></template><script>export default { name: 'App'}</script><style></style>
在这里可以直接使用$store.state.counter++
对Vuex的内容进行修改,但是官方不建议这样做
官方建议通过Mutations对State进行修改,如果有异步操作,可以通过Actions发送异步请求
通过mutations发送请求
plus(state){ state.counter++;},minus(state){ state.counter--;}
plus(){ this.$store.commit("plus");},minus(){ this.$store.commit("minus");}
State单一状态树
单一状态树就是把所有数据都放在一个Vuex实例里面,方便后期维护
Getters基本使用
<h2>{{$store.getters.powerCounter}}</h2> <h2>{{$store.getters.more20stu}}</h2> <h2>{{$store.getters.moreAgeStu(19)}}</h2>
state: { counter: 1000, students: [ {id: 1,age: 18, name: 'me'}, {id: 2,age: 21, name: 'he'}, {id: 3,age: 25, name: 'she' } ]},mutations: { plus(state){ state.counter++; }, minus(state){ state.counter--; }},actions: {},getters: { powerCounter(state){ return state.counter * state.counter }, more20stu(state){ return state.students.filter(s => s.age >20) }, more20stuLength(state,getters){ return getters.more20stu.length }, moreAgeStu(state){ return age => { return state.students.filter(s => s.age >age) } }}
mutations的携带参数
plus5(){ this.$store.commit('plus',5);},minus5(){ this.$store.commit('minus',5);}
在mutations的方法中除了state再增加一个参数
plus(state,count){ if(count==null){ state.counter++; }else{ state.counter+=count; }},minus(state,count){ if(count==null) { state.counter--; }else{ state.counter-=count; }}
传递对象并加入到数组
push(){ const stu = {id:10,age:50,name:'black'} this.$store.commit('push',stu);}
push(state,stu){ state.students.push(stu);}
mutations的提交风格
可以在commit中入一个对象,其中type指向mutations中的方法
this.$store.commit({ type: 'push', stu});
数据的响应式原理
update(state){ //直接使用无法添加元素 state.info['address'] = 'los angeles'; //使用Vue.set可以将元素添加到响应式系统 Vue.set(state.info,'address','los angeles'); //直接使用无法将元素从页面删除 delete state.info.age //使用Vue.delete可以将元素删除同时将元素从页面去除 Vue.delete(state.info,'age');}
vuex-actions的使用
mutations中不能进行异步操作,如果使用可能会造成数据错误,进行异步操作vuex提供了actions
push(){ const stu = {id:10,age:50,name:'black'} this.$store.dispatch('aUpdate',stu)},
actions: { aUpdate(context,stu){ setTimeout(() =>{ context.commit('push',stu) },2000) } }}
结合promise
push(){ const stu = {id:10,age:50,name:'black'} this.$store.dispatch('aUpdate',stu).then((res)=>{ console.log('调用完成'); console.log(res); })}
actions: { aUpdate(context,stu){ return new Promise((resolve,reject) => { setTimeout(() =>{ context.commit('push',stu) },2000) resolve('213123'); }) } }}
Vuex的modules
由于Vuex只建议使用单一状态树,所以在需要将一些数据抽离出来时可以使用modules来进行划分,每个模块可以有自己的state、getters、mutations等
注意:
- 在调用模块中的state要使用state.模块名.属性
- 在模块中调用actions时,actions的commit只会提交到当前模块调用当前模块的mutations中的方法
axios框架的基本使用
axios的基本使用
import axios from 'axios'createApp(App).mount('#app')axios({ method: 'get', // url: 'httpbin.org' url: 'http://123.207.32.32:8000/home/multidata'}).then(data =>{ console.log(data)})
默认发送get请求
get请求使用params传参,post请求使用data传参
axios发送并发请求
//两个请求都响应才返回结果axios.all([ axios('http://123.207.32.32:8000/home/multidata',{ }), axios('http://123.207.32.32:8000/home/multidata',{ })]).then(results => { console.log(results)})
axios的配置信息
//全局配置axios.defaults.baseURL = 'http://123.207.32.32:8000'axios.defaults.timeout = 5
axios的实例和模块封装
创建对应的axios实例
export function instance1(config){//1.创建axios的实例const instance = axios.create({baseURL: 'http://localhost:8080',timeout: 3000})return new Promise((resolve,reject) => {instance(config).then(res => {resolve(res)}).catch(rej =>{reject(rej)})})}
优化:
//创建对应的axios实例export function instance1(config){//1.创建axios的实例const instance = axios.create({baseURL: 'http://localhost:8080',timeout: 3000})return instance(config)// 因为axios返回的本身就是一个Promise}
调用
<template> <div class="HelloWorld"> <h2>{{message}}</h2> </div></template><script> import {instance1} from "../../network/request";export default { name: 'HelloWorld', data() { return { message: '' } }, created() { instance1('/test') .then(res =>{ console.log(res); this.message = res.data.message }).catch(err =>{ console.log(err) }) } };</script>
axios的拦截器的使用
axios提供了四种拦截(请求成功、请求失败、响应成功、响应失败)
使用:
export function instance1(config){
//1.创建axios的实例
const instance = axios.create({
baseURL: 'http://localhost:8080',
timeout: 3000
})
//2.axios的拦截器
//2.1请求拦截
instance.interceptors.request.use(config =>{
// console.log(config);
//拦截器的应用场景
//1.如果config中的信息不符合服务器的要求
//2.每次发送网络请求时希望在页面加一个转圈的图标
//3.某些网络请求时必须携带一些特殊的信息,比如登录需要token
return config
}, error => {
console.log(error);
})
//2.2响应拦截
instance.interceptors.response.use(result => {
console.log("result");
console.log(result);
return result;
}, error => {
console.log(error);
})
return instance(config)
}
项目开发
1.划分目录结构
src
-assets(资源)
-common
--const
-components(公共组件)
--common(完全公共的组件,可以放到下个项目中)
--content(对于项目来说公共的组件)
-network(网络)
-router(路由)
-store(vuex)
-views(页面)
2.引用css文件
引入两个css文件,base.css和normalize.css
3.配置别名
根目录下创建vue.config.js文件
module.exports = {
configureWebpack: {
resolve: {
alias: {
'@': 'src', //默认已经配置好的一个别名
'assets': '@/assets',
'network': '@/network',
'common': '@/common',
'components': '@/components',
'views': '@/views'
}
}
}
}
4.编码风格
根目录下创建.editorconfig文件
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
5.将之前写的tab-bar拷贝过来
这里出现一个bug,启动时报:
Errors compiling template:
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
原因是拷贝时没有注意到App.vue中有两个元素,router-view和tab-bar组件,要在这两个外面套一层div
6.编写nav-bar
在common中创建nav-bar组件
<template>
<div class="nav-bar">
<div class="left">
<slot name="left"></slot>
</div>
<div class="center">
<slot name="center"></slot>
</div>
<div class="right">
<slot name="right"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Navbar'
};
</script>
<style scoped>
.nav-bar{
display: flex;
height: 44px;
text-align: center;
line-height: 44px;
background-color: #ff5777;
}
.left, .right {
width: 60px;
}
.center {
flex: 1;
}
</style>
在home中引用组件
<template>
<div id="home">
<nav-bar class="home-nav">
<div slot="center">购物街</div>
</nav-bar>
</div>
</template>
<script>
import NavBar from "components/common/navbar/NavBar.vue";
export default {
name: "Home",
components: {
NavBar
}
}
</script>
<style scoped>
#home {
/*padding-top: 44px;*/
height: 100vh;
position: relative;
}
.home-nav {
background-color: var(--color-tint);
color: #fff;
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 9;
}
.tab-control {
position: sticky;
top: 44px;
z-index: 9;
}
.content {
overflow: hidden;
position: absolute;
top: 44px;
bottom: 49px;
left: 0;
right: 0;
}
</style>
这里出现一个bug,css样式不生效,经过查找发现,浏览器中html页面中渲染出了nav-bar标签,从这得知,是组件引入问题,从而发现Home.vue中组件关键字使用的是comments而不是components >.<
7.将之前写的axios模块复制过来发送一个网络请求进行测试可用性
复制之前的request.js文件,并按照模块创建对应的网络请求js,这里创建的是home.js,引用时引用request中导出的方法,直接使用方法传入对应url然后.then(res => {})即可获取返回数据
未完待续。。。。。。