Vue 官方网站: https://cn.vuejs.org/
Vue 官方教程(v2): https://cn.vuejs.org/v2/guide/

Vue 简介

Vue 是什么?

Vue 是一套用于构建用户界面渐进式 JavaScript 框架。

构建用户界面: 将得到的各种数据变成用户看到的界面。

渐进式: Vue 可以自底向上逐层应用。简单应用可以只引用一个轻量小巧的核心库,复杂应用可以引入各式各样的 Vue 插件。

谁开发的?

Vue 作者尤雨溪

  • 2013 年:受到 Angular 框架的启发,尤雨溪开发出一款轻量框架 —— Seed。同年 12 月,Seed 更名为 Vue,版本号 0.6.0
  • 2014 年:Vue 正式对外发布,版本号 0.8.0Taylor otwell 在 Twitter 上发表动态,说自己在学习 Vue.js。
  • 2015 年:10 月 27 日,正式发布 Vue 1.0.0 Evangellon(新世纪福音战士)。
  • 2016 年:10 月 1 日,正式发布 Vue 2.0.0 Ghost in the Shell(攻壳机动队)。
  • 2020 年:9 月 18 日,正式发布 Vue 3.0.0 One Piece(海贼王)。

Vue 的特点

  1. 采用组件化模式,提高代码复用率,让代码更好维护。
  2. 声明式编码,让编码人员无需直接操作 DOM,提高开发效率。

Vue 基础教程

Hello World

首先引入 Vue.js:

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

准备一个 div 作为容器,然后创建 Vue 实例,传入配置对象,进行挂载:

<div id='root'>
<h2>{{ hello }}</h2>
</div>
<script>
Vue.config.productionTip = false // 关掉 Vue 生成的更换生产版本提示
new Vue({
el: '#root',
data: {
hello: '你好,世界'
}
})
</script>

注意
HTML 容器与 Vue 实例之间是一一对应的。
root 容器里的代码被称为 Vue 模板
真实开发中只有一个 Vue 实例,并且会配合组件一起使用。
一旦 data 中的数据发生改变,页面中用到该数据的地方也会自动更新。

插值表达式

插值表达式可以读取 data 中的所有属性,任何合法的JS表达式,一般用在标签体内。

<p>{{ msg }}abc</p>

v-bind

v-bind 主要用于元素属性的单向绑定,从 data 自动同步到 view 上。
v-bind:class="fl" 可以简写成 :class="fl"
如果想要实现表达式的拼接,被拼接的字符串一定要用引号包裹,否则会被当成变量解析。

<input type="text" v-bind:value="msg">
<input type="text" :value="msg">

v-model

v-model 主要用于元素属性的双向绑定
在Vue中,只有 v-model 指令实现了数据的双向绑定,其他指令都是单向的。
v-model 主要绑定标签的 value 属性,所以在使用时可以省略 value。
v-model 只能应用在表单元素中。

v-model 的三个修饰符:

lazy: 失去焦点再收集数据。
number: 输入的数据转换成有效数字。
trim: 过滤首尾空格。
<input type="text" v-model:value="msg">
<input type="text" v-model.trim="msg">

el 与 data 的两种写法

el 的两种写法

// 第一种:创建的时候直接指定 el。
new Vue({
el: '#root',
data: {
hello: '你好,世界',
}
})
// 第二种:先创建,然后用 $mount 函数指定挂载元素。
const vm = new Vue({
data: {
hello: '你好,世界',
}
})
vm.$mount('#root');

data 的两种写法

// 第一种:对象式
new Vue({
el: '#root',
data: {
hello: '你好,世界',
}
})
// 第二种:函数式
new Vue({
el: '#root',
data(){
return {
hello: '你好,世界',
}
}
})

一个重要原则:由 Vue 管理的函数,一定不要写成箭头函数的形式,否则 this 不再是 Vue 实例而是 window,这时将无法访问 Vue 实例里的内容(如 data)。

Object.defineProperty()

使用 Object.defineProperty() 方法可以在对象上定义新的属性:

let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
value: 18,
enumerable: true, // 控制属性能否被枚举(遍历),默认为 false
writable: true, // 控制属性能否被修改,默认为 false
configurable: true // 控制属性能否被删除,默认为 false
})

此外,Object.defineProperty() 方法在定义新属性时,还可以使用 gettersetter

let number = 19
let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
get() {
console.log("有人读取了 age");
return number
},
set(val) {
console.log("有人修改了 age,新值为 " + val);
number = val
}
})

Vue 的数据代理

