Vue

Vue是一套用于构建用户界面的渐进式框架,使用的是MVVM思想

官网:https://cn.vuejs.org/

1、什么是MVVM?

  • M:即Model模型,包含数据和一些基本操作
  • V:即View视图,页面渲染结果
  • VM:即View-Model,模型与视图间的双向操作(无需开发人员干涉)

MVVM之前,开发人员从后端获取数据模型,然后要通过DOM操作Model进行渲染到View中,而后当用户操作视图,我们还需要通过DOM获取View中的数据,然后同步到Model

NVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用在关心ModelView之间是如何互相影响的

2、安装使用Vue

我们首先新建一个vue的文件夹,使用如下命令初始化项目

1
npm init -y

初始化后我们就能看到目录中多了一个package.json文件

然后我们使用npm安装vue

1
2
# 最新稳定版 
npm install vue

到此我们就给当前项目成功安装了vue,只需要在项目中引入即可使用啦

1
<script src="./node_modules/vue/dist/vue.min.js"></script>

下面我们使用! tab快速生成一个html文件进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VUE测试</title>
<script src="./node_modules/vue/dist/vue.min.js"></script>
</head>

<body>
<div id="app">
<!-- 声明需要的数据 -->
{{name}}在说Hello World!
</div>

<script>
let vm = new Vue({
// 代表管控的元素
el: "#app",
// 代表使用的属性
data: {
name: "Levi"
}
});
</script>
</body>
</html>

3、双向绑定v-model

我们只需要将元素跟模型中需要使用的数据进行绑定即可

当数据变了输入框会变,同样数据框变了数据也会变

即模型变化,视图变化,反之亦然

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<input type="text" v-model="sum">
当前共有{{sum}}个人
</div>

<script>
let vm = new Vue({
el: "#app",
data: {
sum: 1
}
});
</script>

花括号格式:{{表达式}}

说明:

  • 该表达式支持JS语法,可以调用js内置函数(必须有返回值)
  • 表达式必须有返回结果,例如:1+1,没有结果的表达式不允许使用,如let a=1+1
  • 可以直接获取vue实例中定义的数据或函数

4、事件处理

示例:当点击一下按钮的时候,总人数增加1

1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
当前共有{{sum}}个人<button v-on:click="sum++">添加</button>
</div>

<script>
let vm = new Vue({
el: "#app",
data: {
sum: 1
}
});
</script>

5、声明方法

我们如果处理一下比较复杂的逻辑的时候,可以将内容写到methods

在这里我们可以定义很多方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
当前共有{{sum}}个人
<button v-on:click="sum++">添加</button>
<button v-on:click="cancle">取消</button>
</div>

<script>
let vm = new Vue({
// 绑定元素
el: "#app",
// 封装数据
data: {
sum: 1
},
// 封装方法
methods: {
cancle() {
// 这里需要使用this才能使用到sum
this.sum--;
}
}
});
</script>

6、常用指令

v-html:对内容进行转义后显示,即h1标签会默认识别到,只显示标签内的内容

v-text:不进行转义进行显示文本内容,不存在插值闪烁问题,即当vue的声明是在插值之后的时候,如果网速很慢,在数据未加载完成时,页面会出现原始的{{}}标签,加载完毕后才显示正常数据,我们称之为插值闪烁,例如如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="app">
{{msg}}<br>
<span v-html="msg"></span>
<span v-text="msg"></span>
</div>
<!--这里声明vue是在{{msg}}下方 所以在页面没加载完全的时候 可能看到{{msg}}-->
<script src="./node_modules/vue/dist/vue.min.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
msg: "<h1>Hello World</h1>"
}
});
</script>

v-bind:给html标签的属性进行绑定

例如我们需要动态的将跳转的地址进行更改,代码如下:

1
<div id="app">    <a v-bind:href="link">gogogo</a></div><script>    let vm = new Vue({        el: "#app",        data: {            link: "https://www.baidu.com"        }    });</script>

v-bind:还可以根据boolean指定显示不同的值,可动态修改classstyle

例如我们需要根据不同情况显示class的不同属性值,可以简写为:

1
<!-- 语法:{class名1:布尔值1,class名2:布尔值2} --><div id="app">	<span v-bind:class="{active:isActive,'text-danger':hasError}">Hello</span></div><script>    let vm = new Vue({        el: "#app",        data: {            isActive: true,            hasError: false        }    })</script>

v-model:进行双向绑定,数据和页面任何一方改变,对方都会跟着改变

1
<div id="app">    <input type="checkbox" v-model="name" value="小火龙">宝可梦A    <input type="checkbox" v-model="name" value="杰尼龟">宝可梦B    <input type="checkbox" v-model="name" value="皮卡丘">宝可梦C    <input type="checkbox" v-model="name" value="妙蛙种子">宝可梦D    <br />您选择了:{{name.join(",")}}</div><script>    let vm = new Vue({        el: "#app",        data: {            name: []        }    })</script>

