vue的实用指南
基本实用
常用表达
表达方式 | 作用 | 备注 |
---|---|---|
v-bind:title = ‘message’ | 第二种绑定方式,可直接作用在html特性上。 将这个元素节点的 title 特性和 Vue 实例的 message 属性保持一致 | 简写 v-bind:href == :href |
v-if = ‘seen’ v-else | 控制元素是否显示 | Vue 也提供一个强大的过渡效果系统,可以在 Vue 插入/更新/移除元素时自动应用过渡效果 |
v-for = ‘todo in todos’ | 循环数组进行显示 | 在控制台里,输入 app4.todos.push({ text: ‘新项目’ })可以往数组新增数据 |
v-on:click=’event’ | 添加事件监听器 | methods: { event: function () { this.message = this.message.split(‘’).reverse().join(‘’) } } 简写 v-on:click == @click |
v-modal=’message’ | 实现表单输入和应用状态之间的双向绑定 | <input v-model=”message”> |
v-once | 执行一次性插值,当数据改变时,插值处的内容不会更新。 | span v-once >这个将不会改变: </span> |
v-html | 输出真正的 HTML | <span v-html=”rawHtml”></span> |
组件化
注册组件: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
28
29
30
31// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
props:['todo']
template: '<li>{{ todo.text }}</li>',
})
//调用组件
<ol id='app'>
<!-- 创建一个 todo-item 组件的实例 -->
<todo-item v-for='item in grocyList' :todo='item'>
</todo-item>
</ol>
var data = {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
};
var app = new Vue({
el: '#app',
data: data
})
app.$data = data //true
// $watch 是一个实例方法
app.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `app.a` 改变后调用
})
props向子组件传递值,data是组件内部的值,并且需要设置初始值。
生命周期钩子有: created、mounted、updated 和 destroyed,
不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a) 或 vm.$watch(‘a’, newValue => this.myMethod())。因为箭头函数是和父级上下文绑定在一起的,this 不会是如你所预期的 Vue 实例,经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。
图示:
计算属性和侦听器
计算属性:用来规定复杂逻辑,防止在插值中逻辑太多。
调用方法:也可以在表达式中调用方法来达到相同效果。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<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<p>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('')
}
},
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
message属性更新,依赖的计算属性也更新了。 Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。
区别:
计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
每当触发重新渲染时,调用方法将总会再次执行函数。
侦听器:1
2
3
4
5
6
7watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
数组更新检测
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
- 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.items.length = newLength
【解决方法】
第一类问题,不仅更改值,也能触发状态更新(两种方法):1
2
3
4
5
6
7
8// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
或实例方法
vm.$set(vm.items, indexOfItem, newValue)
或者
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
第二类:1
vm.items.splice(newLength)
对象更新检测
由于js的限制,不能检测对象属性的添加或删除。只有实例创建时data中的属性值才是响应式的。如果已经创建了实例,就不能动态创建根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。
1 | Vue.set(vm.userProfile, 'age', 27) |
事件处理
1 | warn: function (message, event) { |
事件修饰符( 修饰符是由点开头的指令后缀来表示的。)
.stop
.prevent
.capture
.self
.once
.passive 滚动事件的默认行为 (即滚动行为) 将会立即触发,而不会等待 onScroll
完成,这其中包含 event.preventDefault()
的情况
1 | <!-- 阻止单击事件继续传播 --> |
按键修饰符
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left (鼠标事件)
.right
.middle
输入绑定
v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
.lazy 在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:(在“change”时而非“input”时更新)
.number 自动将用户的输入值转为数值类型. 这通常很有用,因为即使在 type=”number” 时,HTML 输入元素的值也总会返回字符串。
.trim 自动过滤用户输入的首尾空白字符
组件基础
组件必须只有一个根元素,组件的data必须为一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
内联:通过事件向父级组件发送消息($emit方法),用$event访问到值1
2
3
4
5
6
7
8<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
若是方法:直接作为第一个参数传入方法1
2
3
4
5
6
7
8
9
10
11<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
这个 0.1就直接传到enlargeAmount了
用于组件上时,v-model 等价于1
2
3
4<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
全局注册:使用 Vue.component 来创建组件。 这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。
局部注册:通过一个普通的 JavaScript 对象来定义组件,然后在 components 选项中定义你想要使用的组件1
2
3
4
5
6
7
8var ComponentA = { /* ... */ }
new Vue({
el: '#app'
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
比如什么输入框等相对通用的元素为基础组件,像element-ui里面一样,为了避免重复import,注册为全局组件。比如基础组件的自动化注册: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
28
29
30
31
32
33
34import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剥去文件名开头的 `./` 和结尾的扩展名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
组件传递的属性值一般会替换掉,但是class 和 style 特性会稍微智能一些,即父传递的和子组件本身的值会被合并起来。
如果你不希望组件的根元素继承特性,你可以设置在组件的选项中设置 inheritAttrs: false。
可以使用$attrs传递组件的属性。
自定义事件
注意:跟组件和 prop 不同,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。因此,推荐始终使用 kebab-case 的事件名(使用全小写,然后用短横线连接)。
自定义v-model:
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突。1
<base-checkbox v-model="lovingVue"></base-checkbox>
model: {
prop: ‘checked’, // 代表引用model时的值传入名叫checked的prop中
event: ‘change’ // 代表触发change时,lovingVue 值改变
}
1 | Vue.component('base-checkbox', { |
原生事件
用 v-on 的 .native 修饰符,在一个组件的根元素上直接监听一个原生事件。
不过在你尝试监听一个类似 <input>
的非常特定的元素时,这并不是个好主意。
这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。
为了解决这个问题,Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。
1 | Vue.component('base-input', { |
动态组件
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。
为了解决这个问题,我们可以用一个 1
2
3
4<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
常见问题
- 对于MVVM的理解?
MVVM 是 Model-View-ViewModel 的缩写。
- Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
- View 代表UI 组件,它负责将数据模型转化成UI 展现出来。
- ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
- vue的生命周期
- beforeCreate(创建前) 在数据观测和初始化事件还未开始
- created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来
- beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
- mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
- beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
- updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
- destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
vue实现双向绑定的原理 Object.defineProperty()
采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。Vue组件间的参数传递
- 父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件:$emit方法传递参数 - 非父子组件间的数据传递,兄弟组件传值
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。
路由实现
hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”vue and react and angular
与React的区别
相同点:
React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
不同点:
- React整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇结合immutable来实现数据不可变。而vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控。 - react的思路是all in js,通过js来生成html,所以设计了jsx。vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。
- react功能一般交给社区,vue是内置。
vue的路由钩子
首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。
beforeEach主要有3个参数to,from,next:
to:route即将进入的目标路由对象,
from:route当前导航正要离开的路由
next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。
比如我写的代码: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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74router.beforeEach((to, from, next) => {
const isLogin = window.sessionStorage.getItem('cweb-access') || window.localStorage.getItem('cweb-access-lo');
const params = {
searchData: {
regex: /achievement\/data-query/g,
canEnter: isLogin,
message: '请先登录。',
toUrl: '/login'
},
account: {
regex: /account/g,
canEnter: store.state.selfInfo && store.state.selfInfo.accountType == 'ADMIN',
message: '请先登录,或您没有查看系统账号列表的权限。',
toUrl: '/'
},
service: {
regex: /service/g,
canEnter: isLogin,
message: '请先登录。',
toUrl: '/login'
},
selfInfo: {
regex: /selfInfo/g,
canEnter: isLogin,
message: '请先登录。',
toUrl: '/login'
},
modifyPassword: {
regex: /login\/modifyPassword/g,
canEnter: isLogin,
message: '请先登录。',
toUrl: '/login'
},
};
//从编辑或者创建页面退出时应该进行提示
if( from.path && from.path.match(/\/(add)|(edit)/g) && (!to.path.match(/\/(view)/g))) {
ElementUI.MessageBox.confirm('返回不能保存已填数据, 是否离开此页面?', '提示', {
type: 'info'
}).then(() => {
/*
* 继续的话退出
* 判断一些模块是否可以访问
*/
for(let i in params) {
let { regex, canEnter, message, toUrl } = params[i];
if( to.path && to.path.match(regex) && !canEnter) {
ElementUI.Message(message);
next({
path: toUrl
});
return;
}
}
next();
}).catch(() => {
next(false);
});
} else {
/*
* 判断一些模块是否可以访问
*/
for(let i in params) {
let { regex, canEnter, message, toUrl } = params[i];
if( to.path && to.path.match(regex) && !canEnter) {
ElementUI.Message(message);
next({
path: toUrl
});
return;
}
}
next();
}
})创建自定义指令
局部指令:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var app = new Vue({
el: '#app',
data: {
},
// 创建指令(可以多个)
directives: {
// 指令名称
dir1: {
inserted(el) {
// 指令中第一个参数是当前使用指令的DOM
console.log(el);
console.log(arguments);
// 对DOM进行操作
el.style.width = '200px';
el.style.height = '200px';
el.style.background = '#000';
}
}
}
})
全局指令:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Vue.directive('dir2', {
// 当被绑定的元素插入到 DOM 中时……
inserted(el) {
console.log(el);
},
// bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind(el, binding, vnode, oldVnode) {
},
//所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
update() {
},
componentUpdated() {
},
unbind() {
}
})
自定义过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var vm=new Vue({
el:"#app",
data:{
msg:''
},
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})keep-alive
keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。1
2
3
4
5<keep-alive include='include_components' exclude='exclude_components'>
<component>
<!-- 该组件是否缓存取决于include和exclude属性 -->
</component>
</keep-alive>