Vue 文档说 Vue.js 的核心思想是数据驱动和组件化。
沿革
原生DOM操作 => jQuery(2006年) => Node.js 服务器 => Angular(2009年) => React(虚拟DOM、JSX语法) => Vue(2014年)
数据驱动
(通过数据控制页面)数据驱动采用的是声明式编码,将视图用模板语法来声明式地渲染进DOM,使数据和DOM建立关联,完成数据绑定,实现响应式。
| 编码方式对比 | 内容 | 备注 |
|---|---|---|
| 命令式编码 | JS原生 | 直接操作DOM |
| 声明式编码 | vue 模板语法 | 无需直接操作DOM |
不再和HTML直接交互,一个Vue应用挂载到一个DOM元素上(#app),然后对其完全控制。
模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板可被浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
若对虚拟 DOM 和原生 JS 比较熟悉,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。
| vue 模板语法 | 常用形式 | 作用 | 备注 |
|---|---|---|---|
插值语法{{ xxx }} | <h1>{{ name }}</h1> | 用于解析标签体 | xxx只能写JS表达式(特殊的JS语句 会产生一个值)xxx可读取到vm上的所有属性和方法 |
| 指令语法 前缀 v- | 1. 绑定元素的属性 v-bind (简写:) 2. 条件渲染 v-if 控制元素显示隐藏 3. 循环遍历元素 v-for 4. 处理用户输入 v-on 给元素添加事件 5. 双向绑定:表单输入和应用状态 v-model | 用于解析标签(包括标签属性 标签体内容、绑定事件) <a v-bind:href="school.url"> | 指令是Vue提供的特殊属性 动态绑定属性 v-bind:简写为:绑定事件 v-on:事件名简写为@事件名 |
插值语法
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<div id="app">
{{ message }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
指令语法
<div id="app-2">
// v-bind:指令是指 将span元素节点的 title 属性和 Vue实例 data 中 message 保持一直
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
<script>
var app2 = new Vue({
el: '#app-2',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
})
</script>
数据绑定
上例 指令语法 中的v-bind:将数据与视图中span标签绑定。数据只要发生变化,页面中用到数据的地方随之变化。
| 标题 | 内容 | 备注 |
|---|---|---|
| v-bind | 单向数据绑定 数据只从data流向页面 | |
| v-model | 双向数据绑定 数据在data和页面间双向流通 | 只用在表单类元素 |
<div id="root">
// 普通写法:
单向数据绑定:<input type="text" v-bind:value="name">
双向数据绑定:<input type="text" v-model:value="name">
// 简写:
单向数据绑定:<input type="text" :value="name">
双向数据绑定:<input type="text" v-model="name">
</div>
new Vue({
el:"#root",
data:{
name:'tony'
}
})
条件渲染
v-if
用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。当与v-for一起使用时,v-for 优先级更高。
<div id="app-3">
// `v-if` 指令将根据表达式 `seen` 的值的真假来插入/移除 `<p>` 元素。
<p v-if="seen">现在你看到我了</p>
</div>
<script>
var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})
</script>
v-else
v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。
<p v-if="false">第3个p标签</p>
<p v-else>第4个p标签</p>
// 渲染时显示:第4个p标签
v-show
<h1 v-show="true">Hello!</h1>
带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display
| 比较 | 内容 | 备注 |
|---|---|---|
| v-if 用于条件很少改变 | “真正”的条件渲染 更高的切换开销 | 条件第一次变为真时,才会开始渲染条件块。 |
| v-show 用于频繁地切换 | 基于 CSS 进行切换 更高的初始渲染开销 | 不管初始条件是什么,元素总是会被渲染 |
列表渲染
用 v-for 基于一个数组来渲染一个列表
使用v-for时尽量提供key属性,
<li v-for="(item, index) in items" :key="item.id">...</li>
// `items` 是源数据数组,而 `item` 则是被迭代的数组元素的别名
// `index` 是当前项的索引; `in` 可以替换为 `of`
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
<script>
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
})
</script>
在 v-for 里使用对象
用来遍历一个对象的属性
<ul id="v-for-object" class="demo">
<li v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
事件处理
添加事件
实现用户交互,用v-on 指令监听DOM事件,通过它调用在 Vue 实例中定义的方法,其中v-on:可缩写为@:
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>
</div>
<script>
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
</script>
在不触碰 DOM 的前提下,通过reverseMessage方法,更新了应用的状态,所有的 DOM 操作都由 Vue 来处理。
事件修饰符
<!-- 阻止单击事件继续传播 -->
<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>
按键修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
按键码
<input v-on:keyup.13="submit">
表单输入绑定
基础用法
用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model 是语法糖,负责监听用户的输入事件以更新数据。
文本
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
多行文本
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
复选框
// 单个复选框,绑定到布尔值:
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
// 多个复选框,绑定到同一个数组:
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
new Vue({
el: '...',
data: {
checkedNames: []
}
})
收集表单数据 v-model
| 标题 | 内容 | 备注 |
|---|---|---|
<input type="text"> | v-model收集的是 value 值 | 用户输入的就是 value 值 |
<input type="radio"> | v-model收集的是 value 值 | 要给 标签 配置 value 值 |
<input type="checkbox"> | 未配置 input 的 value 属性 收集的就是 checked 配置 input 的 value 属性 v-model 的初始值是数组,收集的就是 value 组成的数组 |
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account" > <br />
密码:<input type="text" v-model="userInfo.password" > <br />
年龄:<input type="number" v-model.number="userInfo.age" ><br />
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex"value="female">
<br />
爱好:
吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat" >
睡觉<input type="checkbox" v-model="userInfo.hobby" value="sleep" >
打豆豆<input type="checkbox" v-model="userInfo.hobby" value="boom">
<br />
所属校区:
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="nanjing">南京</option>
</select>
<br />
其他信息:
<textarea v-model.lazy="userInfo.other"></textarea>
<br />
<input v-model="userInfo.agree" type="checkbox" />
阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<button>提交</button>
</form>
new Vue({
el: "#root",
data() {
return {
userInfo: {
account: "",
password: "",
age: "",
sex: "male",
hobby: [],
city: "shanghai",
other: "",
agree: "",
},
};
},
methods: {
demo() {
console.log(JSON.stringify(this.userInfo));
},
},
});
自定义指令 directives
定义一个v-big指令,会把绑定的数值放大10倍:
<h2>当前的n值是:<span v-text="n"></span><h2>
<h2>放大10倍后的n值是:<span v-big="n"></span><h2>
<button @click="n++">点我n+1</button>
new Vue({
el:'#root',
data:{
n:1
},
directives:{
big(element, binding){
element.innerText = binding.value * 10
}
}
})
组件化构建应用
何为组件:实现应用中局部功能代码和资源的集合;
单文件组件 SFC
// 结构
<template>
<h3> SFC </h3>
<template>
// 逻辑(数据、方法等)
<script>
export default {
name:'MyComponent'
}
</script>
// 样式 scoped 仅对当前组件
<style scoped>
h3{color:red;}
</style>
加载组件
// App.vue
<template>
<MyComponent />
<template>
<script>
import MyComponent from './MyComponent.vue'
export default {
name:'App',
components:{
MyComponent
}
}
</script>
Props 组件交互(父传子)
// App.vue
<template>
<MyComponent :title="title" :names="names"/>
<template>
<script>
import MyComponent from './MyComponent.vue'
export default {
name:'App',
data(){
return{
title:"我是一个标题",
names:["frank","jordan","bob"]
}
}
components:{
MyComponent
}
}
</script>
<template>
<h3> prop传递数据 </h3>
<p> {{title}} </p>
<ul>
<li v-for="(item,index) in names" :key="index">{{item}}</li>
</ul>
<template>
<script>
export default {
name:'MyComponent',
props:{
title:{
type:String,
default:""
},
names:{
type:Array,
// 数组和对象必须用函数返回
default:function(){
return []
}
}
}
}
</script>
<style scoped>
h3{color:red;}
</style>
自定义事件 组件交互(子传父)
<template>
<h3> 自定义事件传递数据 </h3>
<button @click="sendClickHandle">点击传递</button>
<template>
<script>
export default {
name:'MyComponent',
data(){
return{
message:"我是MyComponent数据"
}
},
methods:{
sendClickHandle(){
this.$emit("onEvent", this.message)
}
}
}
</script>
<style scoped>
h3{color:red;}
</style>
<template>
<h3> 单文件组件 </h3>
<button @cilck="sendHandle"> send </button>
<template>
<script>
export default {
name:'MyComponent',
methods:{
sendHandle(){
this.$emit("onCustom","数据")
}
}
}
</script>
<style scoped>
h3{color:red;}
</style>
Vue 实例
创建实例vm
const vm = new Vue(options)
// 可简写为 new Vue
vm.__proto__ = Vue.prototype
// 任何一个对象的原型 = 其构造函数的共有属性
vue.__proto__ = Function.prototype
用Vue函数创建一个新的 Vue 实例vm,这个vm对象封装了对视图层的所有操作,包括数据读写、事件绑定、DOM更新(不包括网络层的AJAX)
一个Vue应用由一个通过 new Vue创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。
new Vue的参数options里面有什么
| options | 内容 |
|---|---|
| 数据 | data内部数据、methods方法 props外部属性、computed计算属性(复杂逻辑) watch 监听data变化时做响应改变 |
| DOM | el 指定替换数据的容器,与$mount有替换关系 template 完整版: html + 占位符 render 非完整版: render:h =>h(Demo) |
| 生命周期钩子 | created、updated、mounted |
| 资源 | directives指令、filters过滤 |
| 组合 | provide依赖、inject注入、mixins |
data 内部数据
const Vue = window.Vue
new Vue({
data:{ // data为一个对象
n:0
},
data:function(){ //data为函数,可缩写为 data(){}
return{
n:0
}
},
template:`
<div id="app">
{{n}}
<button @click="add">+1</button>
</div>
`,
methods:{
add(){
this.n += 1
}
}
}).$mount('#app')
methods 事件处理函数或普通函数
const Vue = window.Vue
new Vue({
data(){
return{
n:0,
array:[1,2,3,4,5,6,7,8]
}
},
template:`
<div id="app">
{{n}}
<button @click="add">+1</button>
<hr>
{{filter()}}
</div>
`,
methods:{
add(){
this.n += 1
},
filter(array){
this.array.filter(i => i % 2 === 0)
}
}
})
props 外部属性
与data内部数据有所不同,由外部来传值
// Demo.vue 底层组件
<template>
<div class="red">
<!--这里是 Demo 的内部-->
{{message}}
</div>
</template>
<script>
export default{
props:['message']
//声明方式是写上属性名,从外部接受一个'message',并自动绑在this上面
}
</script>
// 使用组件
const Vue = window.Vue
import Demo from './Demo.vue'
new Vue({
components:{Demo},
data:{
visible: true
},
template:`
<Demo message="你好 props"/>
//从外部传'message',传的方式是在组件后面,加上 key、value
`,
methods:{
toggle(){
this.visible = !this.visible
}
}
}).$mount('#frank')
computed 计算属性
就是插值语法的升级用法,不直接填数据,而是添加处理数据复杂逻辑的方法。对比如下:
// 普通的插值语法,直接填数据
<div id="example">
{{ message }}
</div>
// 升级的插值语法,填计算属性
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
const vm = new Vue({
el:'#example',
data:{
message:'Hello'
},
computed:{
reversedMessage:function(){
return this.message.split('').reverse().join('')
}
}
})
- 计算属性缓存 vs 方法调用
上例还可以直接写为方法调用:
<p>Reversed message: "{{ reversedMessage() }}"</p>
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
这两种方式结果是相同的,但计算属性是基于它们的响应式依赖进行缓存的只在相关响应式依赖发生改变时它们才会重新求值。需要缓存是对于性能开销较大的计算属性,假如是需要遍历一个巨大的数组并做大量的计算,有了缓存就不需要多次执行。对于不希望有缓存可用调用方法来替代。
watch 监视属性
<div id="root">
<h2>weather is {{ info }}</h2>
<button @click="changeWeather">切换天气</button>
</div>
const vm = new Vue({
el:"#root",
data:{
isHot:true
},
// watch 的第一种写法
watch:{
// 监视的属性,配置有immediate、handler
isHot:{
immediate:false, // 初始化时让handler调用一下 默认false
// handler函数在 isHot 发生改变时 就调用
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
}
// watch 简写:默认不需配置 immediate 和 deep
isHot(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
}
})
// watch 的第二种写法:监视对象,配置对象 { }
vm.$watch('isHot',{
immediate:false,
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
})
// watch 的简写:
vm.$watch('isHot',function(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
})
- 深度监视
watch 默认不监测对象内部值的改变
data:{
isHot:true,
numbers:{
a:1,
b:1
}
},
watch:{
// 监视多级结构中某个属性的变化
'numbers.a':{
handler(){}
}
// 监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){}
}
}
- computed 和 watch 的区别
两者都是观察页面数据变化的。两者最大的区别是当需要在数据变化时执行异步或开销较大的操作时使用 watch。
| 标题 | 内容 |
|---|---|
| computed | 只有当依赖的数据变化时才会计算 当数据没有变化时, 它会读取缓存数据 |
| watch | 每次都需要执行函数 更适用于数据变化时的异步操作 |
生命周期钩子
Vue 在关键时刻帮我们调用的一些特殊名称的函数。
以下面代码为例:
<div id="root">
<h2>当前n值为:{{n}}</h2>
<button @click="add">点我n+1</button>
</div>
const vm = new Vue({
el: "#root",
data: {
n: 1,
},
methods: {
add() {
this.n++;
},
},
// 无法通过 vm 访问到 data 中的数据、methods 中的方法
beforeCreate(){
}
// 可通过 vm 访问到 data 中的数据、methods 中的方法
created(){
}
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用 mounted
mounted(){
}
})
资源
| 资源 | 备注 | 使用 |
|---|---|---|
| directives 指令 | 减少DOM操作的重复 | 全局用 Vue.directive('x',{...}) 局部用 options.directives |
| filters 过滤器 | 数组里筛选 | 尽量少用 |
| components 组件 | 引用其他vue文件 |
组合
| 组合 | 备注 | 使用 |
|---|---|---|
| mixins 混入 | 减少data、methods、钩子的重复 | -- |
| extends 扩展 | 作用跟mixin一样,也是复制 | 全局用 Vue.extend({...}) 局部用 options.extends:{...} |
| provide 依赖 | 大范围、隔n代共享信息 祖先提供东西 | -- |
| inject 注入 | 大范围、隔n代共享信息 后代注入东西 | -- |
| parent | -- | 全局用 Vue.mixin({...}) 局部用 options.mixins:[mixin1,mixin2] |
vm的数据与方法
Vue 实例创建时,它将 data对象中的所有属性加入到 响应式系统中,这些属性值发生改变时,视图会响应,并匹配更新为新值,进行重渲染。另外,只有当实例被创建时,就已经存在于 data中的属性才是响应式的,对于新添加属性的改动不会触发视图更新。
除了数据属性,即data的property,Vue 实例还暴露了实例属性与方法,都有前缀$,他们也是有用的。
vm的生命周期钩子
Vue在关键时刻调用(勾住)的特殊名称的函数,本质是回调函数。
生命周期函数 this 指向 vm 或组件对象;
| 主要流程 | 内容 |
|---|---|
| Init Events & Lifecycle | 生命周期、事件、但数据代理还未开始 |
| 挂载 | beforeCreate:此时无法通过vm访问到data中的数据、methods中的方法 Init injections & reactivity:初始化数据监测、数据代理 created:此时可以通过vm访问到data中的数据、methods中配置的方法 Compile el's outerHTML as template:开始解析模板,生成虚拟DOM,页面还不能显示解析好的内容 beforeMount:页面呈现未经Vue编译的DOM结构,所有对DOM操作最终都不奏效 Create vm $el and replace 'el' with it:将内存中的虚拟DOM转为真实DOM插入页面 mounted:页面呈现的是经Vue编译的DOM,所有对DOM操作有效,初始化过程结束 |
| 更新 | beforeUpdate:此时数据时新的,页面是旧的 Virtual DOM re-render and patch:完成了Model到View的更新 updated:此时数据是新的,页面是新的 |
| 销毁 | beforeDestroy:马上要执行销毁过程,vm中的data等处于可用状态 destroyed |
created(){
debugger //可以判断钩子是否生效
console.log('这个死鬼出现在内存中,没有出现在页面中')
},
mounted(){
console.log('我已出现在页面中')
},
updated(){
console.log('更新了')
console.log(this.n)
},
destroyed(){
console.log('已经消亡了')
},
每个 Vue 实例在被创建时,都要经过一系列的初始化过程。需设置数据监听、编译模板、将实例挂载到DOM并在数据变化时更新DOM等。同时在这个过程中会运行一些特殊的回调函数,即生命周期钩子的函数,为了给用户在不同阶段添加自己代码的机会。
created钩子可用来在一个实例被创建后执行代码:
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
Vue API
| 标题 | 内容 | 备注 |
|---|---|---|
| 全局配置 | Vue.config 是一个对象,包含 Vue 的全局配置。可以在启动应用之前修改属性:比如 productionTip | |
| 全局API | Vue.nextTick Vue.set | |
| 选项 | 数据、DOM、生命周期钩子、资源、组合、其他 | |
| 实例属性 | vm.$data | |
| 实例方法 | 数据、事件、生命周期 | |
| 指令 | 以 v- 开头 | |
| 特殊属性 | key、ref、is |
除上表外,还有的API有:内置的组件、VNode接口、服务端渲染。
感谢掘金博主飞跃疯人院的Vue原理解析系列文章!
感谢掘金博主前端论道的Vue相关文章!