数据代理:通过一个对象代理对另一个对象中的属性的操作(读/写)
Vue 通过 vm 对象来代理 data 对象中属性的操作,可以更方便的操作 data 中的数据。
data === vm._data

基本原理:

let obj = {x: 100}
let obj2 = {y: 200}

Object.defineProperty(obj2, 'x', {
get(){
return obj.x
},
set(value){
obj.x = value
}
})
// 修改 obj2.x 时,obj.x 会一并修改。

v-on

v-on 主要用于元素的事件绑定。
v-on:click="method" 可以简写成 @click="method"
在调用时如果不写参数,则形参第一个参数为 event。
在调用时如果传入数据参数,可以用 $event 传入事件参数。

<button v-on:click="show">按钮</button>
<button @click="show($event, 66)">按钮</button>

v-cloak

v-cloak 会在页面渲染完成之后移除。
可以通过 CSS 的属性选择器来操作被其修饰的元素,可以避免将未挂载的页面显示给用户。

<style>
[v-cloak] {
display: none;
}
</style>
<div id='root'>
<h2 v-cloak>{{ name }}</h2>
</div>

v-pre

v-pre 被其修饰的元素不会被 VUE 编译。

v-text

v-text 会全部替换标签体里的内容,而且内部支持写表达式。

<p v-text="msg"></p>

v-text 与 插值表达式 的区别:
插值表达式只会插入内容,并不会清除其余内容。而 v-text 则会清除所有内容。

v-html

v-html 会将元素当成HTML标签解析后输出。

<p v-html="msg"></p>

v-for

我们可以用 v-for 指令基于一个数组来渲染一个列表,推荐只要涉及到 v-for 这种循环,就给循环的每一项都添加 :key 属性。:key 属性只接受 number 或者 string 类型。

<li v-for="item in list" :key="item.id">{{ item }}</li>
<li v-for="(item,index) in list" :key="item.id">{{ index }} ---- {{ item }}</li>

注意:
:key 绑定值为索引时,在列表的最顶端插入数据会造成效率低、数据错乱的问题,这涉及到 Vue 的 Diff 算法。最好是绑定为每一项的 ID 值。

v-if & v-show

v-if 指令和 v-show 指令都用于条件性地渲染一块内容。不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。

<div id="app">
<p v-if="true">MSG</p>
<p v-show="true">MSG</p>
</div>

v-if 也可以和 v-else-ifv-else 来使用,中间不可打断。

<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else>Vue</div>

事件修饰符

  • 在事件后面加上 .stop 即可防止事件冒泡。
<div id="app">
<div id="inner" @click="innerClick">
<button @click.stop="btnClick"></button>
</div>
</div>
  • 在事件后面加上 .prevent 即可阻止事件的默认行为。可以和 .stop 连续写。
<div id="app">
<a href="https://www.baidu.com" @click.prevent.stop="linkClick">百度</a>
</div>
  • .capture 添加事件监听器时使用事件捕获模式。
  • .self 只当事件在该元素本身(比如不是子元素)触发时触发回调。
  • .once 事件只触发一次。

键盘事件

<div id='root'>
<input type="text" @keyup.enter="check">
</div>

Vue 中常用的键盘别名:
enter, delete, esc, space, tab, up, down, left, right

  • Vue 中未提供别名的按键,可以使用按键原始的 key 值去绑定,但要注意转换为 kebab-case(短横线命名),如:@keyup.caps-lock
  • 系统修饰键:ctrl, alt, shift, meta(win键)
    • 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
    • 配合 keydown 使用:正常触发事件。
  • 也可以使用 keyCode 去指定具体的按键(不推荐),如 @keyup.17
  • Vue.config.keyCodes.自定义键名 = 键码, 可以自定义按键别名
  • 组合键:@keyup.ctrl.z

Vue 中使用样式

class 样式

  1. 数组
<h1 :class="['red', 'thin']"></h1>
  1. 三元表达式
<h1 :class="['red', 'thin', isActive?'active':'']"></h1>
  1. 数组中嵌套对象
<h1 :class="['red', 'thin', {'active': isActive}]"></h1>
  1. 直接使用对象
<h1 :class="{red: true, thin: true, active: isActive}"></h1>

内联样式

直接在元素上通过 :style 的形式,书写样式对象。

<h1 :style="{color: 'red', 'font-size': '40px'}"></h1>

