Vue.js
vue.js
vue.js
是一套用于构建用户界面的渐进式框架
渐进式
Vue 核心
- 声明式渲染
- 组件
引入
我们还是先通过 <script> 的方式来引入 vue
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
复制代码
组件
vue
的核心功能之一就是组件
组件基本分类
- 根组件
- 可复用的功能组件
根组件的创建
通过 vue
提供的构造函数可以实例化出来一个跟组件实例对象
let app = new Vue(创建组件所需要的一些配置选项);
复制代码
可复用的功能组件
通过 Vue
提供的静态方法 component
窗口可复用的功能组件
let component1 = Vue.component(创建组件所需要的一些配置选项)
复制代码
组件配置选项:cn.vuejs.org/v2/api/
组件内容渲染
渲染一个组件的内容可以通过两种方式来进行
- template 选项
- render 选项(函数)
template
type : string
组件的模板结构(HTML),模板内容会被 vue
进行渲染,生成最终的 HTML
内容,并替换占位(挂载)元素
el
type : string | Element
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例,
- 该选择只对
new
创建的实例有效 - 如果提供
el
,但是没有提供template
,则el
的内容讲作为template
render
type : (createElement: () => VNode) => VNode
发挥 JavaScript
最大的编程能力,直接创建 VNode
(虚拟dom对象),优先级高于 el
和 template
- 有更强大的编程能力
- 有更高的性能
使用 $mount 方法延迟 Vue
实例的挂载
当 Vue
实例没有 el
选项的时候,它会处于一种 未挂载 的状态,我们可以通过组件 Vue
实例对象的 $mount
方法来手动挂载,通过该方式,我们也可以达到延迟 Vue
实例的挂载的目的
组件中的数据(状态)
data
组件内部使用的数据,data
是一个对象,data
中的值可以中模板中直接访问
Vue
实例组件(根组件)的data
是一个对象- 可复用功能组件的
data
必须是一个函数,且该函数必须返回一个对象(因为复用性,避免多个组件实例引用同一个对象。换句话说,组件中使用的数据必须是一个对象,但是可复用组件的这个数据对象必须通过函数返回
data 的访问
data
数据可以直接通过组件实例对象访问,也可以通过实例对象下的 $data
属性进行访问
组件实例对象下有很多的以
$
开头的属性,这些都是实例对象内置的一些属性和方法,vue
为了区分数据与内置属性方法,内置的属性和方法默认都是以$
开始的,所以我们中数据中应该避免使用$
开头的数据
模板语法
vue 使用了基于 html 的模板语法,使用声明式的方式把实例中的数据(data
)与 DOM 进行绑定,data
中的数据在模板中可以直接使用
Mustache(双大括号,大胡子) 语法
在 vue 中,我们是通过一对双大括号把实例中的数据渲染到模板内容中
插值表达式
在 {{}} 中,我们可以放置表达式值
{{表达式}}
复制代码
new Vue({
el: '#app',
data: {
title: 'vue 框架'
},
template: `<div>{{title}}</div>`
});
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
{{title}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'vue 框架'
}
});
</script>
</body>
</html>
复制代码
数据到视图的更新
vue 会把 data
中的数据挂载到实例属性下,同时对它们进行主动的监听拦截,当数据发生变化的时候,重新渲染模板。我们可以通过实例对象对数据进行修改
app.title = '开课吧';
// or
app.$data.title = '开课吧';
复制代码
检测变化的注意事项
在 vue3 之前,数据的监听是通过 Object.defineProperty
方法来实现的,但是该方法只能监听拦截单个数据,对于对象新增属性无法监听拦截。所以,对于数据对象中新增的属性,我们需要调用 vue 提供的方法来进行处理
扩展
通过 Object.defineProperty
监听拦截中存在一些问题
- 属性新增属性
- 数组方法:push、pop、shift、unshift、splice、sort、reverse
- 数组新增值:[]
- 数组 length 属性
以上的操作中并不会触发监听拦截
vue
对数组中的push
、pop
等方法进行重新包装,所以在vue
中调用这些方法,可以对数组的修改进行监听拦截
使用 Vue.set 方法添加新数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<p>title: {{title}}</p>
<p>user.username: {{user.username}}</p>
<p>user.gender: {{user.gender}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'vue 框架',
user: {
username: 'zMouse'
}
}
});
</script>
</body>
</html>
复制代码
在模板中,我们使用了一个不存在的数据 user.gender
如果我们通过 app.user.gender = '男'
的方式来新增,是不会被 vue 拦截监听处理的,我们需要使用
Vue.set(app.user, 'gender', '男');
// 实例.$set 是 Vue.set 的别名
app.$set(app.user, 'gender', '男');
复制代码
这样的方式,set
方法给 app.user
添加 gender
属性的同时,对它进行了 defineProperty
指令
表达式的值除了可以出现内容中,也可以使用在其它位置,比如:属性。但是不能使用 {{}}
语法,而是需要 指令
在 vue
中,指令是一个带有 v-
前缀的属性,与普通属性不一样的地方在于,指令的值是引号括起来的 表达式
,不同的指令有不同的作用,vue
内置了一些常用的指令,后期我们还可以自定义属于自己的指令
- 内容输出
- 循环
- 逻辑
- 属性绑定
- 事件
- 其它
内容输出
通过 {{}}
我们可以很方便的中模板中输出数据,但是这种方式会有一个问题,当页面加载渲染比较慢的时候,页面中会出现 {{}}
,vue
提供了几个指令来解决这个问题
指令中的表达式不需要使用
{{}}
v-text
<p v-text="title"></p>
复制代码
弊端:
v-text
会填充整个innerHTML
v-cloak
<p v-cloak>{{title}}</p>
复制代码
需要配合 css 进行处理
<style>
[v-cloak] {
display: none;
}
</style>
复制代码
v-html
为了防止 xss
攻击,默认情况下输出是不会作为 html
解析的,通过 v-html
可以让内容作为 html
进行解析
v-once
只渲染元素和组件一次,后期的更新不再渲染
v-pre
忽略这个元素和它子元素内容的编译
逻辑处理
v-show
根据表达式的值(布尔值),切换元素的显示与隐藏(display 属性)
适用于状态切换比较频繁的情况
v-if
根据表达式的值(布尔值),创建或销毁元素
适用于状态切换不频繁的情况
v-else / v-else-if
与 v-else
配合
循环与列表
v-for
根据数据循环渲染 v-for
指令所在的元素及其子元素
可以循环的数据:Array | Object | number | string | Iterable (2.6 新增)
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, key, index) in object"></div>
复制代码
v-for 中也可以使用 of 语法,在 vue 中两者没有什么区别
:key
默认情况下,在渲染 DOM
过程中使用 原地复用 ,这样一般情况下会比较高效,但是对于循环列表,特别是依赖某种状态的列表,会有一些问题,我们可以通过 :key
属性,来给每个循环节点添加一个标识
属性绑定
v-bind
绑定数据(表达式)到指定的属性上,<div v-bind:参数="值/表达式"></div>
,这里的参数就是指定的属性名称
<div id="app">
<div v-bind:id="'box1'"></div>
<div v-bind:id="myId"></div>
</div>
<script>
new Vue({
el: '#app',
data: {
myId: 'kaikeba'
}
})
</script>
复制代码
缩写
有的一些常用指令会有对应的缩写,v-bind
对应的缩写为::
<div :id="myId"></div>
复制代码
样式
针对样式属性,v-bind
值有一些特殊的写法
style
原生普通写法
<div style="width: 100px; height: 100px; background: red"></div>
复制代码
v-bind 写法
<div :style="'width: 100px; height: 100px; background: red'"></div>
复制代码
对象写法
<div :style="style1"></div>
...
<script>
new Vue({
el: '#app',
data: {
style1: {
width: '100px',
height: '100px',
background: 'green'
}
}
});
</script>
复制代码
数组写法
<div :style="[style1, style2]"></div>
...
<script>
new Vue({
el: '#app',
data: {
style1: {
width: '100px',
height: '100px',
background: 'green'
}
},
style2: {
border: '1px solid black'
}
});
</script>
复制代码
class
原生普通写法
<div class="box1 box2"></div>
复制代码
v-bind 写法
<div :class="'box1 box2'"></div>
复制代码
数组写法
<div :class="['box1', 'box2']"></div>
复制代码
对象写法
<div :class="{'box1': isActive, 'box2': isChecked}"></div>
复制代码
使用对象写法,可以根据值(boolean)动态添加对应的 class
单向数据流
通过上面的知识点和案例,我们可以看到,当数据更新的时候,页面视图就会更新,但是页面视图中绑定的元素更新的时候,对应的数据是不会更新的
<input type="text" :value="title" />
复制代码
我们称为:单向数据流 数据 -> 视图
在 vue 中,还有一种双向数据流绑定的方式
v-model
<input type="text" v-model="title" />
复制代码
数据 title
更新,视图中 input
的 value
就会更新。同时,当 input 中的 value
更新的时候,数据 title
也会更新,这就是我们说的 数据双向绑定 [与 React 中的受控组件类似]
表单
针对一般元素,比如 div、span、p、img 等,采用的是单向绑定:v-bind,只需要把数据绑定到视图中就可以,但是对于表单这种交互性比较强的元素或组件,我们一般可能需求双向绑定,即:用户对视图元素的操作同时更新数据
v-model 在内部为不同的输入元素使用不同的属性和事件来处理数据
text
和textarea
checkbox
和radio
select
text
和 textarea
text
和 textarea
元素使用 value
属性和 input
事件
<div id="app">
<input type="text" v-model="v1" />
<textarea v-model="v2" cols="30" rows="10"></textarea>
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
v1: 'aaa',
v2: 'bbb'
}
});
复制代码
checkbox
和 radio
checkbox
和 radio
使用 checked
属性和 change
事件
单选框绑定一个值
<div id="app">
<input type="radio" v-model="v3" value="男" /> 男
<input type="radio" v-model="v3" value="女" /> 女
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
v3: '女',
}
});
复制代码
多选框绑定到一个布尔值或数组
<div id="app">
<input type="checkbox" v-model="v4" /> 同意
<hr/>
<input type="checkbox" v-model="v5" value="足球" /> 足球
<input type="checkbox" v-model="v5" value="音乐" /> 音乐
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
v4: true,
v5: ['足球', '音乐']
}
});
复制代码
select
select
字段将 value
作为 prop
并将 change
作为事件
单选绑定到值,多选绑定到数组
<div id="app">
<select v-model="v3">
<option value="男">男</option>
<option value="女">女</option>
</select>
<select v-model="v5" multiple>
<option value="足球">足球</option>
<option value="音乐">音乐</option>
</select>
</div>
复制代码
指令修饰符
一个指令可以包含的内容包括:
- 指令名称
- 指令值
- 指令参数
- 指令修饰符
<组件 指令:参数.修饰符1.修饰符2="值" />
复制代码
.lazy
取代 input
监听 change
事件
.number
输入字符串转为有效的数字
.trim
输入首尾空格过滤
自定义指令
我们还可以通过 Vue
提供的方法来自定义指令
注册指令
vue
提供了两种指令注册方式
- 全局指令
- 局部指令
全局指令
Vue.directive('指令名称', {指令配置});
复制代码
局部指令
new Vue({
el: '#app',
directives: {
'指令名称': {指令配置}
}
});
复制代码
在使用指令的时候,需要使用
v-指令名称
的方式来调用
指令生命周期(钩子函数)
指令的运行方式很简单,它提供了一组指令生命周期钩子函数,我们只需要在不同的生命周期钩子函数中进行逻辑处理就可以了
- bind : 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted : 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
- update : 所在组件更新的时候调用
- componentUpdated : 所在组件更新完成后调用
- unbind : 只调用一次,指令与元素解绑时调用
不同的生命周期钩子函数在调用的时候同时会接收到传入的一些不同的参数
- el : 指令所绑定的元素,可以用来直接操作 DOM
- binding : 一个对象,包含以下属性:
- name : 指令名,不包括
v-
前缀 - value : 指令的绑定值(作为表达式解析后的结果)
- expression : 指令绑定的表达式(字符串)
- arg : 传给指令的参数,可选
- modifiers : 传给指令的修饰符组成的对象,可选,每个修饰符对应一个布尔值
- oldValue : 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用
- name : 指令名,不包括
案例
官网的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.directive('focus', {
inserted(el) {
el.focus();
}
});
let app = new Vue({
el: '#app'
});
</script>
</body>
</html>
复制代码
扩展:自定义拖拽指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
position: absolute;
left: 100px;
top: 100px;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div id="app">
<button @click="canDrag = !canDrag">Drag : {{canDrag}}</button>
<div class="box" v-drag.limit="canDrag"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.directive('drag', {
bind(el, {modifiers,value}) {
let isDragStart = false;
let disX = 0;
let disY = 0;
el.canDrag = value;
el.addEventListener('mousedown', e => {
if (!el.canDrag) return;
disX = e.clientX - el.offsetLeft;
disY = e.clientY - el.offsetTop;
isDragStart = true;
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (isDragStart) {
let x = e.clientX - disX;
let y = e.clientY - disY;
if (modifiers.limit) {
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
}
el.style.left = x + 'px';
el.style.top = y + 'px';
}
});
document.addEventListener('mouseup', e => {
isDragStart = false;
});
},
componentUpdated(el, {value}) {
console.log('componentUpdated', value);
el.canDrag = value;
}
});
let app = new Vue({
el: '#app',
data: {
canDrag: false
}
});
</script>
</body>
</html>
复制代码
事件
在 vue
中,事件通过指令 v-on
进行绑定,v-on
缩写 @
<组件 v-on:事件名称="表达式" />
<组件 @事件名称="表达式" />
复制代码
组件的 methods
选项
在组件选项中,提供了一个 methods
选项,用来存放组件中使用的函数方法,且存放在 methods
中的函数方法可以通过组件实例(this)进行访问
通过内联方式绑定事件处理函数
<组件 @事件名称="fn" />
<script>
new Vue({
...,
methods: {
fn() {
//...
}
}
})
</script>
复制代码
- 事件绑定函数中的
this
指向组件实例 - 事件绑定函数中的第一个参数默认为
event
对象
<组件 @事件名称="fn('kaikeba', $event)" />
<script>
new Vue({
...,
methods: {
fn(name, ev) {
//...
}
}
})
</script>
复制代码
也可以在事件绑定中直接调用函数(并不会立即执行,也是通过事件触发执行的)
- 事件对象需要手动传入,名称为
$event
事件修饰符
在事件函数中,我们可以通过 ev.preventDefault()
、ev.stopPropagation()
来阻止默认行为,阻止冒泡,但是中 vue 中提供一些更加方便的方式来处理这些问题,这就是事件修饰符
.stop
.prevent
.capture
.self
.once
.passive
按键修饰符
vue
还提供了许多按键修饰符
.keyCode
<组件 @keyup.13="fn" />
复制代码
.enter
.down
.exact
原生事件
自定义组件中可以自定义一些事件,可以通过 .native
修饰符来指定监听原生中的事件,而不是组件自定义事件
computed
在实际的应用中,我们会有一些原始数据,同时在应用中又会有一些数据是根据某些原始数据派生出来的,针对这样的一种情况,vue
定义了一个专门用来处理这种派生数据的选项:computed
<div id="app">
<label><input type="radio" v-model="gender" value="" /> 所有</label>
<label><input type="radio" v-model="gender" value="男" /> 男</label>
<label><input type="radio" v-model="gender" value="女" /> 女</label>
<hr>
<ul>
<li v-for="user of showUsers">
{{user.username}}
</li>
</ul>
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
gender: '',
users: [
{id: 1, username: 'baogege', gender: '男'},
{id: 2, username: 'mt', gender: '男'},
{id: 3, username: 'haigege', gender: '男'},
{id: 4, username: 'zMouse', gender: '男'},
{id: 5, username: 'reci', gender: '女'},
{id: 6, username: 'lisi', gender: '女'}
]
},
computed: {
showUsers() {
return this.gender === '' ? [...this.users] : this.users.filter(user=>user.gender===this.gender);
}
}
});
复制代码
- 计算属性类似
getter
和setter
,当访问某个计算属性的时候,就会调用computed
中同名的函数,函数的返回值将作为该计算属性的值 - 计算属性的值依赖计算函数中依赖的其它响应式数据
- 计算属性的值可以缓存,如果依赖的其它响应式数据没有发生变化,但多次访问该计算属性,得到结果是最近一次变化产生的值(相对于调用方法得到结果在某些时候性能要好一些)
<div id="app">
<p>{{now}}</p>
<button @click="showDate=true">showDate</button>
<p v-if="showDate">{{now}}</p>
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
showDate: false
}
computed: {
now() {
return Date.now();
}
}
});
复制代码
计算属性的 getter
与 setter
默认情况下,计算属性函数是一个 getter
函数,如果计算属性只有 get 需求,则可以简写
computed: {
now() {
return Date.now();
}
// 等于
now: {
get() {
return Date.now();
}
}
}
复制代码
但是有的时候,这种派生数据既有 get
需求,也有 set
需求
<div id="app">
<label><input type="radio" v-model="gender" value="" /> 所有</label>
<label><input type="radio" v-model="gender" value="男" /> 男</label>
<label><input type="radio" v-model="gender" value="女" /> 女</label>
<hr>
<ul>
<li v-for="user of showUsers">
<input type="checkbox" v-model="user.checked" />
{{user.username}}
</li>
</ul>
<label><input type="checkbox" v-model="checkAll">全选</label>
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
gender: '',
users: [
{id: 1, username: 'baogege', gender: '男',checked:false},
{id: 2, username: 'mt', gender: '男',checked:false},
{id: 3, username: 'haigege', gender: '男',checked:false},
{id: 4, username: 'zMouse', gender: '男',checked:false},
{id: 5, username: 'reci', gender: '女',checked:false},
{id: 6, username: 'lisi', gender: '女',checked:false}
]
},
computed: {
showUsers() {
return this.gender === '' ? [...this.users] : this.users.filter(user=>user.gender===this.gender);
},
checkAll: {
get() {
return this.users.every(user=>user.checked);
},
set(newValue) {
this.users = this.users.map(user=>{
return {
...user,
checked: newValue
}
});
}
}
}
});
复制代码
watch
有的时候,我们需要的派生数据是通过异步的方式处理的,这个时候,计算属性就不太好用了(不能处理异步)。
我们可以使用另外一个选项:watch
<div id="app">
<input type="text" v-model="keyWord">
<hr>
<ul>
<li v-for="user of showUsers">
{{user.username}}
</li>
</ul>
</div>
复制代码
let app = new Vue({
el: '#app',
data: {
keyWord: '',
users: [
{id: 1, username: 'baogege', gender: '男',checked:false},
{id: 2, username: 'mt', gender: '男',checked:false},
{id: 3, username: 'haigege', gender: '男',checked:false},
{id: 4, username: 'zMouse', gender: '男',checked:false},
{id: 5, username: 'reci', gender: '女',checked:false},
{id: 6, username: 'lisi', gender: '女',checked:false}
],
showUsers: []
},
watch: {
keyWord(newVal, oldVal) {
// 模拟网络请求
setTimeout(_=>{
this.showUsers = this.users.filter(user=>user.username.includes(newVal));
}, 1000);
}
}
});
复制代码
多层监听
对于多层数据的监听,可以使用字符串+点语法
watch: {
'a.b.c': function() {
//...
}
}
复制代码
深度监听
默认情况下,watch
只对当前指定的值进行一层监听,如果需要对对象进行深度监听
watch: {
a: {
handler() {
console.log('a deep');
},
deep: true
}
}
复制代码
过滤器
过滤器是一个使用在 双大括号插值
和 v-bind
中,用于过滤输出内容的函数
假设有一个用于把内容转为大写的过滤器函数 toUpperCase
{{content|toUpperCase}}
复制代码
|
: 管道符,表示数据从左至右通过管道符进行传递- 过滤器可以有多个,执行顺序从左至右,过滤器函数第一个参数的值就是其管道符前一个的结果
注册过滤器
全局过滤器
Vue.filter('过滤器名称', 过滤器函数);
复制代码
局部过滤器
Vue.component('组件', {
...,
filters: {
'过滤器名称': 过滤器函数
}
})
复制代码
实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
{{title|toUpperCase|slice(3)}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
title: 'kaikeba'
},
filters: {
toUpperCase: function(val) {
return val.toUpperCase();
},
slice(val, n) {
return val.slice(n);
}
}
})
</script>
</body>
</html>
复制代码
脚手架
Vue 提供了一个脚手架工具,帮助我们快速搭建本地项目:vue-cli
安装
npm install -g @vue/cli
# OR
yarn global add @vue/cli
复制代码
创建项目
vue-cli 提供了两种使用方式
- 命令行
- UI
# 命令行
vue create 项目名称
# UI
vue ui
复制代码
运行命令以后,根据提示进行选择
目录结构
- node_modules/
- public/
- src/
- assets/
- components/
- app.vue
- main.js
- ...
复制代码
单文件组件
vue 也是基于组件的开发模式,我们知道一个 UI 组件包含
- 结构
- 样式
- 行为
为了能够更加方便的编写组件,vue 提供了一个特殊的组件定义文件:.vue 文件,我们也称为 单文件组件
组成
一个单文件组件的 结构、样式、行为 分别通过三个标签来进行定义和划分
- <html>
- <script>
- <style>