v-for:遍历操作,可以使用index获取到当前索引值,使用:key指定唯一的主键,可以提高整体渲染速度

1
<div id="app">    <ul>        <li v-for="(user, index) in users" :key="user.name">            学号:{{index+1}} 姓名:{{user.name}} 性别:{{user.gender}}        </li>    </ul></div><script>    let vm = new Vue({        el: "#app",        data: {            users: [                { name: "皮卡丘", gender: "男" },                { name: "杰尼龟", gender: "男" },                { name: "波克比", gender: "女" },                { name: "可达鸭", gender: "男" },                { name: "海星星", gender: "女" }            ]        }    })</script>

在循环内部,也可以获取到对象的keyvalue以及index,名字可以自定义

1
<div id="app">    <ul>        <li v-for="(user, index) in users" :key="user.name" v-if="user.gender=='女'">            <!-- 只有一个代表值 -->            <p v-for="value in user">{{value}}</p>            <!-- 第一个代表值,第二个代表键 -->            <p v-for="(value,key) in user">{{key}}:{{value}}</p>            <!-- 第一个代表值,第二个代表键,第三个代表索引 -->            <p v-for="(value,key,index) in user">{{index}}:{{key}}:{{value}}</p>        </li>    </ul></div><script>    let vm = new Vue({        el: "#app",        data: {            users: [                { name: "皮卡丘", gender: "男" },                { name: "杰尼龟", gender: "男" },                { name: "波克比", gender: "女" },                { name: "可达鸭", gender: "男" },                { name: "海星星", gender: "女" }            ]        }    })</script>

v-if:只有当判断条件成立的时候才会被渲染,不满足条件时页面中无相关代码

1
<!-- 可以将上文中的案例改写如下 只查询性别为女的宝可梦 --><div id="app">    <ul>        <li v-for="(user, index) in users" :key="user.name" v-if="user.gender=='女'">            学号:{{index+1}} 姓名:{{user.name}} 性别:{{user.gender}}        </li>    </ul></div>

同样,也存在v-elsev-else-if操作

1
<div id="app">    <div v-if="type === 'A'">A</div>    <div v-else-if="type === 'B'">B</div>    <div v-else-if="type === 'C'">C</div>    <div v-else>Not A/B/C</div></div><script>    let vm = new Vue({        el: "#app",        data: {            type: 'A'        }    })</script>

v-show:只有当判断条件成立的时候才会显示

不同于v-if,不满足条件时仅作隐藏不会在页面中消失

1
<div id="app">    <h1 v-show="ok">Hello!</h1></div><script>    let vm = new Vue({        el: "#app",        data: {            ok: false        }    })</script>

7、事件修饰符

现有如下案例:我们点击点我去百度的时候,会先弹出一次弹框(点击了小DIV),然后会弹出第二次弹框(点小DIV也算点击了大DIV),最后会跳转到百度,这种情况就叫做事件冒泡

v-on:绑定事件,v-on:click可以简写成@click

1
<div id="app">    <div style="border: 1px solid red;padding: 20px;" v-on:click="hello">        大DIV        <div style="border: 1px solid blue;padding:20px" @click="hello">            小DIV            <a href="https://www.baidu.com">点我去百度</a>        </div>    </div></div><script>    let vm = new Vue({        el: "#app",        methods: {            hello() {                alert("Hello World");            }        }    })</script>

下面我们要解决一下这个问题,外层div不弹窗,在点击事件后增加stop即可

1
<div style="border: 1px solid blue;padding:20px" @click.stop="hello">

这个叫事件修饰符,下面列举一些常用的事件修饰符,也可以查看官网文档

https://cn.vuejs.org/v2/guide/events.html#事件修饰符

  • .stop:阻止事件冒泡,不让当前元素的事件继续往外触发
  • .prevent:阻止事件本身行为,例如阻止超链接跳转,表单提交等
  • .capture:改变js默认的事件机制,默认是冒泡,capture功能是将冒泡改为倾听模式,当元素发生冒泡时,先触发带有该修饰符的元素。若有多个该修饰符,则由外而内触发
  • .self:只有自己出发的自己才会执行,如果是内部冒泡事件则会忽略
  • .once:将事件设置为只执行一次,prevent.once代表只阻止事件行为一次
  • .passive:立即执行默认方法,无需等待浏览器判断是否存在preventDefault阻止该次事件的默认动作,需要注意的是,如果passiveprevent同时存在,后者无效