Vue 计算属性

  • 当要使用的属性为其他属性计算得来时,可以使用计算属性。
  • 原理:底层借助了 Object.defineproperty() 方法提供的 getter 和 setter。
  • get 函数在初次读取时会执行一次,在依赖的数据发生改变时会被再次调用。
  • 与 methods 实现相比,内部有缓存机制,效率更高。
  • 计算属性最终会出现在 vm 上,可以直接读取使用。
new Vue({
el: '#root',
data: {
a: '张',
b: '三'
},
computed: {
fullName: {
get() {
return this.a + this.b
},
set(value) {
// 一般不会使用
}
},
// 如果没有 set 方法,可以使用简写形式:
fullName2() {
return this.a + this.b
}
}
})

Vue 侦听器

  • 侦听器可以侦听属性和计算属性
  • watch 默认不侦听对象内部值的改变,如需侦听,要配置 deep: true
new Vue({
el: '#root',
data: {
isHot: false,
number: {
a: 1,
b: 2
}
},
watch: {
isHot: {
immediate: true, // 初始化时调用一次
handler(newValue, oldValue) {
console.log("isHot 被修改了", newValue, oldValue);
}
},
// 监视多级结构中的属性
'number.a': {
handler(newValue) {
console.log("a 被修改了", newValue);
}
},
// 如果只需要 handler,可以使用简写形式
'number.b'(newValue, oldValue) {
console.log("a 被修改了", newValue, oldValue);
},
// 监视对象中的属性变化(深度监视)
number: {
deep: true,
handler(newValue) {
console.log("number 被修改了", newValue);
}
}
}
})
// 也可以使用 vm.$watch() 方法对属性进行侦听
// vm.$watch('isHot', {...})

Vue 过滤器

概念: Vue.js 允许你自定义过滤器,可以被用作一些常见的文本格式化,过滤器可以用在两个地方:插值表达式和 v-bind 表达式,过滤器应被添加在 JavaScript 表达式的尾部,由管道符指示。

全局过滤器

<p>{{ date | formatDate }}</p>
<script>
Vue.filter('formatDate', function(data){
return data + 'extra';
});
</script>

局部过滤器

如果全局过滤器和私有过滤器重名了,则会优先使用局部过滤器(就近原则)。

<p>{{ date | formatDate }}</p>
<script>
const app = new Vue({
data: {
msg: ""
},
methods: {},
filters: {
formatDate(data) {
return data + "extra";
}
}
});
</script>

Vue 自定义指令

简介: 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

指令会在如下两种情况时调用:
1. 指令与元素绑定成功时。
2. 指令所在的模板被重新解析时。

如果指令名称涉及多个单词,定义时可以用减号连接并用引号包裹。
‘big-number’(element, binding){}

在自定义指令的函数中,this 为 window 对象而不是 Vue 实例对象。

全局自定义指令

<input type="text" v-focus>
<script>
Vue.directive('focus', {
bind(el, binding) {}, // 元素被 VUE 实例解析的时候调用(el为元素)
inserted(el, binding) { // 元素被插入到文本的父节点时调用
el.focus()
},
update(el, binding) {} // 元素被 VUE 实例更新的时候调用(el为元素)
});
</script>

局部自定义指令

<input type="text" v-focus>
<script>
const app = new Vue({
data: {
msg: ""
},
methods: {},
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
},
// 简写:默认为 bind 方法 和 update 方法。
// focus(element, binding) {
// //
// }

})
</script>

钩子函数参数

指令钩子函数会被传入以下参数:

  • el: 指令所绑定的元素,可以用来直接操作 DOM。
  • binding: 一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive=”1 + 1” 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive=”1 + 1” 中,表达式为 “1 + 1”。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

钩子函数简写

在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:

<script>
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
</script>

Vue 监听数据的原理

  1. Vue 会监视 data 中所有层次的数据。
  2. Vue 通过 setter 实现监视,且要在 new Vue 时就传入要监视的数据。
  3. 给对象中追加属性,要用如下两种方式,否则 Vue 不会做响应式处理。
    Vue.set(target, propertyName/index, value)
    vm.$set(target, propertyName/index, value)
  4. Vue 通过包裹数组更新元素的方法实现数组的监测,所以更新数组时用下列方法,Vue 才会重新渲染页面:
    push(), pop(), shift(), unshift(), splice(), sort(), reverse()
    Vue.set(), vue.$set()
  5. Vue.set() 和 vue.$set() 不能给 vm 或 vm 的根数据对象添加属性!

Vue 的生命周期

