本章将介绍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
7
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},

数组更新检测

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如: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
2
3
4
5
6
7
8
Vue.set(vm.userProfile, 'age', 27)
vm.$set(vm.userProfile, 'age', 27)

//赋予多个属性,比如使用 Object.assign() 或 _.extend()。创建一个新的对象,而不是加到原有的对象中
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

事件处理

1
2
3
4
5
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}

事件修饰符( 修饰符是由点开头的指令后缀来表示的。)

.stop
.prevent
.capture
.self
.once
.passive 滚动事件的默认行为 (即滚动行为) 将会立即触发,而不会等待 onScroll 完成,这其中包含 event.preventDefault() 的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 阻止单击事件继续传播 -->
<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>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

按键修饰符

.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
8
var 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
34
import 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})

原生事件

用 v-on 的 .native 修饰符,在一个组件的根元素上直接监听一个原生事件。
不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。
这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。
为了解决这个问题,Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。

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
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})

动态组件

当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。
为了解决这个问题,我们可以用一个 元素将其动态组件包裹起来。

1
2
3
4
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

常见问题

  1. 对于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 来统一管理。
  1. 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(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
  1. vue实现双向绑定的原理 Object.defineProperty()
    采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

  2. Vue组件间的参数传递

  • 父组件与子组件传值
    父组件传给子组件:子组件通过props方法接受数据;
    子组件传给父组件:$emit方法传递参数
  • 非父子组件间的数据传递,兄弟组件传值
    eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。
  1. 路由实现
    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 依赖的页面。”

  2. 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是内置。
  1. 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
    74
    router.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();
    }
    })
  2. 创建自定义指令
    局部指令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var 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
19
Vue.directive('dir2', {
// 当被绑定的元素插入到 DOM 中时……
inserted(el) {
console.log(el);
},
// bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind(el, binding, vnode, oldVnode) {
},
//所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
update() {

},
componentUpdated() {

},
unbind() {

}
})

  1. 自定义过滤器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var 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)
    })
  2. 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>