1
<!-- 阻止单击事件继续传播 --><a v-on:click.stop="doThis"></a><!-- 提交事件不再重载页面 --><form v-on:submit.prevent="onSubmit"></form><!-- 修饰符可以串联 --><a v-on:click.stop.prevent="doThat"></a><!-- 只有修饰符 --><form v-on:submit.prevent></form><!-- 添加事件监听器时使用事件捕获模式 --><!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 --><div v-on:click.capture="doThis">...</div><!-- 只当在 event.target 是当前元素自身时触发处理函数 --><!-- 即事件不是从内部元素触发的 --><div v-on:click.self="doThat">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

因为点击的时候会先prevent,阻止默认事件,阻止了跳转;然后判断是否是self,因为点击到的是div标签,所以不是self。但是a标签self,阻止了alert(2)

8、按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

1
<!-- 只有在key是Enter时调用vm.submit() --><input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符

1
<input v-on:keyup.page-down="onPageDown">

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用

常用键盘码如下

  • .enter
  • .tab
  • .delete (捕获删除退格键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名:

1
// 可以使用v-on:keyup.f1Vue.config.keyCodes.f1 = 112

9、计算属性

任何复杂的逻辑,都应该使用计算属性,计算属性逻辑写在computed中,

只要检测到它使用到的属性有变化,就会触发重新计算

这也是计算属性与直接调用函数计算的区别所在

如果使用到的属性没有任何变化,计算属性是会直接返回缓存结果,而不会再次执行

但是如果使用的是函数的话,则依旧会继续调用

1
<div id="app"><ul><li>西游记:价格:{{xyjPrice}} ,数量:<input type="number" v-model="xyjNum"></li><li>水浒传:价格:{{shzPrice}} ,数量:<input type="number" v-model="shzNum"></li><li>总价:{{totalPrice}}</li></ul></div><script>    let vm = new Vue({        el: "#app",        data: {            xyjPrice: 12,            shzPrice: 19,            xyjNum: 1,            shzNum: 1        },        computed: {            totalPrice() {                return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;            }        }    })</script>

10、侦听属性

侦听属性是一种更通用的方式来观察和响应 Vue实例上的数据变动

当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch,通常更好的做法是使用计算属性而不是命令式的 watch 回调

还是上面的案例,如果西游记只能买3本,超过3本就提示库存已经超了,就可以使用watch监听了,

侦听属性里可以获取到修改前以及修改后的值,这样方便我们进行一些判断操作

1
<div id="app"><ul><li>西游记:价格:{{xyjPrice}} ,数量:<input type="number" v-model="xyjNum"></li><li>水浒传:价格:{{shzPrice}} ,数量:<input type="number" v-model="shzNum"></li><li>总价:{{totalPrice}}</li>{{message}}</ul></div><script>    let vm = new Vue({        el: "#app",        data: {            xyjPrice: 12,            shzPrice: 19,            xyjNum: 1,            shzNum: 1,            message: ""        },        watch: {            xyjNum(newVal, oldVal) {                if (newVal >= 3) {                    this.message = "库存不能超过3";                    this.xyjNum = 3;                } else {                    this.message = "";                }            }        }    })</script>

11、过滤器

我们可以自定义局部或全局的过滤器,对一些常见的文本格式化

过滤器可以用在两个地方:双花括号插值和 v-bind 表达式

过滤器应该被添加在JavaScript表达式的尾部,由管道 |符号指示

1
<!-- 在双花括号中 -->{{ message | capitalize }}<!-- 在 `v-bind` 中 --><div v-bind:id="rawId | formatId"></div>

你可以在一个组件的选项中定义本地的过滤器:

1
<div id="app">    <ul>        <li v-for="name in names">            {{name | nameFilter}}        </li>    </ul></div><script>    let vm = new Vue({        el: "#app",        data: {            names: ["可达鸭","杰尼龟","波克比","小火龙"]        },        filters: {            nameFilter(n) {                if (n == "可达鸭") {                    return n + "真笨";                } else {                    return n;                }            }        }    })</script>

或者在创建Vue实例之前全局定义过滤器:

1
Vue.filter('nameFilterG', function (n) {    if (n == "可达鸭") {        return n + "真蠢";    } else {        return n;    }})new Vue({  // ...})

当全局过滤器和局部过滤器重名时,会采用局部过滤器

12、组件化

在大型应用开发的时候,页面可以划分成很多部分,往往在不同的页面,也会有相同的部分

但是如果每个页面都独自开发,无疑会增加开发的成本,所以将相同的部分拆分成独立的组件,可以在不同的页面共享这些组件,避免重复开发

vue里,所有的vue实例都是组件

例如我们现在有一个点击增加次数的按钮如下

1
<div id="app">    <button v-on:click="num++">我被点击了{{num}}次</button></div><script>    var vm = new Vue({        el: "#app",        data: {            num: 1        }    })</script>

然后我们使用组件来改造这个方法,其中组件的名字counter可以随便更改,且data必须是一个函数才可以,因此每个实例可以维护一份被返回对象的独立的拷贝,否则如果存在多个组件,修改任意一个值都会改变全部组件的值,各个值之间不是互相独立的

1
<div id="app">    <button v-on:click="num++">我被点击了{{num}}次</button>    <counter></counter>    <button-counter></button-counter></div><script>    // 全局声明注册一个组件    Vue.component("counter", {        template: `<button v-on:click="count++">全局组件点击了{{count}}</button>`,        data() {            return {                count: 1            }        }    })    // 局部声明注册一个组件    const buttonCounter = {        template: `<button v-on:click="count++">局部组件点击了{{count}}</button>`,        data() {            return {                count: 1            }        }    };    var vm = new Vue({        el: "#app",        data: {            num: 1        },        components: {            'button-counter': buttonCounter        }    })</script>

13、生命周期和钩子函数

beforeCreate(实例创建前)

实例组件刚开始创建,元素dom和数据都还没有初始化

应用场景:可以在这加个loading事件

created(实例创建后)

数据data已经初始化完成,方法也已经可以调用,但是dom为渲染,在这个周期里面如果进行请求是可以改变数据并渲染,由于dom未挂载,请求过多或者占用时间过长会导致页面线上空白

应用场景:在这结束loading,还做一些初始化,实现函数自执行

beforeMoute(元素挂载前)

dom未完成挂载,数据初始化完成,但是数据的双向绑定还是{{}},这是因为vue采用了虚拟dom技术

mouted(元素挂载后)

数据和dom都完成挂载,在上一个周期占位的数据把值渲染进去,一般请求会放在这个地方,因为这边请求改变数据之后刚好能渲染

beforeUpdate(实例更新前)

只要是页面数据改变了都会触发,数据更新之前,页面数据还是原来的数据,当你请求赋值一个数据的时候就会执行这个周期,如果没有数据改变不执行

updated(实例更新后)

只要是页面数据改变了都会触发,数据更新完毕,页面的数据是更新完成的,beforeUpdatedupdated要谨慎使用,因为页面更新数据的时候都会触发,在这里操作数据很影响性能和死循环

beforeDestory(实例销毁前)

实例销毁之前调用,在这一步,实例仍然完全可用

destory(实例销毁后)

vue实例销毁后调用,调用后,vue实例指示的所有内容都会解除绑定,所有的事件监听器都会被移除,所有的子实例也会被销毁

1
<!DOCTYPE html><html>    <head>        <meta charset="UTF-8">        <title>vue的生命周期示例</title>    </head>    <body>        <div id="app">            {{name}}        </div>        <button onclick="destory()">销毁实例</button>        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>        <script type="text/javascript">            const vm=new Vue({                el:'#app',                data:{                    name:'xx',                    age:18                },                beforeCreate(){                    console.log('============实例创建前=============');                    console.log(this.$el);    //undefined                    console.log(this.$data);//undefined                },                created(){                    console.log('============实例创建后=============');                    console.log(this.$el);                    console.log(JSON.stringify(this.$data));                },                beforeMount(){                    console.log('============元素挂载前=============');                    console.log(this.$el);                        console.log(JSON.stringify(this.$data));                },                mounted(){                    console.log('============元素挂载后=============');                    console.log(this.$el);                        console.log(JSON.stringify(this.$data));                },                beforeUpdate(){                    console.log('============实例更新前=============');                    console.log(this.$el);                        console.log(JSON.stringify(this.$data));                },                updated(){                    console.log('============实例更新后=============');                    console.log(this.$el);                        console.log(JSON.stringify(this.$data));                },                beforeDestroy(){                    console.log('============实例销毁前=============');                    console.log(this.$el);                        console.log(JSON.stringify(this.$data));                },                destroyed(){                    console.log('============实例销毁后=============');                    console.log(this.$el);                        console.log(JSON.stringify(this.$data));                }            });            function destory(){                vm.$destroy();            }        </script>    </body></html>

14、使用Vue脚手架模块化开发

全局安装webpack

命令:npm install webpack -g

全局安装vue脚手架

命令:npm install -g @vue/cli-init

使用脚手架初始化一个叫vue-demo的项目

命令:vue init webpack vue-demo

运行项目

命令:npm run dev

需要注意的两点:

  • 如果提示vue不是内部或外部命令,则在Nodejs目录下全局搜索vue.cmd,将当前文件所在目录加入到path环境变量中即可
  • 如果使用vscode运行则会报错,因为powershell禁止执行脚本文件,所以将vscode的命令方式修改为cmd或直接使用cmd窗口进行执行命令即可