创建阶段

  1. new Vue(): 创建一个Vue的实例

  2. Init Events & Lifecycle

  3. beforeCreate()

  4. Init injections & reactivity: 初始化 data 和 methods

  5. created()

  6. 编译模板页面(不放在页面中)。

  7. beforeMount(): 即将渲染(在这个函数中,页面中的DOM元素是原始的)。

  8. 将指定的 el 元素替换为编译好的 HTML 结构。

  9. mounted(): 已完成渲染,创建阶段结束,进入运行阶段。

运行阶段

主要工作就是根据最新的 data 值来重新渲染页面。

当 data 更新之后触发一下事件:

  1. beforeUpdate(): 页面渲染之前。

  2. updated(): 页面渲染完毕之后。

销毁阶段

  1. beforeDestroy(): 销毁之前调用。

  2. destroyed(): 销毁之后。

Vue 发送请求

vue-resource

https://github.com/pagekit/vue-resource

axios

axios.get(url).then(response=>{});

Vue 动画

  • Vue 把一个完整的动画,拆分成了两部分,入场动画 和 出场动画。

  • 入场动画中,包含两个时间点,分别是 进入之前(v-enter) 和 进入之后(v-enter-to)。

  • v-enter 表示动画入场前的起使状态,v-enter-to 表示动画入场完成之后的终止状态。

  • v-enter-active 表示入场动画的时间段,在这里可以规定动画的时长、动画效果。

使用 Vue 动画

  1. 把要实现动画的元素,用 <transition> 元素包裹起来。

  2. 要实现动画的元素,必须使用 v-ifv-show 来进行控制。

  3. style 里设置 .v-enter,.v-leave-to.v-enter-active,.v-leave-active 的样式。

  4. 可以使用钩子函数设置自定义动画。

<style scoped>
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateX(100px);
}

.v-enter-active,
.v-leave-active {
transition: all 0.5s ease;
}
</style>

<b-button variant="primary" @click="flag=!flag">按钮</b-button>
<transition>
<p v-show="flag">动画测试</p>
</transition>

使用现成动画库

  1. 导入动画css
npm install --save animate.css
import 'animate.css'
  1. 设置动画
<p v-show="flag" class="animate__animated animate__bounce animate__faster">动画测试</p>

列表动画

不知道为啥没效果,可能什么地方不对吧,懒得折腾动画了。

<template>
<div class="hello">
<transition-group tag="ul">
<li v-for="(item,i) in list" :key="item.id" @click="remove(i)">{{ item.name }}</li>
</transition-group>
</div>
</template>

<script>
export default {
name: "HelloWorld",
data() {
return {
list: [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 3, name: "李四" },
{ id: 4, name: "李四" },
],
flag: false,
};
},
methods: {
remove(i) {
this.list.splice(i, 1);
},
},
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
line-height: 30px;
border: 1px solid #ccc;
margin: 5px;
font-size: 12px;
width: 500px;
padding-left: 20px;
}
li:hover {
background-color: orange;
cursor: pointer;
}

.v-enter,
.v-leave-to {
opacity: 0;
transform: translateY(200px);
}

.v-enter-action,
.v-leave-action {
transition: all 0.5s ease;
}
</style>

Vue 自定义组件

  • template 属性中,不能单独放一段文本,必须用标签包裹起来。

  • 如果要在 template 中放置多个元素,那么在这些元素外必须有唯一的根元素进行包裹。

全局自定义组件

第一种方式

<div id="app">
<mycom1></mycom1>
</div>

<script>
// 先调用 Vue.extend 得到组件的构造函数。
const com1 = Vue.extend({
template: '<h1>这是创建的一个全局组件</h1>'
})
// 通过 Vue.component('组件名称', '组件的构造函数') 来注册全局组件。
Vue.component('mycom1', com1);
</script>

第二种方式

<div id="app">
<mycom2></mycom2>
</div>

<script>
Vue.component('mycom2', {
template: '<h1>这是直接使用 Vue.component 创建出来的组件</h1>'
});
</script>

第三种方式

<!-- 定义一个 template 标签元素 -->
<!-- 使用 Vue 提供的 template 标签,可以定义组件的UI模板结构 -->
<template id="tmpl">
<div>
<h3>这是外界定义的组件UI结构</h3>
</div>
</template>

<div id="app">
<mycom3></mycom3>
</div>

<script>
Vue.component('mycom3', '#tmpl');
</script>

局部自定义组件

<div id="app">
<mycom4></mycom4>
</div>

<script>
const app = new Vue({
methods: {},
directives: {},
components: {
"mycom4": {
template: '<h6>这是定义的局部组件 {{ msg }}</h6>',
data: function(){
msg: 'data'
}
}
}
})
</script>

