最近的工作,需要写点类似自己公司内部bms系统的一些web页面。由于不是对外的,一些简单的页面就交给后端自己来完成。为了符合公司整个前端代码风格,研究了下 Vue.js 。
Vue.js 官方文档有中文的,整个上手难度也比较低,看完文档再稍加整合下饿了么的 Element 组件基本就能满足大部分像我这样的需要了。js还是啃大学那会用JQuery的老本,并不是很会。自己又习惯做点md笔记,于是干脆在官方文档的基础上加了一点自己的理解,整理出来share一下,再加上本身就是Javaer的角度,很基础,若对看的人有一点点帮助就算有意义了。
打个照面:
- 自定义标签 嵌入原生HTML标签中去:
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})
完了之后调用:
<div id="app-7">
<ol>
<!--
现在我们为每个 todo-item 提供 todo 对象
todo 对象是变量,即其内容可以是动态的。
我们也需要为每个组件提供一个“key”,稍后再
作详细解释。
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id">
</todo-item>
</ol>
</div>
- 每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:
var vm = new Vue({
// 选项
})
当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
这个自然也是可以控制的:
var obj = {
foo: 'bar'
}
Object.freeze(obj)
new Vue({
el: '#app',
data: obj
})
这里对foo的任何修改不会生效(后话了 这里应该是涉及到js的浅拷贝和深拷贝问题)
new 出来的Vue实例还会暴露出自己的一些属性和方法:通过这写方法和属性,可以操作对应这个new 出来的实例
使用 vm.$watch
形式来操作,具体参见API
-
Vue实例生命周期:
image
上面的生命周期图比较关键,是理解Vue.js很多地方语法结构的助推剂。整体还算比较清晰明了。
- 一般情况下,使用{{}}引用Vue实例中data域的内容,但是HTML元素标签中并不支持此( {{}} )语法。使用v-bind绑定数据域内内容。(id='xx'形式) 而且{{}}内部支持js语法:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
- 调用方法去执行计算和调用computed中的方法区别:后者依赖上一次计算后的缓存,只要不需要重新计算(Vue提供支持)就不会重新计算。
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
上面的代码能同时监听firstName, lastName两个data域值。
绑定class样式
这里的绑定比较灵活而且强大:
<div v-bind:class="{ active: isActive }"></div>
这样子灵活的将active类绑定到data里面isActive的true/false身上。
<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>
然后对应的data如下:
data: {
classObject: {
active: true,
'text-danger': false
}
}
可以注意到这里的 class = 后面是一个json格式的对象,那么直接就可以传一个对象进去了:
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
主要就是这里用json格式来表达一个js对象
在自定义组件时是一种extend的关系,如下:
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
<my-component class="baz boo"></my-component>
其等价于
<p class="foo bar baz boo">Hi</p>
上面是绑定class相关,大致就是js对象绑定和数组字面量绑定。
下面是内联样式相关:
首先仍然是对象语法:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
灵活并且直观。不熟悉弱类型脚本语言的情况下,确实需要时间适应这种松散的语法声明。
然后就是直接绑定对象:
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
条件渲染
- 使用template标签(此标签不可见)批量进行渲染:
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
上面的例子中,如果条件成立,页面元素将被渲染出来。但template本身不会显示。
也有类似于java中的语法:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
使用if else-if else语句,页面渲染时使用的是同一个不显示的template元素。一方面意味高效,另一方面可以实现元素的共用:
<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>
以上代码,实际上是一个template下面的元素。只会渲染其中之一。意味着这里面包裹的元素若是一致的(比如 input ),那么他们在条件变换时也是共用的,即用户在 input 中输入的内容在条件变化过程中不会改变(毕竟是渲染在同一个 input 下的)。
理解共用的道理比这个语法本身貌似更重要些。
当然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标记的绑定,就可以实现二次渲染。
- v-show 渲染
使用 v-show 的元素会被动态切换其css的 display 样式。和上面的动态渲染不同。这个dom元素会始终被渲染在dom树中,只是css样式的变换而已。
使用如下:
<h1 v-show="ok">Hello!</h1>
其也不支持 template 语法。
列表渲染
- v-for语句
类似于java中的 foreach 语法。比较简单:
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
比java好的地方是这里提供一个 index 可选,获取当前元素的下标(毕竟脚本语言,编译语言中要实现这个功能所牺牲的复杂度想必会更高)
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
- 如果 for 后面传入的不是数组对象而是其他object,则可以遍历其json格式下的所有属性:
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
输出会是:
- John
- Doe
- 30
另外,in 前面的参数若是 (value, key) 的形式,则遍历 k-v 键值对:
<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>
结果显然:
firstName:John
lastName:Doe
age:30
最后是 value, key, index 的复合使用:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
- 替换数组
这里使用到一个 变异 非变异 方法的概念:前者就是会改变原数组,后者而是在返回值体现改变的效果。
由于使用Vue进行渲染,就必须保证其能检测到我们的修改。但是第三方框架检测不到原生js数组的一些方法。例子中给出:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
可以使用vue提供的方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
即可。
或者这里使用Vue全局方法: Vue.set
vm.$set(vm.items, indexOfItem, newValue)
另外,使用下面的方法改变长度:
vm.items.splice(newLength)
总之,要响应式的改变dom渲染,就必须让Vue感知到这种变化,这里的注意事项给出了一种值得关注的启发:使用Vue就要关注它全方面的js替代性。
- 添加属性
使用Vue提供的方式:
Vue.set(vm.userProfile, 'age', 27)
vm.$set(vm.userProfile, 'age', 27)
一次赋值多个属性:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
- 使用类似java匿名内部类的比较函数(或者java8的filter的lambda表达函数)返回数组的过滤对象:
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
<li v-for="n in evenNumbers">{{ n }}</li>
类似的在方法中调用:
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
<li v-for="n in even(numbers)">{{ n }}</li>
- 更加直接地调用常量:
div>
<span v-for="n in 10">{{ n }} </span>
</div>
- 在<template>中调用 v-for :
上面说过在template中调用 v-if 起到动态加载dom。这里同样可以使用 v-for 渲染:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
- 代码中混用 v-if v-for :
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
以上的两个条件位于同一个待渲染元素中,官方文档给出的解释是前者运算优先级更高。但个人理解这里的 todo 是在for中声明的,后者当然是在for中去执行的。Vue的确分这两者的执行优先级,只是可能这个demo不能很好诠释这一点。当后遇到这样的困惑时,自然是前者优先级高。
官网还给出了如下demo作为上一个demo的补充去强调 v-if 和 v-for 的关系:
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
从一个javaer的角度去看,按照需要的逻辑实现即可,这里不必过多追究。
- 在组件(component)中使用 v-for
这里文档给了一个比较综合的例子:
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input
v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
<button>Add</button>
</form>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
有两点值得关注:
- 文档中提到:
任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要用 props。
所以在定义自定义组件时声明了 props 属性。(组件相关的介绍被排在Vue基础官方文档的最后优先级,再往后就是 深入了解组件 有一大章节,再往后就是一些高级特性如动画过渡,复用性相关,开发部署等。可见组件还是Vue中的重点和上限比较高的一块)
- <li> 标签中使用到的 is="todo-item" 属性。这里等同使用模板中定义的 <todo-item> 但是可以起到浏览器兼容的效果。
事件处理
- v-on 监听DOM事件
既可以在 v-on 绑定一些触发事件,绑定调用函数,也可以在触发事件后直接写入js代码(个人不建议这么做) :
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
通常是:
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
上面new出来的example2变量可以在js中直接引用使用其成员属性:
example2.greet() // => 'Hello Vue.js!'
文档处给处 内联语法 使用demo,其实就是在html中去使用js。调用声明的方法:
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
在 内联语法 中使用原生DOM事件,则要加上$引用:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
}
在事件响应中,默认的事件发生行为有时候不是我们想要的。比如一个按钮中嵌套另一个按钮,两个按钮同时绑定点击响应时间,但是需求是内部按钮点击事件结束,外部不做任何操作。其实也就是阻断事件的 冒泡 和 捕获 行为。原生js已经提供了这种支持。 Vue 提供 .xxx 的语法封装了这种行为:
<!-- 阻止单击事件继续传播 -->
<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>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
*绑定按键修饰符:
监听键盘行为:
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">
显然这种绑定关系要求我们清楚每个按键的编码,但是比较通用的键盘码已经被封装成代码:
<!-- 同上 -->
<input v-on:keyup.enter="submit">
<!-- 缩写语法 -->
<input @keyup.enter="submit">
enter按键的响应如此。常用的其他的可以在使用过程中查询,反正看了也不一定记得住。Vue可以在全局配置中config此按钮码为字面常量方便使用:
// 现在可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
表单输入绑定
- 使用 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>
这里比较容易。Vue 下面的数组绑定比较灵活:
<div id='example-3'>
<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>
</div>
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
使用 v-bind 渲染数据域的值:
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
一些没有用户输入的表单选项,可以使用 v-bind 绑定想要的值:
<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a
其实也就是对这个单选项进行一个值的绑定,但是为了让 Vue 知道,需要使用它的方式来。文档还给出这样的提示:
v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
自然解释了一切。
Vue还对这个表单数据和数据域内数据双向绑定附加了一些小功能,可能在某些细节优化和刁钻需求的地方会用到:
- .lazy
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
在内部数据发生改变才触发响应
- .trim
<input v-model.trim="msg">
切割掉用户输入的首尾空白符
组件
// TODO 组件是 Vue.js 比较进阶的一块,得空的话可能会深入研究下,简单的文档demo后续会整理......
: )
网友评论