vue
vue2 版本文档: https://cn.vuejs.org/v2/guide/
vue3 版本文档: https://v3.cn.vuejs.org/guide/introduction.html
这里使用v2版本
安装vue
具体安装教程建议看官方文档。
一般开发环境中使用 vue.js ;
生产环境中使用 vue.min.js,这是一个更小的构建,可以带来比开发环境下更快的速度体验。
html 页面中直接使用 script 引入
-
下载 vue.js 文件 本地引入
-
CDN引入
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.8/dist/vue.js"></script>
脚手架中使用 npm 安装
npm install vue
实例化vue对象
一个实例化vue对象,有一个el属性,只对该属性对应的标签生效。如果还有其他标签想使用vue,可以再实例化新的vue对象。
<div id="app"></div>
<script>
let vm = new Vue({
// 给id="app"的标签绑定此实例中的属性与方法
el: '#app',
// data 中定义数据属性
data:{
msg: "杨稀饭",
age: 18,
hobby: "<h3>唱歌,戳麻将</h3>",
homework: {
"math":"6道应用题",
"chinese":"一个小作文",
}
},
// methods 中定义方法
methods:{
add (x,y) { return x+y },
multi (x,y) { return x*y },
}
})
</script>
模板语法
在el对应的标签中可以使用模板语法 {{ }} 获取vue实例中定义的属性与方法,也可以进行字段拼接,简单的数字运算,以及方法调用,三元表达式等。
<div id="app">
<!-- 模板语法 {{ }} -->
{{ msg + age + hobby }}
{{ age-2 }}
{{ homework.math }}
{{ add(1,4) }}
{{ multi(1,4) }}
{{ 0 ? 'YES' : 'NO' }}
</div>
<script>
let vm = new Vue({
// 给id="app"的标签绑定此实例中的属性与方法
el: '#app',
// data 中定义数据属性
data:{
msg: "杨稀饭",
age: 16,
hobby: "<h3>唱歌,戳麻将</h3>",
homework: {
"math":"6道应用题",
"chinese":"一个小作文",
}
},
// methods 中定义方法
methods:{
add (x,y) { return x+y },
multi (x,y) { return x*y },
}
})
</script>
指令
指令 (Directives) 是带有 v-
前缀的特殊 attribute。指令中调用的属性名直接写成字符串的形式。指令中直接写值而非属性名,将值再加一层引号标识。
例如:
<!-- 直接添加名为active的类 -->
<div class="box" v-bind:class="'active'"></div>
<!-- 添加一个类,类名为vue实例中active属性对应的值 -->
<div class="box" v-bind:class='active'></div>
v-text 与 v-html
v-text='msg' 给该标签插入文本,文本内容为msg属性对应值
v-html="hobby" 给该标签插入html标签,标签代码为hobby属性对应的内容
<div id="app">
<span v-text='msg'></span>
<span v-text='age'></span>
<span v-html="hobby"></span>
</div>
<script>
let vm = new Vue({
// 给id="app"的标签绑定此实例中的属性与方法
el: '#app',
// data 中定义数据属性
data:{
msg: "杨稀饭",
age: 16,
hobby: "<h3>唱歌,戳麻将</h3>",
},
})
</script>
v-show 与 v-if v-else
v-show 相当于css的display属性,true 与 false分别表示展示与隐藏
v-if true 与 false分别表示添加标签与删除标签,与v-show本质上是不一样的。
对于isShow属性会频繁切换的状况最好使用 v-show 来减少渲染开销。
<div id="app">
<span v-text='msg' v-show="isShow"></span>
<span v-text='age' v-if="isShow"></span>
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
msg: "杨稀饭",
age: 16,
isShow: true
},
})
</script>
当isShow为true时,两个标签正常渲染
当isShow为false时,v-show对应标签添加 display: none,v-if 对应标签被移除
v-else 一般与 v-if 联用
<div id="app">
<span v-if="isShow">hello vue !!!</span>
<span v-else>see u vue !!!</span>
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
isShow: false
},
})
</script>
v-bind 与 v-on
v-bind 用来给html标签设置属性
- 直接将标签要设置的属性内容放到 vue的属性value中,再引用这个属性key。
- style 属性可以像css一样直接写,但要采用驼峰式命名 v-bind:style="
<div id="app">
<div id="box" v-bind:style="box"></div>
<div v-bind:style="{backgroundColor:'red',width: '100px',height: '100px'}"></div>
<a v-bind:href="pic">图片</a>
<img v-bind:src="pic" alt="">
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
box:"width: 100px;height: 20px;background-color: red;",
pic:"https://images.cnblogs.com/cnblogs_com/huandada/2032710/t_210917084707%E6%98%9F%E4%BA%91.jpg"
},
})
</script>
-
类属性有两种绑定方式:
-
对象语法动态绑定
前面是类名active,后面的bool值 true/false决定是否添加此类。该bool可以设置为vue的一个数据属性,再动态调整该属性的bool值。
<div v-bind:class="{active:bool}" ></div>
-
数组语法动态绑定class
<!-- 直接添加名为active test 的两个类 --> <div class="box" v-bind:class="['active','test']"></div> <!-- 添加两个类,类名分别为vue实例属性active与test对应的值 --> <div class="box" v-bind:class="[active,test]"></div>
-
v-on 用来给标签添加事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="vue.min.js"></script>
<style>
.box {
width: 100px;
height: 100px;
background-color: green;
}
.active {
background-color: yellow;
}
</style>
</head>
<body>
<div id="app">
<!-- 默认box类为绿色,加上active类变黄色。 效果:鼠标移上去变黄,移走变绿 -->
<div class="box" v-bind:class="{active:isActive}" v-on:mouseenter="handerIn" v-on:mouseleave="handerOut"></div>
<!-- 按钮点击,改变div变色 -->
<button v-on:click="changeColor">click</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
isActive:false
},
methods:{
handerIn(){
this.isActive = true;
},
handerOut(){
this.isActive = false;
},
changeColor(){
this.isActive = !(this.isActive);
}
}
})
</script>
</body>
</html>
缩写:
v-bind: 可以缩写为冒号 :
v-on: 可以缩写为@
<div class="box" v-bind:class="{active:isActive}" v-on:mouseenter="handerIn" v-on:mouseleave="handerOut">
缩写为:
<div class="box" :class="{active:isActive}" @mouseenter="handerIn" @mouseleave="handerOut"></div>
v-for
v-for 的优先级很高,因为要先for循环渲染出标签。
for循环渲染标签时,一定要给每一个标签绑定key值,可以是index,也可以是数据的id,绑定之后当你某条数据改变时,整个for循环不会全部重新渲染,而是只渲染改变的地方。
for循环数组 (value,index)
<div id="app">
<ul v-if="status === 'ok'">
<li v-for="(user,index) in users" :key="user.id">
{{ user.id}} - 姓名:{{ user.name }} - 年龄:{{ user.age}}
</li>
</ul>
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
status: 'ok',
users: [
{id:1,name:"aaa",age:17},
{id:2,name:"bbb",age:20},
{id:3,name:"ccc",age:30},
{id:4,name:"ddd",age:40},
],
},
})
</script>
效果
for 循环对象 (value,key,index)
<div id="app">
<li v-for="(value,key,index) in person" :key="index">
{{ key }}: {{ value }}
</li>
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
person: {
'tom':"186 帅哥 喜欢打篮球",
'lisa':"162 美女 喜欢rap 唱跳",
'yxf':"30 喵仔超人 喜欢睁一只眼睡觉"
}
},
})
</script>
watch 侦听器
属性变化时执行侦听器中该属性对应的函数,侦听器中还能获取到属性更新前后的值。
<div id="App">
{{name}}
</div>
<script>
let vm = new Vue({
el: '#App',
data(){return {
name: 'yxf'
}},
watch:{
// name: 表示对name属性进行侦听,函数中第一个参数为新值,第二个参数为旧值
// 第一种写法
name: function(val,oldval){
console.log('name新值',val)
console.log('name旧值',oldval)
}
// 第二种写法
// name:{
// handler: function(val,oldval){
// console.log('name新值',val)
// console.log('name旧值',oldval)
// }
// }
}
})
</script>
接下来演示使用watch监听,改变按钮颜色
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="vue.min.js"></script>
</head>
<style>
.success {
background-color: blue;
}
.fail {
background-color: red;
}
</style>
<body>
<div id="app">
<div v-text="msg"></div>
<button class="success" :class="{fail:isFail}" @click="handleClick">click</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data:{
msg:"success",
isFail:false,
},
methods:{
// 按钮对应的点击事件,点击改变msg的值 success/fail
handleClick(){
if (this.msg == 'fail'){
this.msg = 'success';
}
else {
this.msg = 'fail';
}
}
},
watch:{
// 侦听msg属性,当其改变时,根据值的不同决定是否给按钮添加(红色的)fail类
msg: function(args){
console.log(args) // 当msg属性值改变时,会自动传入改变后的值 这里的args === this.msg
if (args == 'fail'){
this.isFail = true
}
else {
this.isFail = false
}
},
// 侦听多个属性,直接往下加就行
isFail: function(v){console.log(v)},
}
})
</script>
</body>
</html>
侦听可变对象的坑
上面我们演示例子都是侦听的不可变类型,字符串或者布尔值。
监听不可变类型字符串,没有问题。因为字符串改变时,会再开一个内存地址存放新值,所以新旧值对应不同的内存地址。
监听可变类型,例如 对象/数组 时,就会有问题:
- 数组长度改变,能触发监听回调函数执行,获取不到旧值,旧值打印出来也是新的值,是因为数组元素改变,但其内存地址没有改变,获取旧值和新值都是走的同一个内存地址。
- 数组长度不变,只改变元素,监听不到,深度监听也不行,页面上对应的值也不会改变,这是一个坑!!!!
<div id="App">
{{students}}
</div>
<script>
let vm = new Vue({
el: '#App',
data(){return {
students: ['杨稀饭','谭棉花']
}},
watch:{
// 侦听可变对象 数组
students:{
handler: function(val,oldval){
console.log('name新值',val)
console.log('name旧值',oldval)
}
}
}
})
</script>
那么只更改元素,不更改长度的情况,怎么才能被检测到更新呢?
可以配置显性更改 $set ,告诉组件,我要更改某个属性
组件实例.$set(属性, 数组的索引或对象的key, 新的值)
<div id="App">
{{students}}
<button @click="clickhandle">点我更改第一个数组的第一个元素</button>
</div>
<script>
let vm = new Vue({
el: '#App',
data(){return {
students: ['杨稀饭','谭棉花']
}},
methods: {
clickhandle() {
console.log(this)
// 显性更改student索引为 0 的元素为 yxf
this.$set(this.students,0,'yxf');
// vm.$set(this.students,0,'yxf');
},
},
watch:{
// 使用$set() 更新数组后,能触发这个侦听器,页面展示的students也改变
students:{
handler: function(val,oldval){
console.log('name新值',val)
console.log('name旧值',oldval)
}
}
}
})
</script>
深度侦听
深度侦听可以侦听到对象中值的改变,数组还是不行。
<div id="App">
{{students}}
<button @click="clickhandle">点我更改第一个数组的第一个元素</button>
</div>
<script>
let vm = new Vue({
el: '#App',
data(){return {
// student为一个对象
students: {s1:'yxf',s2:'tmh'}
}},
methods: {
clickhandle() {
console.log(this)
this.students['s1']='yyy'
},
},
watch:{
students:{
handler: function(val,oldval){
console.log('name新值',val)
console.log('name旧值',oldval)
},
// 打开深度侦听
deep: true
}
}
})
</script>
(播放器)
<!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>Document</title>
<script src="vue.min.js"></script>
<style>
/* 正在播放的歌曲样式类 */
.playing{
background: burlywood;
}
</style>
</head>
<body>
<div id="musicPlayer">
<!-- audio标签 mp3 播放器; autoplay选择歌曲后自动播放;@ended一首歌曲结束后调用此方法 -->
<audio controls autoplay @ended="nextHandler" :src="musicDir+songs[currentIndex].songName" ></audio>
<span>正在播放: {{songs[currentIndex].songName}}</span>
<br><br>
<ul>
<!-- 正在播放的歌曲添加 playing类样式;点击歌曲信息播放该歌曲 -->
<li v-for="(song,index) in songs" :key="index" @click="handlePlay(index)" :class="{playing:index==currentIndex}">
<h3>作曲: {{ song.composer }} </h3>
<div>歌名: {{ song.songName }}</div>
<hr>
</li>
</ul>
</div>
<script>
let songs = [
{"id":1,"songName":"壹-前奏-降E大调钢琴第五十九章第二节-海顿.mp3","composer":"海顿",},
{"id":2,"songName":"贰-月光-升C小调钢琴奏鸣曲-贝多芬.mp3","composer":"贝多芬"},
{"id":3,"songName":"叁-悲歌-钢琴三重奏-拉赫玛尼诺夫.mp3","composer":"拉赫玛尼诺夫"},
{"id":4,"songName":"肆-钟-帕格尼尼大练习曲-李思特.mp3","composer":"李思特"},
{"id":5,"songName":"伍-降E大调夜曲-肖邦.mp3","composer":"肖邦"},
]
let vm = new Vue({
el: '#musicPlayer',
data: {
currentIndex: 0,
musicDir: "music/",
songs:[],
},
methods: {
// 点击歌曲信息调用的方法
handlePlay(index){
this.currentIndex = index
},
// 一首歌曲结束自动切换到下一曲,整个列表轮播
nextHandler(){
if (this.currentIndex == this.songs.length-1){
this.currentIndex = 0
console.log(this.currentIndex)
}
else { this.currentIndex ++ }
}
},
// created在实例化完成之后调用
created(){
this.songs = songs
}
})
</script>
</body>
</html>
计算属性
对于任何包含响应式数据的复杂逻辑,你不应该在指令或者模板语法中书写冗长的代码,应该使用计算属性。它会将最终的结果放到缓存,只有计算属性中的数据属性值改变时,才会重新执行计算属性中对应的方法,更新数据。
例如播放器中audio标签的src属性就有的复杂了,把它改成计算属性。
当 this.songs[this.currentIndex].songName 中的任何属性改变时,都会更新到缓存。
<!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>Document</title>
<script src="vue.min.js"></script>
<style>
.playing{
background: burlywood;
}
</style>
</head>
<body>
<div id="musicPlayer">
<!-- 调用计算属性 currentSongName -->
<audio controls autoplay @ended="nextHandler" :src="musicDir+currentSongName" ></audio>
<span>正在播放: {{currentSongName}}</span>
<br><br>
<ul>
<li v-for="(song,index) in songs" :key="index" @click="handlePlay(index)" :class="{playing:index==currentIndex}">
<h3>作曲: {{ song.composer }} </h3>
<div>歌名: {{ song.songName }}</div>
<hr>
</li>
</ul>
</div>
<script>
let songs = [
{"id":1,"songName":"壹-前奏-降E大调钢琴第五十九章第二节-海顿.mp3","composer":"海顿",},
{"id":2,"songName":"贰-月光-升C小调钢琴奏鸣曲-贝多芬.mp3","composer":"贝多芬"},
{"id":3,"songName":"叁-悲歌-钢琴三重奏-拉赫玛尼诺夫.mp3","composer":"拉赫玛尼诺夫"},
{"id":4,"songName":"肆-钟-帕格尼尼大练习曲-李思特.mp3","composer":"李思特"},
{"id":5,"songName":"伍-降E大调夜曲-肖邦.mp3","composer":"肖邦"},
]
let vm = new Vue({
el: '#musicPlayer',
data: {
currentIndex: 0,
musicDir: "music/",
songs:[],
},
methods: {
handlePlay(index){
this.currentIndex = index
},
nextHandler(){
if (this.currentIndex == this.songs.length-1){
this.currentIndex = 0
console.log(this.currentIndex)
}
else { this.currentIndex ++ }
}
},
// 计算属性 获取当前歌曲名 currentSongName
computed: {
currentSongName () {
return this.songs[this.currentIndex].songName
}
},
created(){
this.songs = songs
}
})
</script>
</body>
</html>
v-model
v-model
指令可以在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。
input
vue中input 不再使用value,而是直接用v-model,可以绑定属性,将属性值渲染到input框,更改input框中的内容,也就更改了属性值。从而实现双向数据绑定。
文本 input
<div id="App">
<label for="greeting"></label>gretting:
<input type="text" id="greeting" v-model="msg">
<h3 v-text="msg"></h3>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
msg: 'hello vue'
}
}
})
</script>
单个复选框 checkbox input
v-model 对应布尔值,true为选中,false为未选中
<div id="App">
<label for="greeting" v-text="msg"></label>
<input type="checkbox" id="greeting" v-model="msg">
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
msg: false,
}
}
})
</script>
多个复选框 checkbox input
多个 checkbox的input标签绑定到同一个 数组,则为一组复选框,勾选后,数组会获取到勾选的标签的value值。
<div id="App">
<p>爱好:</p>
<input type="checkbox" id="sport" value="sport" v-model="hobbyList">
<label for="sport">运动</label>
<input type="checkbox" id="music" value="music" v-model="hobbyList">
<label for="music">音乐</label>
<input type="checkbox" id="study" value="study" v-model="hobbyList">
<label for="study">学习</label>
<p>{{ hobbyList }}</p>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
// 数组可以为空,也可以有默认的选中值
hobbyList: ["study"],
}
}
})
</script>
单选 radio checkbox
<div id="App">
<input type="radio" id="i1" value="喜欢" v-model="pick">
<label for="i1"></label>喜欢稀饭仔
<input type="radio" id="i2" value="拒绝" v-model="pick">
<label for="i2"></label>拒绝!!!
<input type="radio" id="i3" value="观望" v-model="pick">
<label for="i3"></label>观望下下~
<p>pick: {{ pick }}</p>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
pick: ""
}
}
})
</script>
textarea
多行文本 textarea
<div id="App">
<textarea cols="30" rows="10" v-model="txt" placeholder="介绍一下自己吧~"></textarea>
<p>txt: {{ txt }}</p>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
txt: '',
}
}
})
</script>
select
select 单选
<div id="App">
<p>选择一个你想去的大城市:</p>
<select name="" id="" v-model="choose" >
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">深圳</option>
</select>
<p>choose: {{ choose }}</p>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
choose: 1,
chooseList: [1,2]
}
}
})
</script>
select 多选 需定义数组
1. 定义每个option的value
<div id="App">
<p>选择多个你想去的大城市:</p>
<select name="" id="" v-model="chooseList" multiple>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">深圳</option>
<option value="4">成都</option>
</select>
<p>chooseList: {{ chooseList }}</p>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
chooseList: [1,2]
}
}
})
</script>
- option不定义value,其文本默认为value
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<div id="App">
<p>选择多个你想去的大城市:</p>
<select name="" id="" v-model="chooseList" multiple>
<option>北京</option>
<option>上海</option>
<option>深圳</option>
<option>成都</option>
</select>
<p>chooseList: {{ chooseList }}</p>
</div>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
chooseList: ["北京","上海"]
}
}
})
</script>
修饰符
.lazy
懒监听,v-model
在每次 input
事件(即每输入一个字符都会触发input)触发后将输入框的值与数据进行同步 ,可以添加 lazy
修饰符(v-model.lazy="msg"),从而转为在 change
事件(即输入完成敲回车,或光标由框内聚焦到框外)_之后_进行同步。
<div id="App">
<label for="greeting"></label>gretting:
<input type="text" id="greeting" v-model.lazy="msg">
<h3 v-text="msg"></h3>
</div>
<script src="vue.min.js"></script>
<script>
let vm = new Vue ({
el: '#App',
data (){
return {
msg: 'hello vue'
}
}
})
</script>
.number
.number 可以自动将用户输入转为数值类型。
<input type="text" id="age_number" v-model.number="age">
.trim
提交时可以去掉用户输入内容的前后空格
<input type="text" id="age_number" v-model.trim="age">
自定义指令
Vue 提供了自定义指令的5个钩子函数:
- bind:指令第一次绑定到元素时调用,只执行一次。在这里可以进行一次性的初始化设置。
- inserted:被绑定的元素,插入到父节点的 DOM 中时调用(仅保证父节点存在)。
- update:组件更新时调用。
- componentUpdated:组件与子组件更新时调用。
- unbind:指令与元素解绑时调用,只执行一次。
自定义的指令在组件的directives中定义,自定义的指令名中不要有大写,因为可能会不生效。
<div id="App">
<!-- 绑定自定义指令 myfocus -->
<input type="text" v-myfocus>
<!-- 绑定自定义指令 v-mybgc -->
<div v-mybgc>hahha</div>
</div>
<script>
let vm = new Vue({
el: '#App',
data(){return {
}},
directives:{
// 自定义myfocus指令, 自动获取焦点
myfocus: {
inserted: function(e){
e.focus()
}
},
// 自定义mybgc指令, 设置背景颜色
mybgc: {
inserted: function(e){
e.style.backgroundColor="red"
}
},
}
})
</script>
页面加载后的效果
组件
组件是可复用的 Vue 实例,且带有一个名字。但组件和实例还有所不同:
- 实例中有el属性,data也可以写成对象式(data:{}),即实例是绑定了固定的标签;
- 组件没有el属性,data必须写成函数式,即组件是可复用的,不能使用el属性绑定固定标签。
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
局部组件
template介绍:
-
是一个字符串模板,为实例/组件定义html页面;
-
template中必须要有一个根标签,将其他标签包裹起来;
-
当实例中的template不为空时,el属性失效。
局部组件的三步操作: 声明、挂载、使用
声明一个名为App的局部组件
let App = {
data(){
return {
msg: 'from App',
greeting: 'hello,vue~'}
},
template: `
<div class="App">
<h1 v-text="msg"></h1>
<p>{{ greeting }}</p>
</div>`
}
挂载:components: { App:App, } 或 components: { App, }
使用:以标签的形式使用
// 实例 可以直接在el对应的标签中使用组件,也可以在template使用
// 虽然实例中template不为空,el绑定失效,但el还是得有
let vm = new Vue ({
el: '#App',
data (){
return {
}
},
template: `
<div id="id_vue">
<App/>
</div>`,
// 挂载组件App,挂载名与组件名一致,可以直接简写
components: {
// App:App,
App,
}
})
不仅实例中可以使用局部组件,组件中也可以使用,也是需要先挂载再在template中使用的。
全局组件
全局组件的两步操作: 声明挂载、使用,因为是面向全局的,所以声明和挂载是一步操作。
声明全局组件:Vue.component('组件名',{需要定义的options})
Vue.component('MyBtn',{
data () {
return{
msg: "click"
}
},
template: `<button>{{ msg }}</button>`
})
使用全局组件: 可以在声明后的任意组件或实例中使用全局组件
let App = {
data(){
return {
msg: 'from App',
greeting: 'hello,vue~'}
},
// 在template中引入 MyBtn 全局组件
template: `
<div class="App">
<h1 v-text="msg"></h1>
<p>{{ greeting }}</p>
<MyBtn/>
</div>`
}
通过插槽分发内容
当要父组件要像子组件传递一个内容,例如: 父组件使用了一个按钮的子组件,想在按钮上展示登录或注册信息。可以先在子组件使用slot开辟一个空间,父组件使用子组件时再将数据传递过去。
Vue.component('MyBtn',{
data () {
return{
}
},
// 使用slot开辟一个插槽
template: `
<button>
<slot></slot>
</button>`
})
let App = {
data(){
return {
msg: 'from App',
}
},
// 在template中引入 MyBtn 全局组件,并传入数据插槽中
template: `
<div class="App">
<h1 v-text="msg"></h1>
<MyBtn>登录</MyBtn>
<MyBtn>注册</MyBtn>
</div>`
}
父组件给子组件传值
子组件使用props接收传来的值,并且该值也可以作为属性调用。
let App = {
data(){
return {
title: 'hello vue',
}
},
// props 中注册标签中要接收的属性名,注册后可以直接作为此组件的属性使用
props: ['msg'],
template: `
<div class="App">
<div>{{ title }}</div>
<! 使用父组件传来的信息>
<div>{{ msg }}</div>
</div>`
}
let vm = new Vue ({
el: '#App',
data (){
return {
greeting: '这是来自你爸爸的信息',
}
},
// 给子组件的标签以绑定属性的方式传递信息
template: `
<div id="id_vue">
<App :msg='greeting'></App>
</div>`,
components: {
// App:App,
App,
}
})
子组件给父组件传值
- 父组件自定义一个事件,在使用子组件时绑定该事件;
- 子组件使用 $emit调用 父组件的自定义事件并传值。
$emit('事件名',要传的值)
例如,父组件中使用子组件的按钮,并接收子组件传来的点击次数并展示。
Vue.component('MyBtn',{
data () {
return{
times: 0,
}
},
// 给按钮添加一个点击事件,每点击一次times+1,并将times传给父组件
template: `
<button @click="clickHandle">
click
</button>`,
methods:{
clickHandle(){
this.times ++
// 调用父组件中定义的showTimes事件,并传值
this.$emit('showTimes',this.times)
}
}
})
let vm = new Vue ({
el: '#App',
data (){
return {
greeting: 'hello,你好',
times: 0,
}
},
// 给子组件的标签传递一个自定义事件showTimes,这个事件对应的函数可以接收值
template: `
<div id="id_vue">
<MyBtn @showTimes='showTimes'></MyBtn>
<div> 点击了{{ times }}次 </div>
</div>`,
methods: {
showTimes(times){
this.times = times
}
}
})
平行组件传值
此种方式适用于任意组件间传值。
- 定义一个bus对象用于定义事件与触发事件传值;
- 接收数据组件中使用 bus.$on 自定义事件;
- 发送数据组件中使用bus.$emit 触发事件
将父组件给子组件传值的例子改为平行组件传值的方式
let bus=new Vue();
Vue.component('MyBtn',{
data () {
return{
times: 0,
}
},
// 给按钮添加一个点击事件,每点击一次times+1,并将times传给另一个组件
template: `
<button @click="clickHandle">
click
</button>`,
methods:{
clickHandle(){
this.times ++
// 调用bus中定义的showTimes事件,并传值
bus.$emit('showTimes',this.times)
}
}
})
let vm = new Vue ({
el: '#App',
data (){
return {
greeting: 'hello,你好',
times: 0,
}
},
template: `
<div id="id_vue">
<MyBtn></MyBtn>
<div> 点击了{{ times }}次 </div>
</div>`,
methods: {
},
// 实例化完成后,在bus中定义事件,里面使用箭头函数 this才能指向vm,普通函数this会指向bus
created(){
bus.$on('showTimes',(times)=>{
this.times = times
})
}
})
过滤器
vue中可以自定义过滤器用于文本的格式化。过滤器可以用在两个地方:双花括号插值和 v-bind
表达式。在需要格式化的数据后添加 管道符 过滤器名。
局部过滤器
局部过滤器在局部(组件内部)设置,局部生效。
例如: 设置两个过滤器
-
字符翻转
-
时间格式化
js中处理时间可以用 Moment.js 库 http://momentjs.cn/
moment的加载
<script src="moment.js"></script>
moment的简单使用举例 .format 与 .fromNow
> moment(new Date).format('YYYY-MM-DD') '2022-04-24' > moment('2018-5').fromNow() '4 years ago' > moment('2022-2-10').fromNow() '2 months ago'
let vm = new Vue ({
el: '#App',
data(){
return {
msg: "hello vue",
nowtime: new Date()
}
},
// 使用过滤器时,管道符前面的内容作为第一个参数参入
template: `
<div class="App">
{{ msg | myReverse }}
<hr>
{{ nowtime | formatTime('YYYY-MM-DD') }}
</div>
`,
// 局部过滤器 filters 设置
filters: {
// 字符翻转
myReverse: function(val){
return val.split('').reverse().join('')
},
// 时间格式化,格式化样式可作为参数传入
formatTime: function(val,formatStr){
return moment(val).format(formatStr)
}
}
})
全局过滤器
全局过滤器设置后可以在后面的任意组件/实例中使用
// 设置全局过滤器 Vue.filter('过滤器名',函数)
Vue.filter('myReverse',function(val){
return val.split('').reverse().join('')
});
Vue.filter('formatTime',function(val,formatStr){
return moment(val).format(formatStr)
})
let vm = new Vue ({
el: '#App',
data(){
return {
msg: "hello vue",
nowtime: new Date()
}
},
// 使用过滤器时,管道符前面的内容作为第一个参数参入
template: `
<div class="App">
{{ msg | myReverse }}
<hr>
{{ nowtime | formatTime('YYYY-MM-DD') }}
</div>
`,
})
生命周期钩子
标星号的最常用
beforeCreate
created *****
组件创建之后,可以发送ajax 获取数据,实现数据驱动视图
beforeMount
mounted *****
组件挂载之后,可以获取页面的真实DOM对象进行操作
beforeUpdate
updated
activated
激活当前组件
deactivated
停用当前组件 , 与 Vue提供的内置组件keep-alive联用,让组件缓存。
beforeDestroy
destroyed ***
实例销毁后调用,可在此关闭定时器
beforeCreate 与 created
- beforeCreate 实例创建之前
- created,实例创建之后(即数据侦听、计算属性、方法、事件/侦听器的回调函数已就位),组件挂载之前被调用。可以在这里操作数据,发送ajax,实现数据驱动视图。
let App = {
data(){
return {
greeting: 'hi friend'
}
},
template: `
<div class="App">
{{greeting}}
</div>
`,
beforeCreate (){
// undefined 组件创建之前触发,greeting还未获取到值
console.log(this.greeting)
},
created (){
// 组件创建后触发,可以在这里操作数据,发送ajax,实现数据驱动视图
console.log(this.greeting) // hi friend
this.greeting = this.greeting + ' created 钩子'
},
}
beforeMount 与 mounted
- beforeMount 该组件被挂载之前触发
- mounted 该组件被挂载之后触发,可以获取渲染到页面的真实DOM 进行操作。
let App = {
data(){
return {
greeting: 'hi friend'
}
},
template: `
<div class="App">
{{greeting}}
</div>
`,
beforeMount () {
// 组件还未挂载,页面还未渲染出来
console.log(document.getElementsByClassName('App')) // 空数组
},
mounted () {
// 挂载到DOM树之后,就能获取到DOM对象
console.log(document.getElementsByClassName('App')) // HTMLCollection [div.App]
},
}
beforeUpdate 与 updated
这里的update是指更新本组件标签对应的DOM对象。
- beforeUpdate 更新DOM之前触发;
- updated 更新DOM之后触发;
let App = {
data(){
return {
greeting: 'hi friend'
}
},
// 点击button按钮更新greeting属性值,从而更新DOM标签
template: `
<div class="App">
<div :class='"greet"'>{{greeting}}</div>
<button @click='clickHandle'>click</button>
</div>
`,
methods:{
clickHandle () {
this.greeting='hello myfriend'
}
},
// beforeUpdate 在greeting属性更新之后,DOM更新之前触发
beforeUpdate () {
console.log(this.greeting) // hello myfriend
console.log(document.getElementsByClassName('greet')[0].innerText) // hi friend 还是老的greeting属性值,说明DOM标签还未更新
},
// updated在DOM更新之后触发
updated () {
console.log(this.greeting) // hello myfriend
console.log(document.getElementsByClassName('greet')[0].innerText) // hello myfriend
},
}
beforeDestroy 与 destroyed
- beforeDestroy 所在组件的实例被销毁前触发,此时实例已被卸载。一般
- destroyed 所在组件的实例被销毁后触发,此钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁,页面变成一个写死的页面了,不能被更改。可在这一步销毁定时器。
常与v-if联用,使用该组件时,给该组件标签添加v-if属性,从而实现该组件的创建与销毁。
例如:
vm实例挂载App组件,vm实例中给App标签绑定v-if时间,点击按钮创建/删除App标签。App中设置一个定时器,在组件实例被删除时,触发App的beforeDestroy 与 destroyed事件,destroyed事件中清除定时器。
let App = {
data(){
return {
count: 0,
// 将计时器定义为一个属性,方便后续删除
timer: '',
}
},
template: `
<div class="App">
<button @click='activeTime'>点击计时</button>
<div>计时器: {{ count }}秒</div>
</div>
`,
methods:{
// 点击计时按钮时触发此事件: 创建计时器并计时
activeTime () {
this.timer = setInterval(()=>{
this.count++
},1000)
}
},
beforeDestroy () {
console.log('in beforeDestroy')
},
// 组件实例被删除时触发,清除定时器
destroyed () {
console.log('in destroyed')
clearInterval(this.timer)
},
}
let vm = new Vue ({
el: '#App',
data(){
return {
msg: "hello vue",
exist: true
}
},
// <App/> 绑定v-if事件,创建/删除组件实例
template: `
<div id="vm">
<App v-if='exist'></App>
<hr>
<button @click='clickHandle'>点击销毁/创建 App组件</button>
</div>
`,
methods:{
clickHandle () {
this.exist = !this.exist
}
},
components: {
App,
},
})
activated 与 deactivated
activated 与 deactivated ,激活与停用,一般与 keep-alive 标签联用。
keep-alive 能将被其包裹的组件页面的状态放到缓存中,组件便不会被销毁,只会有激活与停用的状态。
例如:
App组件设置一个定时器 与 可以更改颜色的box,挂载在实例vm上,并设置keep-alive,让App可以在缓存中保存状态。通过控制 v-if 的 bool 值,在页面删除/添加App组件实例,从而调用App组件的 activated 与 deactivated。
<!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>Document</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: green;
}
.active {
background-color: yellow;
}
</style>
</head>
<body>
<script src="vue.min.js"></script>
<div id="App"></div>
<script>
let App = {
data(){
return {
count: 0,
// 将计时器定义为一个属性,方便后续删除
timer: '',
isActive: false
}
},
// 一个计时器 一个可更改颜色的div,都是有状态的
template: `
<div class="App">
<button @click='activeTime'>点击计时</button>
<div>计时器: {{ count }}秒</div>
<hr>
<button @click='changeColor' >点击更改颜色</button>
<div class="box" :class="{active:isActive}"><div>
</div>
`,
methods:{
activeTime () {
this.timer = setInterval(()=>{
this.count++
},1000)
},
changeColor () {
this.isActive = !this.isActive
}
},
activated () {
console.log('组件被激活')
},
deactivated () {
console.log('组件被停用')
},
}
let vm = new Vue ({
el: '#App',
data(){
return {
msg: "hello vue",
exist: true
}
},
//为App设置keepalive只后,v-if=false时,也会将此组件从页面删除,但是组件的状态会保存在缓存中
template: `
<div id="vm">
<keep-alive>
<App v-if='exist'></App>
</keep-alive>
<hr>
<button @click='clickHandle'>点击添加/删除 App组件</button>
</div>
`,
methods:{
clickHandle () {
this.exist = !this.exist
}
},
components: {
App,
},
})
</script>
</body>
</html>
组件实例化对象中的内置属性
$parent 与 $root
二者都是获取父组件的实例化对象,区别是:
- $parent 获取最近的父组件;
- $root 获取 根父组件。
$children
返回该组件的子组件实例化对象组成的的列表。
$ref 使用
获取DOM对象
在组件中,给标签绑定ref属性,可通过 $refs.ref属性值 获取该标签对应的DOM对象。
Vue.component('MyBtn',{
data () {
return{
}
},
// 给按钮添加一个点击事件,每点击一次times+1,并将times传给另一个组件
template: `
<div>
<div :ref='"mydiv1"' > hello1 </div>
<div :ref='"mydiv2"' > hello2 </div>
</div>`,
mounted(){
console.log(this.$refs.mydiv1) // 获取到DOM对象 <div> hello1 </div>
console.log(this.$refs.mydiv2) // 获取到DOM对象 <div> hello2 </div>
}
})
获取实例化组件对象
父组件使用子组件时加上ref属性,可通过 $refs.ref属性值 获取子组件的实例化对象,从而获取到子组件的属性与方法,执行子组件的方法。
Vue.component('MyBtn',{
data () {
return{
msg: 'hello vue!'
}
},
template: `
<div>
<div> hello1 </div>
</div>`,
methods: {
sayHi (greeting) {
this.msg = 'hi vue!'
return 'hi'
}
}
})
let vm = new Vue ({
el: '#App',
data (){
return {
}
},
// 在使用MyBtn组件时加上ref属性
template: `
<div id="id_vue">
<MyBtn :ref='"mybtn"'></MyBtn>
</div>`,
mounted (){
console.log(this.$refs.mybtn) // MyBtn的实例化对象
console.log(this.$refs.mybtn.msg) // hello vue! 获取到MyBtn的msg属性
console.log(this.$refs.mybtn.sayHi('来自父组件的问候')) // hi 父组件操作子组件的方法,从而更改了子组件的msg属性
console.log(this.$refs.mybtn.msg) // 来自父组件的问候 子组件的msg属性被变更了
}
})
vue-router
Vue的套件 vue + vue-router + vuex
vue + vue-router 主要用来做 SPA ( Single Page Application ) 单页面应用
- SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。(单页面应用指一个系统只加载一次资源,然后下面的操作交互、数据交互是通过router、ajax来进行,页面并没有刷新)
- MPA多页面应用 (MultiPage ),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。
为什么要使用单页面应用?
传统的路由在后端做,路由跳转时,如果后端资源过多,会导致页面白屏现象。让前端做路由,某个组件需要数据,让其在生命周期钩子中发送Ajax,数据驱动视图,而不会出现一大片白屏的现象。
vue-router 是vue的核心插件。
路由配置入门
-
加载 vue-router;
-
定义路由组件,就类似于视图函数;
-
定义路由对象,绑定路由与路由组件,类似于urls.py;
-
挂载路由对象到要使用路由的组件/实例中;
-
使用路由:vue-router 提供的两个全局组件 router-link与router-view。
-
router-link 相当于a标签,绑定跳转资源;
-
router-view 是路由组件template展示的出口。
-
<div id="App"></div>
<script src="vue.min.js"></script>
<!-- 加载 vue-router -->
<script src="vue-router.js"></script>
<script>
// 定义路由组件 home 与 course
let home = {
data (){
return {}
},
template:`<div>主页</div`,
};
let course = {
data (){
return {}
},
template:`<div>免费课程页面~</div>`,
};
// 创建路由对象 将路由与组件绑定
let router = new VueRouter({
routes: [
{
path: '/home',
component: home,
},
{
path: '/course',
component: course,
},
]
});
let vm = new Vue({
el: '#App',
data(){
return {}
},
// 挂载路由对象 router:router,
router,
// 在template或绑定的标签中使用路由 router-link与router-view
template:`
<div>
<div class="header">
<router-link to="/home">首页</router-link>
<router-link to="course">免费课程</router-link>
</div>
<router-view></router-view>
</div>`,
})
命名路由
可以给路由定义一个name属性,使用路由时绑定name,即使路由路径更改也不会有影响。
let router = new VueRouter({
routes: [
{
path: '/home',
// 添加 name 属性
name: 'home',
component: home,
},
{
path: '/courses',
name: 'course',
component: course,
},
]
});
// router-link中直接绑定到路由的name属性
<router-link :to="{ name:'home' }">首页</router-link>
<router-link :to="{ name:'course' }">免费课程</router-link>
嵌套路由
当你的路由是以下情况,可以考虑使用嵌套路由,也叫做子路由。
http://localhost:8080/course
/course下有很多子路由:
http://localhost:8080/course/model1
http://localhost:8080/course/model2
http://localhost:8080/course/model3
嵌套路由的配置:
import course from '@/components/actual_course/course'
import model2 from '@/components/actual_course/model2'
import model1 from '@/components/actual_course/model1'
routes: [
{
path: '/course',
name: 'course',
component: course,
// children[] 中定义子路由
children: [
{
// 这里的 model1 前面没有 /,会带上父路由,url为 /course/model1
path: 'model1',
name: 'model1',
component: model1
},
{
// 当 model2 前面有 /,不会带上父路由,url为/model1
// path: '/model2',
path: 'model2',
name: 'model2',
component: model2
}
]
},
],
嵌套路由 在父路由的template中引入
<template>
<div>
<div>实战课{{ msg }}</div>
<!-- 引入子路由 -->
<router-link :to="{name:'model1'}"> model1 </router-link>
<router-link :to="{name:'model2'}"> model2 </router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'course msg'
}
}
}
</script>
<style scoped>
</style>
路由跳转
当请求 / 时跳转到 /home
let router = new VueRouter({
routes: [
{
path: '/',
// redirect 跳转
redirect: '/home',
},
{
path: '/home',
name: 'home',
component: home,
},
]
});
动态路由匹配
当请求 以下路由 时,这个user_id是动态变化的,Vue中还提供了动态路由匹配的方法。
/user/1
/user/2
因为所有 /user/不同的userid 的路由都要用到用一个user组件渲染,所以VueRouter中 path与组件绑定时用到 “动态路径参数”,这个参数名可以随便起,格式是 :参数名
let router = new VueRouter({
routes: [
{
path: '/user/:id',
name: 'user',
component: user,
},
]
});
页面使用路由时,可指定上面定义的动态参数名对应的值 params:{动态参数名:用户真实id}
<router-link :to="{name:'user',params:{id:1}}">用户1</router-link>
<router-link :to="{name:'user',params:{id:2}}">用户2</router-link>
响应路由参数的变化
当链接中携带的动态路由的动态值 id 变化时,路由组件 user 怎么去响应参数的变化呢?
注意: 当使用动态路由参数时,例如从 /user/1
导航到 /user/2
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用,所以监控路由变化的函数不能放在created中。
使用 $route ,可获取路由相关的信息,利用watch监控$route,从而响应路由变化。
$route 表示(当前路由信息对象),其中记录了当前激活的路由的状态信息。
watch:{
// to表示$route变化之前的路由信息对象,from表示$route变化之前的路由信息对象
'$route' (to,from){
// 这里对路由变化做出响应...
}
}
console.log(this.$route)
具体使用:
let user = {
data (){
return {
user_id:null,
}
},
template:`<div>我是用户{{user_id}}</div>`,
watch:{
'$route' (to,from){
// 这个id就是定义路由对象path: '/user/:id'中的id
this.user_id = this.$route.params.id
}
}
};
下面展示动态路由例子的完整代码:
<div id="App"></div>
<script src="vue.min.js"></script>
<!-- 加载 vue-router -->
<script src="vue-router.js"></script>
<script>
let user = {
data (){
return {
user_id:null,
}
},
template:`<div>我是用户{{user_id}}</div>`,
watch:{
// $route 表示(当前路由信息对象) 表示当前激活的路由的状态信息
// to表示$route变化之前的路由信息对象,from表示$route变化之前的路由信息对象
'$route' (to,from){
// 这个id就是定义路由对象path: '/user/:id'中的id
this.user_id = this.$route.params.id
}
}
};
// 创建路由对象 将路由与组件绑定,设置动态路由参数 id
let router = new VueRouter({
routes: [
{
path: '/user/:id',
name: 'user',
component: user,
},
]
});
let vm = new Vue({
el: '#App',
data(){
return {}
},
// 挂载路由对象 router:router,
router,
// 在template或绑定的标签中使用路由:
// router-link与router-view 是vue-router 提供的两个全局组件
// router-link 相当于a标签,绑定跳转资源
// router-view 是路由组件template展示的出口
template:`
<div>
<div class="header">
<router-link :to="{name:'user',params:{id:1}}">用户1</router-link>
<router-link :to="{name:'user',params:{id:2}}">用户2</router-link>
</div>
<router-view></router-view>
</div>`,
})
</script>
声明式导航 与 编程式导航
声明式导航就是上面那种 router-link
<router-link :to="{name:'user',params:{id:1}}">用户1</router-link>
编程式导航 $router.push(xxx) 跳转到某个ur
$router为VueRouter实例,想要导航到不同URL,则使用$router.push方法,返回上一个history使用$router.go方法。跟上面说的一样,这里的$router就管理路由的跳转,英文里er结尾的都表示一类人,这里你可以把这个想象中一个管理者,他来控制路由去哪里(push、go)
$router.push('/xxx')
使用举例:
<div id="App"></div>
<script src="vue.min.js"></script>
<!-- 加载 vue-router -->
<script src="vue-router.js"></script>
<script>
// 定义路由组件 course
let course = {
data (){
return {}
},
template:`<div>免费课程页面~</div`,
};
// 创建路由对象 将路由与组件绑定
let router = new VueRouter({
routes: [
{
path: '/course',
name: 'course',
component: course,
},
]
});
let vm = new Vue({
el: '#App',
data(){
return {}
},
// 挂载路由对象 router:router,
router,
template:`
<div>
<span @click="clickHandler">点击跳转课程页面</span>
<router-view></router-view>
</div>`,
// 编程式导航 $router.push(xxx) 跳转到某个url
methods:{
clickHandler(){
// console.log(this.$router)
// this.$router.push('/course')
this.$router.push({name:'course'})
}
}
})
</script>
$router 与 $route区别
-
$router为VueRouter实例,想要导航到不同URL,则使用$router.push方法,返回上一个history使用$router.go方法。跟上面说的一样,这里的$router就管理路由的跳转,英文里er结尾的都表示一类人,这里你可以把这个想象中一个管理者,他来控制路由去哪里(push、go)。
-
$route 表示(当前路由信息对象),其中记录了当前激活的路由的状态信息。
路由钩子
全局路由钩子有 beforeEach 与 afterEach
- beforeEach 路由跳转之前调用
import vueRouter from './router/index'
vueRouter.beforeEach(function(to,from,next){
// to: 去往哪里 路由实例
// from: 来自哪里 路由实例
// next: 指定下一跳
// 如果你不写,就不会跳转
// 写了 next() 就会正常跳转to的url
// 也可以自己指定跳转路由 next('/course')
next()
})
- afterEach 路由跳转之后调用,有to与from两个参数
import vueRouter from './router/index'
vueRouter.afterEach(function(to,from){
// to: 去往哪里 路由实例
// from: 来自哪里 路由实例
console.log(to,from)
})
路由中的Meta
可在meta中定义一些该路由的元信息,以供后续操作。
例如判断该路由是否需要登录后才能跳转
// router/index.js 文件中
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: home,
},
{
path: '/free_course',
name: 'free_course',
component: free_course,
// 给/free_course路由设置meta元数据
meta:{
require_login: true
}
},
],
})
// 在其他文件中根据路由的元数据 来跳转
import vueRouter from './router/index'
vueRouter.beforeEach(function(to,from,next){
if (to.meta.require_login){
next('/')
}else{
next()
}
})
脚手架的使用
node与npm介绍与部署
node.js - 它不是一个框架,而是一个javascript运行环境,是一个能让 前端语言 javascript 运行在服务器上的开发平台。
npm - 就是javascript的第三方库管理工具,类似于python的pip。安装好node.js后,npm就有了。
npm常用命令
npm -v:查看版本号
npm config list:查看配置信息,比如npm源等
npm init:初始化package.json。参数-y跳过输入自动生成
npm install:根据package.json的配置安装插件。缩写npm i
npm i 包名:安装某包。缩写npm i 包名。参数-g全局安装;参数--save(缩写-S)保存到package.json的依赖中;参数--save-dev(缩写-D)保存到package.json的开发环境依赖中
npm update:更新。参数类似npm install。不带包名表示package.json里面的所有依赖更新
npm uninstall:卸载。参数类似npm install。不带包名表示卸载package.json里面的所有依赖
npm run dev:执行dev命令。dev是在package.json的scripts中配置的命令
npm install --production:只有dependencies节点下的模块会下载到node_modules目录中,devDependencies节点下的模块不会下载到node_modules目录
devDependencies节点下的模块是我们在开发时需要用的,比如项目中使用的压缩css、js的模块等。
这些模块在我们的项目部署后是不需要的,所以我们可以使用 -save-dev 的形式安装
node环境的部署
这里演示部署到windows电脑上。
去官网下载node.js安装包,再点击安装即可。
安装好之后
> node -v
v16.14.2
> npm -v
8.5.0
配置淘宝的源,后续安装用cnpm就从淘宝的源拉取
> npm install -g cnpm --registry=https://registry.npm.taobao.org
模块引用规范
CMD规范的模块引用
CMD规范全称是Common Module Definition。在这个规范中,主要规范了基本的书写格式和交互规则。在CMD规范中,一个模块就是一个文件。
模块的引用 模块b 引用 模块a定义的数据:
-
文件 a.js 中 module.exports 抛出变量/函数/对象
var person1 = { name: '杨稀饭' }; function personFunc () { let greet='hello vue'; return greet; }; // 抛出一个变量 // module.exports = person1; //抛出多个变量 module.exports = [person1,personFunc];
-
文件 b.js 使用 require('文件a路径') 接收抛出的变量/函数/对象
可以使用 node b.js 在服务端执行代码,浏览器端不识别,需要借助webpack。
var person = require('./a.js') p1 = person[0] p2 = person[1] console.log(p1.name) // 杨稀饭 console.log(p2()) // hello vue
ES6 module 规范
ES6中的模块引入也和CMD中不一样,ES6中使用export 与 import
export default 变量 与 export 变量 的区别
-
export default 变量
意思是在export时设置抛出的变量对应的key为default。
一个文件只能有一个默认抛出,接收时的变量名可以随意命名。
// 抛出/加载一个变量
// test1.js 抛出
let name='aaa';
export default name;
// test2.js 加载
import name from './test2.js'
console.log(name)
// 抛出/加载多个变量
// test1.js 抛出
let name='aaa';
let person={name:'yxf',age:3};
export default {name,person};
// test2.js 加载
import a from './test2.js'
console.log(a.name)
console.log(a.person)
- import {xxx} from ,不用设置default,但接收时要加上
// 抛出/加载一个变量
// test1.js 抛出
export let name='aaa';
// test2.js 加载
import {name} from './test2.js'
console.log(name)
// 抛出/加载多个变量
// test1.js 抛出
let name='aaa';
let person={name:'yxf',age:3};
export {name,person}
// test2.js 加载
import {name,person} from './test2.js'
console.log(name,person)
下面来演示一下:
-
文件 a.js 中抛出变量/函数/对象
let person1 = { name: '杨稀饭' }; //抛出对象person1 default是接收时该对象对应的key值 export default person1; //抛出对象person2 export let person2 = { name: '杨稀饭2' }; // 抛出函数 export function personFunc () { let greet='hello vue'; return greet; }; // 抛出变量 export let myname = '稀饭姐';
-
文件 b.js 接收抛出的变量/函数/对象
// 接收一个变量 // import a from './a.js' // 接收多个变量 import * as a from './a.js' console.log(a) // 输出: // [Module: null prototype] { // default: { name: '杨稀饭' }, // myname: '稀饭姐', // person2: { name: '杨稀饭2' }, // personFunc: [Function: personFunc] // } console.log(a.default.name) // 杨稀饭
这个时候执行 node b.js 会报错,是因为还要告诉node我们使用了ES6 module规范的代码。
初始化生成 package.json
npm init -y
在 package.json 中声明 "type": "module"
{
"name": "cmd",
"version": "1.0.0",
"description": "",
"main": "index.js",
// 添加此行
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
webpack 打包工具
官网: https://webpack.docschina.org/ 详细使用看官网
本文使用 webpack3.12.0 比较老的版本了,也只是简单使用。
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,你的项目部署时就无需加载各种繁杂的模块,而是加载bundles即可。通过webpack打包也能将浏览器不能识别的语法自动处理为浏览器能够识别的语法。
webpack中的几个概念:
- 入口(entry): 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始的文件,普遍是 main.js 或 index.js 。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
- 输出(output): output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是
./dist/main.js
。 - loader:webpack 只能理解 JavaScript 和 JSON 文件。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中
- 插件(plugin): loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量
webpack 安装
npm i webpack@4 -g
npm i webpack-cli -g
命令行执行webpack
上面介绍的两种模块引入规范的代码可以在服务器上用node执行,但在html页面加载 b.js后,在前端是无法识别的,产生以下报错。
使用webpack对js代码进行打包处理后,就能正常识别。
命令: webpack 入口文件 出口文件
> webpack b.js bundle.js
生成的 bundle.js 在 index.html中引入,就能在浏览器正常打开
配置中设定打包命令
如果使用全局webpack打包命令报错,可能是版本问题,可在项目局部安装其他版本
到你的项目目录,npm init初始化后,局部安装:
npm i webpack@4 -D
npm i webpack-cli -D
> webpack -v
webpack: 4.46.0
webpack-cli: 4.9.2
打包命令在package.json文件中配置,如果不指定配置文件会默认找webpack.config.js,也可通过 --config 指定配置文件。
webpack的出口、入口文件可在 webpack.config.js中声明,然后执行 npm run dev 即可。
module.exports = {
entry: {
'main': './b.js'
},
output: {
'filename': 'bundle.js'
},
// watch: true,
// development 开发模式
mode: 'development',
}
watch: true 实时监控打包编译,打开这个选项后,npm run dev 会卡住,当你修改了页面内容时,它会自动打包编译,你只需刷新浏览器即可看到页面。
打包css文件
新加一个css样式文件 index.css,将其一起打包到 bundle.js
下载样式文件相关loader
cnpm i css-loader style-loader -D
webpack.config.js中配置loader
module.exports = {
entry: {
'main': './b.js'
},
output: {
'filename': 'bundle.js'
},
// watch: true,
module:{
rules:[{
//正则表达式,表示.css后缀的文件
test:/\.css$/,
//针对css文件使用的loader,有先后顺序,数组项越靠后越先执行(从下到上,从右到左)
use:['style-loader','css-loader']
},]
},
mode: 'development',
}
入口文件 b.js 中引入css文件
import './index.css'
然后执行 npm run dev,就会生成 dist/bundle.js
在html文件引用时,要注意路径
在浏览器打开,样式生效。
还有一个webpack-dev-server是webpack的升级版:
webpack-dev-server --hot --open --config webpack.config.js
引入index.html文件
一般打包之后js文件在dist下,那么我也想在打包好后将index.html自动放到dist下,这样上线就只用发布dist目录即可。
使用 html-webpack-plugin 插件可用实现。
插件安装
npm i html-webpack-plugin@4 -D
在 webpack.config.js中使用插件
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
'main': './b.js'
},
output: {
'filename': 'bundle.js'
},
mode: 'development',
// 添加插件
plugins:[
new HtmlWebpackPlugin({
//这个文件是参照物,打包之后会将它拷贝到dist下
template: './index.html',
})
]
}
然后npm run dev 打包编译后,dist目录出现index.html,并且会自动引入打包好的js文件。
vue-cli介绍与使用
在软件开发中(包括前端开发)的脚手架指的是:有人帮你把开发过程中要用到的工具、环境都配置好了,让程序员可以方便地直接开始做开发,专注于业务,而不用再花时间去配置这个*开发环境*,这个开发环境就是脚手架。
Vue CLI 就是一个 脚手架,它是一个基于 Vue.js 进行快速开发的完整系统,它能通过一个简单的命令行工具来帮助我快速地构建一个足以支撑实际项目开发的Vue环境。
安装vue-cli
npm install -g @vue/cli
脚手架中提供了多种项目模板最常用的是 webpack-simple 与 webpack 模板,下面来使用一下。
vue-cli 3版本中的创建项目
vue create 项目名
vue-cli 2版本中的创建项目
// 安装vue-cli 2版本
npm i -g @vue/cli-init
// 新建模板项目
vue init 模板 项目名
以下主要讲vue2中建项目
使用webpack-simple模板创建项目
// vue init 模板名 项目名
vue init webpack-simple webpack-s-myproject
npm install 是根据项目的package.json 记录的信息安装依赖
npm run dev是执行package.json中的dev命令
webpack-dev-server 是webpack打包工具的升级版,--open 表示起服务后打开默认浏览器,--hot为热更新
我在src/App.vue中进行了更改,浏览器页面也立即更新。。
webpack-dev-server 打包的入口出口参数在 webpack.config.js中有配置
Vue组件文件可用.vue结尾,使用.vue后书写格式也有所变化
演示新建一个子组件在App.vue中加载
- 新增p1.vue组件
为了防止子组件/父组件的样式覆盖,给子组件的style设置 scoped参数,设置后样式只作用于本组件的标签。
- App.vue中引入p1.vue
- 效果
使用webpack模板创建项目
vue init webpack webpacknew-myproject
项目创建完后也会有后续安装依赖以及打包命令提示
运行效果
项目结构
build 中配置了webpack的基本配置、开发环境配置、生产环境配置等
config 中配置了路径端口值等
node_modules 放了安装的依赖包
src 放置组件和入口文件和路由
index.html 文件入口
起服务后,访问路由出现#号,可将路由模式改为mode="history",这种模式需要服务端的支持,直接文件打开会出问题。
当你get请求需要带参数类似于 /free_coures?userid=2 ,可在router-link的时候加上query参数,可通过 this.$route.query.userid 拿到值。
<router-link :to="{name:'free_course',query:{userid:2}}">免费课程</router-link>
Element-UI
官方文档: https://element.eleme.io/#/zh-CN/component/installation
在上面模板中引入Element-UI:
先安装
cnpm install element-ui --save
在 main.js 中写入以下内容
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
然后就能直接在vue文件中引入Element-UI中的组件代码啦。
axios
文档: http://www.axios-js.com/zh-cn/docs/
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,类似于ajax的功能,axios是基于ajax的封装,二者都是异步的。
安装
使用 npm:
$ npm install axios
使用 bower:
$ bower install axios
使用 cdn:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
使用
执行 GET
请求
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 上面的请求也可以这样做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
执行 POST
请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
其他写法:
// 也可以去掉这个.request
//this.$axios({
this.$axios.request({
method: 'get',
url: 'course/category/free/?courseType=free',
})
.then((data)=>{
let all_course = {id: 0,name: '全部'}
this.course_lists = data.data.data
this.course_lists.splice(0,0,all_course)
})
.catch((err)=>{
console.log(err)
});
.then: 请求成功了就执行.then中的方法;
.catch: 请求失败或 .then中抛出异常就会进入 .catch。
在项目中使用时,你可以先在main.js中将axios绑定给vue的原型再使用,绑定到原型上就相当于加了个父类的方法。
main.js中
如果你的请求前面一部分url总是相同,可设置baseurl
import Vue from 'vue';
import Axios from 'axios'
Vue.prototype.$axios = Axios
// Axios.defaults.baseURL = 'https://api.xxx.com/api/v1/'
其他组件中使用axios,axios中的函数使用箭头函数,this才能执行当前组件。
created (){
// 设置 baseURL 后,每次请求前会将这种半截的拼接到baseURL上请求
// this.$axios.get('course/category/free/?courseType=free')
this.$axios.get('https://api.xxx.com/api/v1/course/category/free/?courseType=free')
.then((data)=>{
console.log(this.msg)
console.log(data.data.data)
})
.catch((err)=>{
console.log(err)
});
}
vuex
官网: https://vuex.vuejs.org/zh/
介绍
前面我们介绍了组件间信息传递的三种方式,这里我们再介绍一种vuex。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
可用理解为vuex 专门有一个库store 存储数据,当某个组件a要数据时,就直接从库里拿,组件b更新库里的数据后,组件a中的数据也会更新。
数据状态:在我理解中就是属性与属性值的对应关系。
vuex中有几个核心概念:
-
state : 单一状态树,包含了全部的应用层级状态(数据源),即store中的数据都存储在state中。在其他组件中获取state中的某个属性值(例如 num:1)如下。可在计算属性中获取store中的数据应用于页面。
this.$store.state.num
但不可在其他组件中直接更改state中的状态。
-
getter: 有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数,就可以在getter中操作,可以看成是store的计算属性。getter还能对自己已经定义的属性进行操作。
当state中属性值变更时,与之关联的getters中的值也会更新。
定义 getter:
const store = new Vuex.Store({ state:{ test_list:['aaa',3,'bbb','ccc',12] }, getters:{ // 对state中的test_list进行过滤,只保留string类型的元素。 需要传参 state cleanList: function(state){ let cleanList=[] state.test_list.forEach((element) => { if (typeof element == 'string'){ cleanList.push(element) } }); return cleanList }, // 获取getters中的cleanList的长度 // 因为cleanList需要state参数,而此函数也依赖于cleanList,所以这里也要传入state cleanListCount: function(state,getter){ return getter.cleanList.length }, }, })
获取getter数据
this.$store.getters.cleanList this.$store.getters.cleanListCount
-
mutation : 更改store中的数据状态都要通过mutations,可使用$store.commit()来提交mutation。
// mutations中定义的方法, state是固定参数 changeNumMutations (state,val){ state.num = state.num + val }
// this.$store.commit('mutation中定义的函数名','传入值') this.$store.commit('changeNumMutations',2)
mutation 都是同步事务,其中一旦有异步操作很容易出现数据紊乱。
-
action : action 类似于 mutation,不同在于:
-
Action 提交的是 mutation,而不是直接变更状态。
提交action使用 $store.dispatch。
// actions中定义的方法,context是固定参数,和this参数一样,就是store实例本身 changeNumActions (context,val){ context.commit('changeNumMutations',val) }
// this.$store.dispatch('action中定义的函数名','传入值') this.$store.dispatch('changeNumActions',2)
-
Action 可以包含任意异步操作。所以当有异步操作时变更数据状态的步骤应该是: 组件 --> actions --> mutation --> state
-
安装
在 Vue 之后引入 vuex
会进行自动安装:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
npm
npm install vuex@next --save
yarn
yarn add vuex@next --save
在项目中使用vuex
安装vuex ,因为用到vue2版本,安装vuex3版本
npm install vuex@3 -S
main.js中引入vuex,创建一个store实例,并在vue实例中注入store机制。vue实例便有一个内置的 $store 属性,对应store实例了。
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
// 创建store实例
const store = new Vuex.Store({
state:{
num: 1,
},
mutations: {
},
actions: {
}
})
new Vue({
el: '#app',
router,
// vue实例中注入store
store,
components: { App },
template: '<App/>'
})
获取store中数据
在组件 home.vue 的计算属性中获取store中num数据
computed: {
myNum (){
return this.$store.state.num
}
}
更改store中数据
在组件 free_course.vue 中更新store中num的值
场景一: 更新操作中都是同步操作
-
先在store实例中定义mutations与actions方法
const store = new Vuex.Store({ state:{ num: 1, }, mutations: { changeNumMutations (state,val){ // 更改state中num的值 state.num = state.num + val } }, actions: { changeNumActions (context,val){ // 调用mutations中的changeNumMutations context.commit('changeNumMutations',val) } } })
-
free_course组件中更改store的num属性状态,由于整个更新过程中都是同步操作,所以可以直接 调用mutations,也可以调用actions。
-
调用mutations更新num属性状态
<button @click="clickHandler">同步修改</button> clickHandler(){ this.$store.commit('changeNumMutations',2) }
-
调用actions更新num属性状态
<button @click="clickHandler1">同步修改</button> clickHandler1(){ this.$store.dispatch('changeNumActions',2) }
-
场景二: 更新操作中涉及异步操作
有异步操作必须走 组件 --> actions --> mutation --> state
-
在store实例中定义mutations与actions方法
const store = new Vuex.Store({ state:{ num: 1, }, mutations: { changeNumMutations (state,val){ state.num += val }, }, actions: { changeNumAsyncActions(context,val){ // 异步修改 setTimeout setTimeout(()=>{ context.commit('changeNumMutations',val) },1000) } } })
-
free_course组件中调用actions更改store的num属性状态
<button @click="clickHandler2">异步修改</button> clickHandler2(){ this.$store.dispatch('changeNumAsyncActions',10) }
使用vuex获取ajax请求
- 在store实例中定义mutations与actions方法
const store = new Vuex.Store({
state:{
course_detail_lists:[],
},
mutations: {
getCourseDetailMutations(state,val){
state.course_detail_lists = val
}
},
actions: {
// 发送axios请求 异步操作
getCourseDetailAtions(context){
Axios.get('course/free/')
.then((data)=>{
// 调用 mutations 中的方法
context.commit('getCourseDetailMutations',data.data.data)
})
.catch((err)=>{
console.log(err)
})
}
}
})
- 组件中调用actions存储接口数据到vuex
created (){
this.$store.dispatch('getCourseDetailAtions')
},
- 组件中获取接口数据
computed:{
course_detail_lists (){
return this.$store.state.course_detail_lists
}
}
vue使用中遇到的问题
编程式导航视图展示问题
编程式导航一些情况下不写router-view 标签也可以展示???
可以参考: https://blog.csdn.net/weixin_39540315/article/details/111112294
路由a 切换到 路由b
-
当切换路由同级时,切换路由其实只是切换了上级router-view容器的内容,所以路由a中不用写router-view;
-
当切换路由为父子关系时,父路由需要有挂有router-view容器,展示子路由的视图。
过滤器中this不识别
filters中是不识别this的,可以在计算属性中将this赋值给that,调用过滤器方法时传入that变量
例如:使用moment格式化时间数据
# 安装moment包
cnpm install moment -S
# main.js 中 给vue原型添加Moment方法
import Vue from 'vue'
import Moment from 'moment'
Vue.prototype.$Moment=Moment
插件中使用moment
<template>
<div>
<!-- 调用过滤器时传入that -->
<div> -- {{comment.account}} {{ comment.date | formatTime(that) }}</div>
</div>
</template>
<script>
export default {
name: 'comment',
computed:{
detail: function () {
return this.$store.state.courseDetail
},
// 将this赋值给that
that: function() {
return this
}
},
filters: {
formatTime: function(val,that){
// 返回格式化好的数据
return that.$Moment(val).format('YYYY-MM-DD hh:mm:ss')
}
}
}
</script>
异步获取数据渲染报错
vuex中有个courseDetail数据,默认为空对象,组件b获取数据后,会存到 store的courseDetail
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
// courseDetail:null
courseDetail:{}
},
mutations: {
storeCourseDetail(state,val){
state.courseDetail=val
}
}
})
export default store
我的组件a要从 vuex 仓库中获取 detail数据,在页面进行渲染,但是组件a渲染页面与组件b获取数据是异步的,就会导致组件a渲染时,仓库的courseDetail数据还没有存上,courseDetail还没值:
-
当vuex 中默认 courseDetail:null 或 courseDetail:'' 时,组件a中渲染 {{ courseDetail.key }},key不存在,会报错
-
当vuex 中默认 courseDetail:{ } 或 courseDetail:[ ],
组件a中渲染 {{ courseDetail.key }} ,key不存在,不会报错,只是undefind,
组件a中渲染 {{ courseDetail[index] }}会报错,因为索引不存在。
所以这些报错怎么解决:
-
数据默认值最好是 空对象 或 空列表,不能直接为 null 或 ' ';
-
可以在组件a渲染时加上if 判断:
<template> <!-- 加上if判断,如果courseDetail为空,就不会创建这个标签 --> <div v-if="courseDetail"> <div> <div> {{ courseDetail.title }} </div> <div> 讲师: {{ courseDetail.teachers[0].name }} </div> </div> </div> </template> <script> export default { name: 'Chapter', computed:{ // 从vuex中拿取数据 courseDetail: function () { return this.$store.state.courseDetail } } } </script>
for循环中的url 如何放在src属性中
例如: 后端返回课程数据 ,每一个课程都有一个图片链接,需要放在 img标签的 src属性中
将后端数据存在 course_list 里面
<div v-for="(course) in course_list" :key="course.id" class="single_course" @click="clickCourse(course.id)"> <!-- :src 可以获取到 course变量 --> <img :src="course.course_img" alt=""> <div>{{ course.title }}</div> </div>