组件中的 data 需要定义成一个 function ,因为每实例化一个组件,就会调用一次 data 的 function 来获取一个新的值,从而保证每个组件的 data 数据都是独立的。

评论例子

<template>
<div class="hello">
<cmt-box @func="add"></cmt-box>
<ul>
<cmt-item v-for="(item,i) in list" :key="i" :item="item"></cmt-item>
</ul>
</div>
</template>

<script>
export default {
name: "HelloWorld",
data() {
return {
list: [
{ id: 1, name: "张三", content: "我是张三" },
{ id: 2, name: "李四", content: "我是李四" },
{ id: 3, name: "小黄", content: "我是小黄" },
{ id: 4, name: "小红", content: "我是小红" },
],
};
},
methods: {
add(item) {
this.list.push(item);
},
},
components: {
"cmt-item": {
template: `<li><h5>评论人: {{ item.name }}</h5><p>{{ item.content }}</p></li>`,
props: ["item"],
},
"cmt-box": {
template: `
<div class="m-4">
<label>昵称:</label>
<input type="text" v-model="name" />
<br />
<label>内容:</label>
<textarea name id rows="3" v-model="content"></textarea>
<br />
<button @click="cmt_box_add">发表</button>
</div>`,
data() {
return {
name: "",
content: "",
};
},
methods: {
cmt_box_add() {
this.$emit("func", { name: this.name, content: this.content });
},
},
},
},
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
line-height: 30px;
border: 1px solid #ccc;
margin: 5px;
padding-top: 15px;
padding-left: 20px;
width: 500px;
}
</style>

使用 refs 来获取元素和组件

  • 被用来给元素或子组件注册引用信息(id 的替代者)
  • 应用在 HTML 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(VueComponent)
<!-- 在元素加上加上 ref 属性 -->
<h3 ref="myh3"></h3>

<script>
// 在需要的地方使用
console.log(this.$refs.myh3.innerHTML);
</script>

使用 props 给子组件传值

父组件传值方式:和属性赋值一样,直接写在子组件的标签中。

<Demo name=""></Demo>

子组件接受方式:

// 第一种方式:只接受
props: ['name']

// 第二种方式:限制类型
props: {
name: String
}

// 第三种方式:完整形式
props: {
name: {
type: String,
required: true, // 必要性
default: '张三' // 默认值,与必要性一般不连用
}
}

注意:props 是只读的,Vue 底层会检测你对 props 的修改,但是会发出警告。若业务需求确实要修改,可以在 data 中定义一个新的变量,赋值为属性的值。

data() {
return {
myAge: this.age
}
},
props: ['age']

Vue mixin(混入)

功能:可以吧多个组件共用的配置提取成一个混入对象。
步骤:创建一个 js 文件存放 mixin 数据,其他组件引入混入数据,即可使用。
注意:如果混合中的 data 与组件中的 data 冲突,则会使用组件中的 data;如果混合数据与组件都有 mounted 生命周期函数,则都会调用。

// mixin.js
export const mixinMethods = {
methods: {
showName() {
alert(this.name)
}
}
}

// Student.vue
import ( mixinMethods ) from '../mixin'

export default {
name: 'Student',
data() {
return {
name: '张三'
}
},
mounted() {
this.showName();
},
mixins: [mixinMethods]
}

全局混入:调用 Vue.mixin() 函数注册全局混入,其他所有组件都可以直接使用。

// main.js
import { mixinMethods } from './mixin'

Vue.mixin(mixinMethods)

Vue 插件

功能:用于增强 Vue
本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个及以后的参数是插件使用者传递的数据。

定义插件:

// plugins.js
export default {
// a, b, c 是用户在使用插件时传入的数据,一般只有一个 Vue 参数
install(Vue, a, b, c) {
console.log(a, b, c)

// 注册一个全局过滤器
Vue.filter('mySlice', function(value){
return value.slice(0, 4)
})

// 注册一个全局指令(部分)
Vue.directive('fbind', {...})

// 定义一个全局混入(部分)
Vue.mixin({...})

// 给 Vue 原型上添加一个方法
Vue.prototype.hello = () => { alert('Hello') }

// ......
}
}

使用插件:

// main.js
import plugins from './plugins'

Vue.use(plugins, 1, 2, 3) // 后面的三个参数是 install 方法中接收的参数,一般不会传数据

scoped 样式

作用:让样式在局部生效,防止冲突。

<style scoped>
</style>

另外,在 style 标签中也可以指定 CSS 的 lang 属性,可以直接写 less,默认是 css。

<style lang='less' scoped>
</style>