Vue.js是什么
Vue.js是一个渐进式javascript框架,渐进式就是由浅入深、由简单到复杂的方式去使用Vue.js。Vue的核心是一个允许采用简洁的模版语法来声明式地将数据渲染进DOM的系统。
Vue有响应式和双向绑定特性。
响应式就是修改data中的数据,视图的数据也会发生改变,双向绑定是针对表单元素,比如文本框中输入的数据发生改变视图的数据也会改变。
组件化应用构建:
组件系统是Vue的另一个重要概念,因为它是一个抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。所有界面都可以抽象为一个组件树。
Vue中的所有组件都是Vue实例,我们只看到根组件有new Vue(),但是在子组件中并没有,为什么子组件也是Vue实例,那是因为webpack编译的时候会使用vue-loader去处理这些vue文件,生成一个个vue组件定义。
当一个Vue实例被创建时,它将data对象中的所有属性加入到Vue的响应式系统中。当这些属性的值发生改变时,视图将会产生响应,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时data中存在的属性才是响应式的。这里唯一的例外是使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。
生命周期钩子函数给了用户在不同阶段添加自己代码的机会。不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a)。因为箭头函数没有this,this会作为变量一直向上查找,经常导致报错。
指令是带有v-前缀的特殊特性。指令特性的值预期是单个javascript表达式(v-for是例外情况)。
动态参数
可以用方括号括起来的javascript表达式作为一个指令的参数:
<a v-bind:[attributeName]="url">...</a>
这里的attributeName会被作为一个javascript表达式进行动态求值,如果你的Vue实例有一个data属性attributeName,其值为href,那么这个绑定将等价于v-bind:href。
注意:如果返回的是非字符串则会触发警告,并且方括号中不能有空格或者引号,变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
计算属性和侦听器
计算属性
背景
模版内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模版中放入太多的逻辑会让模版过重且难以维护。所以对于任何复杂逻辑,你都应当使用计算属性。
例如:
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
从上面的例子中我们可以看到vm.reversedMessage 的值始终取决于 vm.message 的值。你可以像绑定普通属性一样在模版中绑定计算属性。Vue知道vm.reversedMessage 依赖于 vm.message,因此当vm.message发生改变时,所有依赖vm.reversedMessage的绑定也会更新。我们可以得出结论,data中定义的数据和computed中定义的数据都是响应式的。
计算属性缓存vs方法
背景:
我们为什么需要缓存?假设我们有一个性能开销比较大的方法A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有很多地方需要调用A。如果没有缓存,我们将会多次执行方法A,造成性能的浪费。这时候我们可以使用计算属性缓存的方式来代替。
我们可以用计算属性也可以直接在表达式中调用方法来达到同样的效果:
<p>{{reverseMessage()}}</p>
不管是计算属性还是方法得到的结果都是相同的。然后不同的是:计算属性是基于它们的响应式依赖进行缓存的。
只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要message还没有发生变化,多次访问reversedMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。比如:页面上有多个地方访问reversedMessage,那么它只会计算一次,返回的结果相同,但如果是用方法的话,访问几次就会调用几次。
计算属性的setter
计算属性默认只有getter,不过在需要时你也可以提供一个setter:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
现在再运行vm.fullName = 'john doe'时,setter会被调用,vm.firstName和vm.lastName也会相应地更新。
侦听器watch
背景
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
计算属性可以直接在模版还有js中使用,主要用在简单逻辑的运算上,而且是同步不能是异步的情况下使用,它必须返回的是一个数值,而watch可以用于异步或者复杂运算上,两者在某种程度上功能可以重合的,不过使用场景也有所不同
Class与Style绑定
绑定Class
有两种方式一种是对象一种是数组
<div :class="{ active: isActive }" class="static"></div>
如果是对象,那么active是class名,isActive可以是布尔值也可以是js表达式但是最终一定会被转为布尔值。
绑定的class也可以写在computed或data中
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
数组语法
可以是变量
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
可以是三目运算
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
可以是对象和变量
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
用在组件上
<my-component :class="baz boo"></my-component>
绑定内联样式
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div v-bind:style="[baseStyles, overridingStyles]"></div>
变量都可以绑定到data或computed中。
条件渲染
用key管理可复用的元素
【Vue会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使vue变得非常快之外,还有其他一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input> 不会被替换掉——仅仅是替换了它的 placeholder。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
使用场景:比如之前做的情报墙有多个图表循环切换的时候就会出现样式问题,这时候可以给它们一个key就有可能避免这种情况。
维护状态
当Vue正在更新使用v-for渲染的元素列表时,它默认使用就地更新
的策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时DOM状态(例如:表单输入值)的列表渲染输出。
为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的key属性。
简单的理解:比如一个输入框列表,在第一个输入框中输入了数值,把第一个输入框和第二个位置互换就会发现输入的数值还是在第一个输入框里,并没有随着第一个输入框位置的改变而改变
文章:https://juejin.im/post/5c2746b4e51d450d50306db2
https://www.zhihu.com/question/61078310
数组更新监测
除了以下两种方式无法监测其他情况都可以监测。
由于js的限制,vue不能监测以下数组 的变动:
1.当你利用索引值设置一个数组项时,也就是说改变数组中的某一个元素的值,例如vm.items[indexOfItem] = newValue
2.当你修改数组的长度时,例如:vm.items.length = newLength
举个例子:
let vm = new Vue({
data () {
items: ['a', 'b', 'c']
},
})
vm.items[1] = 'x' // 不是响应式的,不会触发dom更新
vm.items.length = 2 // 不是响应式的,不会触发dom更新
解决方法:
第一类问题解决方案:
vm.$set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
第二类问题解决方案:
vm.items.splice(newLength)
对象变更检测注意事项
还是由于js的限制,vue不能检测对象属性的添加或删除
总结:Vue不能检测到数组和对象某个元素的变化,导致数据发生变化但是页面没有响应
在v-for里使用值范围
v-for不仅可以接受数组、对象,还可以接受整数。在这种情况下,它会把模版重复对应次数。
<div>
<span v-for="n in 10">{{n}}</span>
</div>
v-for与v-if一同使用
不推荐在同一个元素上使用v-if和v-for。当它们处于同一节点,v-for的优先级比v-if更高。
在组件上使用v-for
<my-component v-for="item in items" :key="item.id"></my-component>
事件处理
监听事件的几种写法
直接写在html中
<div id="example1">
<button v-on:click="counter += 1">Add 1</button>
<p>{{ counter }}</p>
</div>
new Vue ({
el: '#example1',
data: {
counter: 0
}
})
这里适合比较简单的代码逻辑
事件处理方法
如果是比较复杂的代码逻辑一般会直接写在methods中,有时需要在内联语句处理器中访问原始的DOM事件。可以用特殊变量$event把它传入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
new Vue({
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
}
})
事件修饰符
passive修饰符
Vue对应addEventListener中的passive选项提供了.passive修饰符。
// 滚动事件的默认行为(即滚动行为)将会立即触发,而不会等待onScroll完成
// 这其中包含event.preventDefault()的情况
<div v-on:scroll.passive="onScroll">...</div>
这个.passive修饰符尤其能够提升移动端的性能。注意不要把.passive和.prevent一起使用,因为.prevent将会被忽略。
.exact修饰符
.exact修饰符允许你控制由精确的系统修饰符组合触发的事件。例如:
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
鼠标按钮修饰符
- .left
- .right
- .middle
表单输入绑定
基本用法
你可以用v-model指令在表单<inut>、<textarea>和<select>元素上创建双向数据绑定。v-model本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text和textarea元素使用value属性和input事件;
- checkbox和radio使用checked属性和 change事件;
- select字段将value作为prop并将change作为事件;
Vue双向绑定
很多时候我们在理解Vue的时候都把Vue的数据响应理解为双向绑定,但实际上这是不正确的,我们之前提到的数据响应,都是通过数据的改变去驱动DOM的变化,而双向绑定已有数据驱动DOM外,DOM的变化反过来影响数据,是一个双向关系,在Vue中,我们可以通过v-model来实现双向绑定。
v-model既可以作用在普通表单元素上,又可以作用在组件上,它其实是一个语法糖。
<input
v-bind:value="message"
v-on:input="message=$event.target.value">
<span>{{message}}</span>
new Vue({
data () {
return { message: '' }
}
})
这样子就实现了vue数据的双向绑定,当input输入的值发生改变时就会触发input事件,将当前输入框的值赋值给message,这时候页面的数据就会发生改变,页面的数据message发生改变时,因为input输入框绑定的是data中message的值,所以也会发生改变。这样子就实现了数据的双向绑定。所以说v-model实际上就是语法糖。
修饰符
.lazy
默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。你可以添加lazy修饰符,从而转变为使用change事件进行同步
<input v-model.lazy="msg">
.number
如果想自动将用户的输入值转为数值类型,可以给v-model添加number修饰符:
<input v-mode.number="age" type="number">
这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给v-model添加trim修饰符。
组件基础
data必须是一个函数
我们在定义组件的时候,你会发现它的data并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data () {
return {
count: 0
}
}
一个组件就是一个vue实例,同样也是一个对象,如果一个组件在一个项目中被多次使用,表示这个组件对象被复制了多次,它们都是指向同一个引用地址,如果直接给data赋值一个对象,那么改变其中一个组件中data的值,那么其他的组件data中的值也会发生改变,这不是我们想要的,所以为了保证每个组件对象互相独立,可以使用function return的方式来实现,因为function每次都会return一个对象,这个地方是新创建的,所以就不会存在上述问题。
使用事件抛出一个值
有时候用一个事件来抛出一个特定的值非常有用。例如我们可能想让<blog-post>组件决定它的文本要放大多少。这时候可以使用$emit的第二个参数来提供这个值:
<button v-on:click="$emit('enlarge-text', 1)">
Enlarge text
</button>
然后当在父级组件监听这个事件的时候,我们可以通过$event访问到被抛出的这个值:
<blog-post
v-on:enlarge-text="postFontSize += $event"
>
</blog-post>
$event既可以是dom元素的值也可以是这个dom元素还可以是子组件传过来的值
在组件上使用v-model,实现父子组件的双向绑定
自定义事件也可以用于创建支持v-model的自定义输入组件。记住:
<input v-model="searchText">
等价于:
<input
v-bind:value="searchText"
v-on:input="searchText="$event.target.value"">
当用在组件上时,v-model则会这样:
<template>
<div id="demo">
<test-model v-model="haorooms"></test-model>
<span>{{haorooms}}</span>
</div>
</template>
<script>
import testModel from './testModel'
export default {
watch: {
haorooms(val) {
console.log('haorooms', val)
}
},
data(){
return{
haorooms: 111
}
},
components: {
testModel,
}
}
</script>
子组件
<template>
<div>
<input
ref="input"
:value="haorooms"
@input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
props: ["haorooms"]
}
</script>
Vue 是单项数据流,v-model 只是语法糖而已
参考文章:https://www.haorooms.com/post/vue_vmodel
Vue组件化
概念
Vue的一个核心思想就是组件化,什么是组件化?组件化就是把页面拆分成多个组件,每个组件依赖的CSS、JavaScript、模版、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用,组件和组件之间可以互相嵌套,并且会对外暴露接口方便别的组件调用。
组件可以是一个模块也可以是一个功能,比如导航、下拉菜单、时间控件等等都可以。
组件化的特性
- 高内聚性:组件功能必须是完整的,最好是单一的,一个组件只负责一个功能。
- 低耦合度:通俗来说,代码独立不会和页面中的其他代码发生冲突。如果用以前的框架去编写代码,比如一个页面只有一个html文件,所有CSS、JS、HTML代码都写在这个文件上,那么这个文件会非常大,经常上千行,很不利于开发和维护,如果去掉某个功能,牵扯的代码很多,很容易出现问题,现在一个页面分为很多个组件,组件之间互相独立,就会避免这种问题。
组件化的优点
- 方便重复使用
- 提高了开发效率
- 简化调试步骤
- 提升整个项目的可维护性
- 便于协同开发
组件化解决的问题
组件化开发是为了解决复杂业务逻辑的开发思想,解决多人多团队协作难,组件化思想可以帮助我们去尽可能做到:谁开发,谁负责;谁管理,谁维护;职责清晰,沟通简单方便。
单页面应用(SPA)
单页面应用是指只有一个浏览器页面的应用,在浏览器中运行期间不会重新加载页面,之后所有的交互操作都在一个页面上完成,这些都是通过切换vue-router匹配不同的vue组件来显示不同的页面内容。
多页面应用(MPA):是指一个应用中有多个页面,页面跳转时是整页刷新。
单页面应用的好处
- 用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点SPA对服务器压力较小。从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。
- 单页面应用没有页面之间的切换,就不会出现"白屏现象",也不会出现假死并有"闪烁"现象。
- 良好的前后端分离。后端不再负责模版渲染、输出页面工作,只需要出数据就可以,后端API通用化,即同一套后端程序代码,不用修改就可以用于web界面、手机、平板等客户端。
单页面应用的缺点
- 首次加载耗时比较多:为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面可以在需要的时候加载。所以必须对JavaScript及CSS代码进行合并压缩处理,如果使用第三方库,建议使用一些大公司的CDN,因此带宽的消耗是必然的。
- SEO难度较高:由于所有的内容都在一个页面中动态替换显示,所以搜索引擎只能捕获到后端返回的数据,无法捕获到整个HTML的数据。
单页应用实现原理
单页应用是指在浏览器中运行的应用,在使用期间页面不会重新加载。当点击导航时,通过哈希监听事件,如果哈希发生了变化,则改变哈希值:window.location.hash,来调用相应的js文件。
相应的js文件里面可以放相应的数据模板,当用ajax请求并返回数据时,渲染模板,生成相应的DOM结构,再插入对应的page 的div中。
基本原理:以 hash 形式(也可以使用 History API 来处理)为例,当 url 的 hash 发生改变时,触发 hashchange 注册的回调,回调中去进行不同的操作,进行不同的内容的展示。
为什么单页面应用和vue-router有关系,是因为单页面应用不会重新加载页面,只是通过改变hash来匹配显示不同的组件。所以单页面应用是基于vue-router和组件来实现的。
首先vue是单页面应用,导致了切换成其他页面不会改变url,这时候发现改变hash路由不会触发url让页面重新渲染,所以就用hash来做vue路由,这也是vue路由的由来。vue路由就是用来匹配对应的组件。