文章目录
概念
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。
索引
官网:https://vuejs.org
中文官网:https://cn.vuejs.org
API:https://cn.vuejs.org/v2/api/
安装vue
在线使用:
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
安装教程:https://cn.vuejs.org/v2/guide/installation.html
可以直接下载并使用<script>引用:
开发版本:https://cn.vuejs.org/js/vue.js
生产版本:https://cn.vuejs.org/js/vue.min.js
使用npm安装:npm install vue
使用CLI:https://cli.vuejs.org/
API
参见:https://cn.vuejs.org/v2/api/
可以直接浏览器内全文搜索查找。
基本语法
声明式渲染
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。
通过 “Mustache”语法 (双大括号) 进行文本插值,使数据和 DOM 建立关联,所有东西都是响应式的。修改 app.message 的值,则展示的信息会相应地更新。
v-bind 特性被称为指令。指令带有前缀 v-,以表示它们是 Vue 提供的特殊特性。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。一些指令能够接收一个“参数”,在指令名称之后以冒号表示。
Mustache 语法不能作用在 HTML 特性上,遇到这种情况应该使用 v-bind 指令。
下面例子里,v-bind指令的意思是:“将这个元素节点的 title 特性和 Vue 实例的 message2 属性保持一致”。
<div id="app">
<!-- 2个大括号来引用数据 -->
{{ message }}
<!-- 使用v-bind标签来绑定数据 -->
<span v-bind:title="message2">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
<script type="text/javascript">
var app = new Vue({
// 绑定的元素
el: '#app',
// 导出的数据
data: {
message: 'Hello Vue!'
message2: '页面加载于 ' + new Date().toLocaleString()
}
})
</script>
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<!--
注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。
-->
<a v-bind:[attributeName]="url"> ... </a>
表单输入绑定
v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定。
v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>
<script>
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})
</script>
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用 value 属性和 input 事件;
- checkbox 和 radio 使用 checked 属性和 change 事件;(单个复选框,绑定到布尔值;多个复选框,绑定到同一个数组)
- select 字段将 value 作为 prop 并将 change 作为事件。
自定义输入组件也可以使用v-model。
model可以使用.lazy,.number,.trim修饰符。
<!-- 在“change”时而非“input”时更新数据 -->
<input v-model.lazy="msg" >
<!-- 自动将用户的输入值转为数值类型 -->
<input v-model.number="age" type="number">
<!-- 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">
数据绑定中进行数据处理
使用JS表达式
对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持:
<html>
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
<!-- 有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。 -->
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
</html>
计算属性
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。对于任何复杂逻辑,你都应当使用计算属性。
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello',
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
computed: {
// 计算属性的 getter (计算属性的 getter 函数是没有副作用的)
// Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
},
// 侦听属性,等价于:computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
</script>
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。
然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
<p>Reversed message: "{{ reversedMessage() }}"</p>
<script>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
computed: {
// 下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖
now: function () {
return Date.now()
}
}
</script>
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter,现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
侦听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>
Class 与 Style 的数据绑定
在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
Class的数据绑定
- 我们可以传给 v-bind:class 一个对象,以动态地切换 class。
- 可以在这个对象中传入更多属性来动态切换多个 class。
- v-bind:class 指令也可以与普通的 class 属性共存。
- 绑定的数据对象不必内联定义在模板里。
- 也可以在v-bind:class 指令中绑定一个返回对象的计算属性。
- 我们可以把一个数组传给 v-bind:class,以应用一个 class 列表。
- 可以在数组中,结合使用对象语法。
- 当在一个自定义组件上使用 class 属性时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。
<!-- 对象语法 -->
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
<!-- 等价的表示形式 -->
<div v-bind:class="classObject"></div>
<!-- 数组语法 -->
<div v-bind:class="[activeClass, errorClass]"></div>
<!-- 数组内可以使用对象 -->
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
<script>
data: {
// 对象语法中使用,渲染结果为:<div class="static active"></div>
isActive: true,
hasError: false,
// 等价的表示形式
classObject: {
active: true,
'text-danger': false
},
// 数组语法中使用,渲染结果为: <div class="active text-danger"></div>
activeClass: 'active',
errorClass: 'text-danger'
}
</script>
Style的数据绑定
- v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。
- 绑定的数据对象不必内联定义在模板里。
- 对象语法常常结合返回对象的计算属性使用。
- v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上。
- 当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS 属性时,如 transform,Vue.js 会自动侦测并添加相应的前缀。
- 从 2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值。
<!-- 对象语法 -->
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<!-- 等价表达 -->
<div v-bind:style="styleObject"></div>
<!-- 数组语法,其中,baseStyles和overridingStyles都是对象 -->
<div v-bind:style="[baseStyles, overridingStyles]"></div>
<!-- 这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex -->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
<script>
data: {
// 对象语法使用
activeColor: 'red',
fontSize: 30,
// 等价表达
styleObject: {
color: 'red',
fontSize: '30px'
}
}
</script>
条件与循环
条件渲染
v-if指令
条件使用v-if指令:
(注:永远不要把 v-if 和 v-for 同时用在同一个元素上。)
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
</div>
<script>
var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})
</script>
因为 v-if 是一个指令,所以必须将它添加到一个元素上。如果想切换多个元素,可以把一个 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
可以使用 v-else 指令来表示 v-if 的“else 块”。
2.1.0 新增 v-else-if 。
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:
<!-- 每次切换时,输入框都将被重新渲染。 -->
<!-- 但是,<label> 元素仍然会被高效地复用(只是变换文字内容),因为它们没有添加 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>
v-show指令
v-show:带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
v-show 不支持 元素。
<h1 v-show="ok">Hello!</h1>
对比 vi-if:
- v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
- v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
- v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
列表(数组)渲染
循环使用v-for指令。
我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。
<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 还支持一个可选的第二个参数,即当前项的索引。
<li v-for="(item, index) in items">...</li>
也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:
<div v-for="item of items"></div>
也可以用 v-for 来遍历一个对象的属性。
在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
<ul id="v-for-object" class="demo">
<!-- 也可以提供第二个的参数为 property 名称 (也就是键名):v-for="(value, name) in object" -->
<!-- 还可以用第三个参数作为索引:v-for="(value, name, index) in object -->
<li v-for="value in object">
{{ value }}
</li>
</ul>
<script>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
</script>
建议尽可能在使用 v-for 时提供 key attribute,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素。
如果不指定key,默认使用“就地更新”的策略。如果数组中数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素。
我们想要显示一个数组经过过滤或排序后的版本,而不实际改变或重置原始数据。在这种情况下,可以在 v-for 中使用计算属性或者方法。例如:n in computedVar/method()
v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。例如:v-for="n in 10"
可以利用带有 v-for 的 来循环渲染一段包含多个元素的内容。
在自定义组件上,你可以像在任何普通元素上一样使用 v-for 。2.2.0+ 的版本里,当在组件上使用 v-for 时,key 现在是必须的。
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 prop:
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
数据更新检测
Vue 将被侦听的数组的变异方法(mutation method,变异方法,顾名思义,会改变调用了这些方法的原始数组,例如:push(), pop(), shift(), splice(), unshift(), sort(), reverse() )进行了包裹,所以它们也将会触发视图更新。
非变异 (non-mutating method) 方法,例如 filter()、concat() 和 slice() 。它们不会改变原始数组,而总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
注意:当你利用索引直接设置一个数组项时,或者当你修改数组的长度时,Vue 并不能检测到数组的变动:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的,使用 Vue.set(vm.items, indexOfItem, newValue) 或者 vm.items.splice(indexOfItem, 1, newValue) 代替,也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:vm.$set(vm.items, indexOfItem, newValue)
vm.items.length = 2 // 不是响应性的,使用 vm.items.splice(newLength) 代替
Vue 不能检测对象属性的添加或删除:
var vm = new Vue({
data: {
a: 1,
userProfile: {
name: 'Anika'
}
}
})
// `vm.a` 现在是响应式的
vm.b = 2
// `vm.b` 不是响应式的
// 对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性,例如上例中的b。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。
// 还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:vm.$set(vm.userProfile, 'age', 27)
Vue.set(vm.userProfile, 'age', 27)
// 为已有对象赋值多个新属性,应该用两个对象的属性创建一个新的对象
vm.userProfile = Object.assign({}, vm.userProfile, {
sex: 'female',
favoriteColor: 'Vue Green'
})
事件监听器
用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
<!-- 有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法: -->
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</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('')
}
},
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.message + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
},
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
})
</script>
所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护上的困难。使用 v-on 有几个好处:
- 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
- 因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
- 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
修饰符
修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
<!-- .prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault() -->
<form v-on:submit.prevent="onSubmit">...</form>
事件修饰符
Vue.js 为 v-on 提供了事件修饰符,专门去处理 DOM 事件细节。
支持:.stop, .prevent, .capture, .self, .once(2.1.4 新增), .passive(2.3.0 新增)。
.once 修饰符还能被用到自定义的组件事件上,其它只能对原生的 DOM 事件起作用。
<!-- 阻止单击事件继续传播 -->
<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>
按键修饰符
Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。其中按键名称参见:https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
按键码
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:.enter,.tab,.delete (捕获“删除”和“退格”键),.esc,.space,.up,.down,.left,.right。推荐优先使用别名。
你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名:
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器:.ctrl,.alt,.shift,.meta(在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。)(前面均为2.1.0 新增),.exact(2.5.0 新增),.left,.right,.middle(2.2.0 新增)
<!-- Alt + C -->
<input @keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- .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 -->
指令的缩写
Vue 为 v-bind 和 v-on 这两个最常用的指令,提供了特定简写:
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
组件
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。每用一次组件,就会有一个它的新实例被创建。
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。
Vue的组件和原生自定义组件并不相同,具体参见:https://cn.vuejs.org/v2/guide/index.html#与自定义元素的关系
注册组件
在 Vue 中注册组件很简单:
<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>
<script>
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义特性。
// 这个 prop 名为 todo。
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: '随便其它什么人吃的东西' }
]
}
})
</script>
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
// 全局注册。
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
// 其后的vue实例,其对应的组件树<div id="app">中所有子组件都可以使用
new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
可以使用 JavaScript 的模板字符串来让多行的模板更易读。
模板字符串使用反引号 () 来代替普通字符串中的用双引号和单引号。在模版字符串内使用反引号(`)时,需要在它前面加转义符(\)。
模板字符串在 IE 下并没有被支持,所以如果你需要在不 (经过 Babel 或 TypeScript 之类的工具) 编译的情况下支持 IE,请使用折行转义字符取而代之。
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
组件名和Prop名
组件名,推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
推荐使用 kebab-case (短横线分隔命名) 定义一个组件,例如 <my-component-name>。
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
Prop名也是一样。HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。
局部注册
全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
局部注册方式:
// 定义组件
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
el: '#app',
components: {
// 对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象
'component-a': ComponentA,
'component-b': ComponentB
}
})
注意,局部注册的组件在其子组件中不可用。
// 如果希望 ComponentA 在 ComponentB 中可用:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
在模块系统中局部注册
我们推荐创建一个 components 目录,并将每个组件放置在其各自的文件中。然后,在局部注册之前导入每个你想使用的组件。
// ComponentB.vue 文件
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
// 让 ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用:
ComponentA,
ComponentC
},
// ...
}
自动化全局注册
示例代码:
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
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件:全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
Prop
可以以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
使用Prop时也可以传入任何类型:
<!-- 传入一个数组:即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
<!-- 如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name) -->
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
<!-- 以上等价于 -->
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。
这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。注意,在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
如果确实需要在子组件使用父组件的Prop属性,有2个建议的方法:
// 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
// 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
可以为组件的 prop 指定验证要求,为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
组件可以接受任意的特性,而这些特性会被添加到这个组件的根元素上。
对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。
class 和 style 特性会稍微智能一些,即两边的值会被合并起来:
<!-- <bootstrap-date-input>的模板如下: -->
<input type="date" class="form-control">
<!-- class 最终的值:form-control date-picker-theme-dark -->
<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>
如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false。
注意, inheritAttrs: false 选项不会影响 style 和 class 的绑定。
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
// 实例的 $attrs 属性包含了传递给一个组件的特性名和特性值,可以让父组件的任意特性来作用到子组件
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
组件的数据
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
v-model
自定义事件也可以用于创建支持 v-model 的自定义输入组件。需要组件内的<input>满足以下条件:
- 将其 value 特性绑定到一个名叫 value 的 prop 上
- 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
<!-- v-model本质 -->
<input v-model="searchText">
等价于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
<!-- 组件上的v-model的本质 -->
<custom-input v-model="searchText"></custom-input>
等价于:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
所以,组件要支持:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
// 必须声明 checked 这个 prop
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
.async
使用 .sync 修饰符对一个 prop 进行“双向绑定”。类似 v-model,带有 .sync 修饰符的 v-bind 不能和表达式一起使用,只能提供你想要绑定的属性名。
<script>
// 触发更新事件
this.$emit('update:title', newTitle)
</script>
<text-document v-bind:title.sync="doc.title"></text-document>
<!-- 等价于 -->
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
插槽
通过插槽方式使用数据,插槽内可以包含任何模板代码,包括 HTML。
<!-- 如果 <alert-box> 没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。 -->
<alert-box>
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Something bad happened.
</alert-box>
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<!-- 插槽,接收组件标签内的内容 -->
<slot></slot>
</div>
`
})
<!-- 为插槽指定默认值,当使用组件并且不提供任何插槽内容时会渲染默认值 -->
<button type="submit">
<slot>Submit</slot>
</button>
<!-- 动态指令参数也可以用在 v-slot 上,来定义动态的插槽名: -->
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
插槽跟模板的其它地方一样可以访问相同的实例属性 (也就是相同的“作用域”),而不能访问所属组件的作用域。
为了让 属性 在父级的插槽内容中可用,我们可以将 该属性 作为 <slot> 元素的一个特性绑定上去。绑定在 <slot> 元素上的特性被称为插槽 prop。
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为 "/profile" 是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>
<!-- 让 user 在父级的插槽内容中可用的例子,组件定义: -->
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
<!-- 在父级作用域中,我们可以给 v-slot 带一个值来定义我们提供的插槽 prop 的名字:-->
<current-user>
<!-- 当被提供的内容有且只有默认插槽时,下述可以缩写为 v-slot="slotProps" -->
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
<!-- 等价方式:使用 ES2015 解构来传入具体的插槽 prop -->
<!-- 实际上, ES2015 解构还提供了prop 重命名和默认值功能,例如:
{ user: person }
{ user = { firstName: 'Guest' } }
-->
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
通过具名插槽的方式来使用多个插槽。
v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header。
同理,v-slot:default=“slotProps” 可以缩写为 #default=“slotProps”。
<!-- 组件的模板内容 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<!-- 不带 name 会带有隐含的名字“default”。 -->
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!-- 使用组件时:我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称 -->
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<!-- 任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容 -->
<p>A paragraph for the main content.</p>
<template v-slot:default>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
组件的事件
子组件可以通过调用内建的 $emit 方法 并传入事件名称来触发一个事件;
注:事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。并且, v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。因此,推荐你始终使用 kebab-case 的事件名。
父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件。
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<!-- 抛出一个名为enlarge-text的事件 -->
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
可以使用 $emit 的第二个参数来使用事件抛出一个值;
当在父级组件监听这个事件的时候,可以通过 $event 访问到被抛出的这个值。
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
<!-- 如果这个事件处理函数是一个方法,这个值将会作为第一个参数传入这个方法 -->
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
<script>
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
</script>
想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符:
<base-input v-on:focus.native="onFocus"></base-input>
Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有原生事件的监听器。
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>
`
})
is特性和动态组件
使用is特性可以在不同组件之间进行动态切换:
<!-- 组件会在 `currentTabComponent` 改变时改变,currentTabComponent是已注册组件的名字,或一个组件的选项对象 -->
<component v-bind:is="currentTabComponent"></component>
<!-- 在组件间切换时,Vue 默认是每次都创建了一个新的 currentTabComponent 组件实例,如果希望组件实例能够被在它们第一次被创建的时候缓存下来需要使用keep-alive标签 -->
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
有些元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。
例外,从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: ‘…’)
- 单文件组件 (.vue)
- <script type=“text/x-template”>
<!-- 错误代码 -->
<table>
<blog-post-row></blog-post-row>
</table>
<!-- 正确代码 -->
<table>
<tr is="blog-post-row"></tr>
</table>
异步组件
Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。
一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用。
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
// 你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:
Vue.component(
'async-webpack-example',
// 这个 `import` 函数会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
异步组件工厂函数也可以返回一个如下格式的对象,以支持处理加载状态等:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
组件构建应用
在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。这里有一个 布局Layout 例子,以展示使用了组件的应用模板是什么样的:
<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>
vue实例
- 每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:
new Vue({ // 选项 })
。 - 一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。
- 当一个 Vue 实例被创建时,它将 data 对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
- 值得注意的是只有当实例被创建时就已经存在于 data 中的属性才是响应式的;后来添加一个新的属性,这个属性的改动将不会触发任何视图的更新。
- 使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。
- 除了数据属性,Vue 实例还暴露了一些有用的实例属性与方法。它们都有前缀 $,以便与用户定义的属性区分开来。例如 vue.$data来引用实例vue的data对象。
实例生命周期的钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
例如:
new Vue({
data: {
a: 1
},
// created就是一个钩子
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
更多的钩子函数和被调用时间参见 生命周期图示:
模板语法
Vue.js 使用了基于 HTML 的模板语法,所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。
高级语法
组件相关的边界情况
参见:https://cn.vuejs.org/v2/guide/components-edge-cases.html
- 【不推荐】在每个 new Vue 实例的子组件中,其根实例可以通过 $root 属性进行访问。
- 【不推荐】$parent 属性可以用来从一个子组件访问父组件的实例。
- 【不推荐】可以通过 ref 特性为一个子组件赋予一个 ID 引用,在 JavaScript 里直接访问这个子组件。
- 【不推荐】依赖注入:provide 选项允许我们指定我们想要提供给后代组件的数据/方法。然后,在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的属性。
- 程序化的事件侦听器:通过 $on(eventName, eventHandler) 侦听一个事件;通过 $once(eventName, eventHandler) 一次性侦听一个事件;通过 $off(eventName, eventHandler) 停止侦听一个事件。
- 递归组件:组件是可以在它们自己的模板中调用自身的。它们通过 name 属性选项来做这件事。
- 组件之间的循环引用,在本地注册组件的时候,你可以使用 webpack 的异步 import 来避免解析依赖报错。
- 【不推荐】内联模板:当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。
- 【不推荐】另一个定义模板的方式是在一个 <script> 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。
- 【不推荐】通过 $forceUpdate 来在 Vue 中做一次强制更新。
- 【不推荐】静态组件:在根元素上添加 v-once 特性以确保这些内容只计算一次然后缓存起来。
过渡 & 动画
进入、离开&列表过渡 参见:https://cn.vuejs.org/v2/guide/transitions.html
状态过渡 参见:https://cn.vuejs.org/v2/guide/transitioning-state.html
- Vue 提供了 transition 的封装组件,可以给任何元素和组件添加进入/离开过渡(例如使用 v-if 导致变化)。
- 在进入/离开的过渡中,会有 6 个 class 切换。 v- 是这些类名的默认前缀,会被替换为 transition 的name属性:
- v-enter:定义进入过渡的开始状态。
- v-enter-active:定义进入过渡生效时的状态。
- v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。
- v-leave: 定义离开过渡的开始状态。
- v-leave-active:定义离开过渡生效时的状态。
- v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。
- CSS过渡 使用 transition 关键字定义,CSS动画 使用 animation 关键字定义。
- 可以通过以下特性来自定义过渡类名:enter-class,enter-active-class,enter-to-class (2.1.8+),leave-class,leave-active-class,leave-to-class (2.1.8+)。
- 同时使用过渡和动画时,使用 type 特性并设置 animation 或 transition 来明确声明你需要 Vue 监听的类型。
- 可以用 <transition> 组件上的 duration 属性定制一个显性的过渡持续时间。
- 可以在属性中声明 JavaScript 钩子,例如:<transition
v-on:before-enter=“beforeEnter” > 。 - 可以通过 appear 特性设置节点在初始渲染的过渡。
- Vue 提供了 过渡模式:1)in-out:新元素先进行过渡,完成之后当前元素过渡离开。2)out-in:当前元素先进行过渡,完成之后新元素过渡进入。
- 多个元素的过渡,设置 key 是一个更好的实践。
- 多个组件的过渡,只需要使用动态组件。
- 列表过渡,使用 <transition-group> 组件。
- 创建一个可复用过渡组件,将 <transition> 或者 <transition-group> 作为根组件,然后将任何子组件放置在其中就可以了。
- 动态过渡最基本的例子是通过 name 特性来绑定动态值。实际上,所有过渡特性都可以动态绑定。
混入
参见:https://cn.vuejs.org/v2/guide/mixins.html
- 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
- 一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
- 当组件和混入对象含有同名选项时,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
- 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
- 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
- 自定义选项将使用默认策略,即简单地覆盖已有值。如果想让自定义选项以自定义逻辑合并,可以向 Vue.config.optionMergeStrategies 添加一个函数
- 混入也可以进行全局注册。它将影响每一个之后创建的 Vue 实例。
自定义指令
参见:https://cn.vuejs.org/v2/guide/custom-directive.html
- 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许通过Vue.directive()注册自定义指令。
- 一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- bind:只调用一次,指令第一次绑定到元素时调用。
- inserted:被绑定元素插入父节点时调用。
- update:所在组件的 VNode 更新时调用。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
渲染函数 & JSX
参见:https://cn.vuejs.org/v2/guide/render-function.html
- Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数render,它比模板更接近编译器。
- 浏览器会为HTML建立一个对应的“DOM 节点”树。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode(virtual node) 树的称呼。
- Babel 插件,用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。
- 将组件标记为 functional,意味着它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。
- Vue 的模板实际上被编译成了渲染函数。这是一个实现细节,通常不需要关心。
插件
参见:https://cn.vuejs.org/v2/guide/plugins.html
- 插件通常用来为 Vue 添加全局功能。例如,全局方法或者属性,全局资源等。
- 或者使用插件引入一个库,提供自己的 API,同时提供多种功能。
- 通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成。
- Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。
- awesome-vue 集合了大量由社区贡献的插件和库。
过滤器
参见:https://cn.vuejs.org/v2/guide/filters.html
- Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。
- 过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。
- 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示。
- 过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。过滤器是 JavaScript 函数,还可以接收更多的传入参数。
- 可以在一个组件的选项中使用 filters: {} 定义本地的过滤器。
- 也可以在创建 Vue 实例之前使用 Vue.filter() 全局定义过滤器。
- 当全局过滤器和局部过滤器重名时,会采用局部过滤器。
- 多个过滤器可以使用管道符串联。
工具
单文件组件
参见:https://cn.vuejs.org/v2/guide/single-file-components.html
- 文件扩展名为 .vue 的 single-file components(单文件组件) ,可以使用 webpack 或 Browserify 等构建工具。
单元测试
参见:https://cn.vuejs.org/v2/guide/unit-testing.html
- Vue CLI 拥有开箱即用的通过 Jest 或 Mocha 进行单元测试的内置选项。
- 可以在不同的 props 中,通过 propsData 选项断言它的渲染输出。
- 由于 Vue 进行 异步更新 DOM 的情况,一些依赖 DOM 更新结果的断言必须在 Vue.nextTick 回调中进行。
更多参见Cookbook:https://cn.vuejs.org/v2/cookbook/unit-testing-vue-components.html
TypeScript 支持
参见:https://cn.vuejs.org/v2/guide/typescript.html
- Vue CLI 提供了内建的 TypeScript 工具支持,可以使用 TypeScript 生成新工程。
- 静态类型系统能帮助你有效防止许多潜在的运行时错误,而且随着你的应用日渐丰满会更加显著。这就是为什么 Vue 不仅仅为 Vue core 提供了针对 TypeScript 的官方类型声明,还为 Vue Router 和 Vuex 也提供了相应的声明文件。
- vue已经把类型声明发布到了 NPM,最新版本的 TypeScript 也知道该如何自己从 NPM 包里解析类型声明。
- 你需要引入 strict: true (或者至少 noImplicitThis: true,这是 strict 模式的一部分) 以利用组件方法中 this 的类型检查,否则它会始终被看作 any 类型。
- 要使用 TypeScript 开发 Vue 应用程序,强烈建议使用 Visual Studio Code,它为 TypeScript 提供了极好的“开箱即用”支持。WebStorm 同样为 TypeScript 和 Vue 提供了“开箱即用”的支持。
- 要让 TypeScript 正确推断 Vue 组件选项中的类型,您需要使用 Vue.component 或 Vue.extend 定义组件。
- 如果在声明组件时更喜欢基于类的 API,则可以使用官方维护的 vue-class-component 装饰器。
- 插件可以增加 Vue 的全局/实例属性和组件选项。在这些情况下,在 TypeScript 中制作插件需要以 模块补充 方式来类型声明。
- 因为 Vue 的声明文件天生就具有循环性,TypeScript 可能在推断某个方法的类型的时候存在困难。因此,你可能需要在 render 或 computed 里的方法上标注返回值。
- 使用 --noImplicitAny 选项将会帮助你找到这些未标注的方法。
生产环境部署
参见:https://cn.vuejs.org/v2/guide/deployment.html
- 开发环境下,Vue 会提供很多警告来帮你对付常见的错误与陷阱。而在生产环境下,这些警告语句会增加应用的体积和还有一些小的运行时开销。
- 直接用 <script> 元素引入 Vue 而不提前进行构建,请记得在生产环境下使用压缩后的版本 (vue.min.js)。
- 当使用 webpack 或 Browserify 类似的构建工具时,Vue 源码会根据 process.env.NODE_ENV 决定是否启用生产环境模式,默认情况为开发环境模式。
- 当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。预编译模板最简单的方式就是使用单文件组件。
- 当使用单文件组件时,组件内的 CSS 会以 <style> 标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,如果你使用服务端渲染,这会导致一段“无样式内容闪烁 (fouc)”。将所有组件的 CSS 提取到同一个文件,如果使用webpack则可以参考 webpack + vue-loader 。
- 如果在组件渲染时出现运行错误,错误将会被传递至全局 Vue.config.errorHandler 配置函数 (如果已设置)。利用这个钩子函数来配合错误跟踪服务是个不错的主意。比如 Sentry,它为 Vue 提供了官方集成。
深入响应式原理
参见:https://cn.vuejs.org/v2/guide/reactivity.html
- 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
- Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
- 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。
- 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
- 受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
- Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
- 如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
- 为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
热点问题
风格指南
参见:https://cn.vuejs.org/v2/style-guide/
优先级 A 的规则:必要的 (规避错误)
- 组件名为多个单词
- 组件的 data 必须是一个函数。
- Prop 定义应该尽量详细。
- 为 v-for 设置键值 Key。
- 避免 v-if 和 v-for 用在一起。
- 为组件样式设置作用域。
- 使用模块作用域保持不允许外部访问的函数的私有性。
优先级 B 的规则:强烈推荐 (增强可读性)
- 只要有能够拼接文件的构建系统,就把每个组件单独分成文件。
- 单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。
- 应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。
- 只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。
- 和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
- 组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。
- 在单文件组件、字符串模板和 JSX 中没有内容的组件应该是自闭合的——但在 DOM 模板里永远不要这样做。
- 对于绝大多数项目来说,在单文件组件和字符串模板中组件名应该总是 PascalCase 的——但是在 DOM 模板中总是 kebab-case 的。
- JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用 Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串。
- 组件名应该倾向于完整单词而不是缩写。
- 在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。
- 多个特性的元素应该分多行撰写,每个特性一行。
- 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
- 应该把复杂计算属性分割为尽可能多的更简单的属性。
- 非空 HTML 特性值应该始终带引号 (单引号或双引号,选你 JS 里不用的那个)。
- 指令缩写 (用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:) 应该要么都用要么都不用。
优先级 C 的规则:推荐 (将选择和认知成本最小化)
- 组件/实例的选项应该有统一的顺序。
- 元素 (包括组件) 的特性应该有统一的顺序。
- 你可能想在多个属性之间增加一个空行,特别是在这些选项一屏放不下,需要滚动才能都看到的时候。
- 单文件组件应该总是让 <script>、<template> 和 <style> 标签的顺序保持一致。且 <style> 要放在最后,因为另外两个标签至少要有一个。
优先级 D 的规则:谨慎使用 (有潜在危险的模式)
- 没有在 v-if/v-else-if/v-else 中使用 key
- 元素选择器应该避免在 scoped 中出现。
- 应该优先通过 prop 和事件进行父子组件之间的通信,而不是 this.$parent 或改变 prop。
- 应该优先通过 Vuex 管理全局状态,而不是通过 this.$root 或一个全局事件总线。
在 VS Code 中调试
参见:https://cn.vuejs.org/v2/cookbook/debugging-in-vscode.html
你必须安装好 Chrome 和 VS Code。同时请确保自己在 VS Code 中安装了 Debugger for Chrome 扩展的最新版本。
你需要更新 webpack 配置以构建 source map。做了这件事之后,我们的调试器就有机会将一个被压缩的文件中的代码对应回其源文件相应的位置。
使用Debug方式启动应用之后,即可设置断点,中断应用。
客户端存储
参见:https://cn.vuejs.org/v2/cookbook/client-side-storage.html
- 客户端存储是快速为一个应用进行性能优化的绝佳方法。
- 客户端存储可以通过这些技术来实现:cookie、Local Storage (更准确地说是“Web Storage”)、IndexedDB 和 WebSQL (这项技术已经被废弃了,你不应该在新项目中使用它)。
- Local Storage 使用键/值对来存储数据,示例代码:
<div id="app">
<p>
My name is <input v-model="name">
and I am <input v-model="age"> years old.
</p>
<p>
<button @click="persist">Save</button>
</p>
</div>
const app = new Vue({
el: '#app',
data: {
name: '',
age: 0
},
mounted() {
// 加载持久化了的数据
if (localStorage.name) {
this.name = localStorage.name;
}
if (localStorage.age) {
this.age = localStorage.age;
}
},
methods: {
persist() {
// 存储数据
localStorage.name = this.name;
localStorage.age = this.age;
console.log('now pretend I did more stuff...');
}
}
})
- 为了在 Local Storage 存储对象和数组这样更复杂的数据,你必须使用 JSON 来对数据进行序列化和反序列化。示例:
<div id="app">
<h2>Cats</h2>
<div v-for="(cat, n) in cats">
<p>
<span class="cat">{{ cat }}</span>
<button @click="removeCat(n)">Remove</button>
</p>
</div>
<p>
<input v-model="newCat">
<button @click="addCat">Add Cat</button>
</p>
</div>
const app = new Vue({
el: '#app',
data: {
cats: [],
newCat: null
},
mounted() {
// 使用localStorage的API来访问,推荐该方式
if (localStorage.getItem('cats')) {
try {
// 取出后需要反序列化
this.cats = JSON.parse(localStorage.getItem('cats'));
} catch(e) {
localStorage.removeItem('cats');
}
}
},
methods: {
addCat() {
if (!this.newCat) {
return;
}
this.cats.push(this.newCat);
this.newCat = '';
this.saveCats();
},
removeCat(x) {
this.cats.splice(x, 1);
this.saveCats();
},
saveCats() {
// 先序列化再存储
const parsed = JSON.stringify(this.cats);
// 使用localStorage的API来访问,推荐该方式
localStorage.setItem('cats', parsed);
}
}
})
- 下面几个插件包装了对 Local Storage 的访问并让它变得简单易用,同时加入了诸如默认值等功能:
Devtools
参见:https://github.com/vuejs/vue-devtools
Vue CLI
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。
Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。
Vue CLI 提供:
- 通过 @vue/cli 搭建交互式的项目脚手架。
- 通过 @vue/cli + @vue/cli-service-global 快速开始零配置原型开发。
- 一个运行时依赖 (@vue/cli-service)。
- 一个丰富的官方插件集合,集成了前端生态中最好的工具。
- 一套完全图形化的创建和管理 Vue.js 项目的用户界面。
Vue CLI 同时管理了多个单独发布的包:
- CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令。
- 通过 vue create 快速创建一个新项目的脚手架
- 通过 vue serve 构建新想法的原型
- 通过 vue ui 通过一套图形化界面管理你的所有项目
- CLI 服务 (@vue/cli-service) 是一个开发环境依赖,构建于 webpack 和 webpack-dev-server 之上。它是一个 npm 包,局部安装在每个 @vue/cli 创建的项目中。
- 加载其它 CLI 插件的核心服务
- 一个针对绝大部分应用优化过的内部的 webpack 配置
- 项目内部的 vue-cli-service 命令,提供 serve、build 和 inspect 命令
- CLI 插件是向你的 Vue 项目提供可选功能的 npm 包,例如 Babel/TypeScript 转译、ESLint 集成、单元测试和 end-to-end 测试等。
- @vue/cli-plugin- (内建插件)
- vue-cli-plugin- (社区插件)
使用如下命令安装:
npm install -g @vue/cli
Vue Loader
参见:https://vue-loader.vuejs.org/zh/
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。
Vue Router
参见:https://router.vuejs.org/zh/
基本介绍
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。
一个路由的基本例子:
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
命名路由:
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
路由匹配
使用路径参数,例如: { path: '/user/:id', component: User }
。当匹配到一个路由时,参数值会被设置到 this.$route.params
。
如果想匹配任意路径,我们可以使用通配符 (*), this.$route.params.pathMatch
包含了 URL 通过通配符被匹配的部分。
同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
编程式导航
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由,/user/123
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
重定向和别名
“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
{ path: '/c', redirect: { name: 'foo' }}
]
})
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a:
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
HTML5 History 模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id
。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
不过这种模式还需要后台配置支持。
因为我们的应用是个单页客户端应用(只有 index.html 页面),如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404。
所以,要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配到静态资源则返回静态资源,否则返回 index.html 页面(index.html通过前端路由指向真正的控件)。
# nginx配置
location / {
try_files $uri $uri/ /index.html;
}
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏。把组件的共享状态抽取出来,以一个全局单例模式管理,则我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
创建store:
// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:
store.commit('increment')
console.log(store.state.count) // -> 1
Vue SSR
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
与使用 web 服务器实时动态编译 HTML不同,使用预渲染方式,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。如果你使用 webpack,你可以使用 prerender-spa-plugin 轻松地添加预渲染。
axios
介绍
官网:GitHub项目首页 和 axios中文网
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
使用如下命令进行安装: npm install axios
,或者使用 cdn: <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
。
发送请求
通过向 axios 传递相关配置来创建请求:
// 获取远端图片
axios({
method:'get',
url:'http://bit.ly/2mTM3nY',
responseType:'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
使用别名方法进行发送请求,无需在配置中指定 method 等属性:
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
// 执行 POST 请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
请求配置
创建请求时可以用的配置选项:
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `headers` 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: {
ID: 12345
},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: {
firstName: 'Fred'
},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 1000,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,以使测试更轻松
// 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
adapter: function (config) {
/* ... */
},
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
// 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0,将不会 follow 任何重定向
maxRedirects: 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
socketPath: null, // default
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
// `keepAlive` 默认没有启用
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 'proxy' 定义代理服务器的主机名称和端口
// `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
// 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken` 指定用于取消请求的 cancel token
// (查看后面的 Cancellation 这节了解更多)
cancelToken: new CancelToken(function (cancel) {
})
}
响应结构
某个请求的 then 响应包含以下信息:
{
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 服务器响应的头
headers: {},
// `config` 是为请求提供的配置信息
config: {},
// 'request'
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance the browser
request: {}
}
// 实例:
axios.get('/user/12345')
.then(function(response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
错误处理
某个请求的 catch 响应包含以下信息:
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
使用 application/x-www-form-urlencoded format
默认情况下,axios将JavaScript对象序列化为JSON。要以application / x-www-form-urlencoded格式发送数据可以:
// 浏览器中
const qs = require('qs');
axios.post('/foo', qs.stringify({ 'bar': 123 }));
// Node.js中
const querystring = require('querystring');
axios.post('http://something.com/', querystring.stringify({ foo: 'bar' }));
其他API和功能特性
- 处理并发请求的助手函数: axios.all(iterable) 和 axios.spread(callback) ;
- 创建一个 axios 实例: axios.create([config]) ,然后可以使用该实例的.post等方法;
- 在请求或响应被 then 或 catch 处理前使用拦截器拦截它们: axios.interceptors.request.use() 和 axios.interceptors.response.use() ;
- 使用 cancel token 取消请求;
Element
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。
可以通过 unpkg.com/element-ui 获取到最新版本的资源,在页面上引入 js 和 css 文件即可开始使用:
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用: npm i element-ui -S
。
完整引入 Element :
import Vue from 'vue';
import ElementUI from 'element-ui';
// 样式文件需要单独引入
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
借助 babel-plugin-component ,我们可以只引入需要的组件,以达到减小项目体积的目的。
在引入 Element 时,可以传入一个全局配置对象。
import Vue from 'vue';
import Element from 'element-ui';
// size 用于改变组件的默认尺寸,zIndex 设置弹框的初始 z-index(默认值:2000)
Vue.use(Element, { size: 'small', zIndex: 3000 });
主题定制的方法有:使用在线主题编辑器,在项目中改变 SCSS 变量,使用主题生成工具来安装自定义主题。
具体参见:https://element.eleme.cn/#/zh-CN/component/custom-theme。
Element 内应用在部分组件的过渡动画,也可以直接使用。
具体参见:https://element.eleme.cn/#/zh-CN/component/transition
Element UI 的组件在以下网页查找,注意使用“搜索文档”功能时不能以标签名的代码作为目标去搜索。
参见:https://element.eleme.cn/#/zh-CN/component/layout
vue-element-admin
vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈(vue/vuex/vue-router/element等),内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。
可以把 vue-element-admin 当做工具箱或者集成方案仓库,在 vue-admin-template 的基础上进行二次开发,想要什么功能或者组件就去 vue-element-admin 那里复制过来。
element-ui 官方对自己的定位是桌面端后台框架,而且对于管理后台这种重交互的项目来说是不能通过简单的适配来满足桌面端和移动端两端不同的交互,所以 vue-element-admin 项目也不会适配移动端。
介绍
模块组成
- 集成方案: vue-element-admin
- 基础模板: vue-admin-template
- 桌面终端: electron-vue-admin
- Typescript 版: vue-typescript-admin-template (鸣谢: @Armour)
- Others: awesome-project
功能介绍
- 登录 / 注销
- 权限验证
- 页面权限
- 指令权限
- 权限配置
- 二步登录
- 多环境发布
- dev sit stage prod
- 全局功能
- 国际化多语言
- 多种动态换肤
- 动态侧边栏(支持多级路由嵌套)
- 动态面包屑
- 快捷导航(标签页)
- Svg Sprite 图标
- 本地/后端 mock 数据
- Screenfull全屏
- 自适应收缩侧边栏
- 编辑器
- 富文本
- Markdown
- JSON 等多格式
- Excel
- 导出excel
- 导入excel
- 前端可视化excel
- 导出zip
- 表格
- 动态表格
- 拖拽表格
- 内联编辑
- 错误页面
- 401
- 404
- 組件
- 头像上传
- 返回顶部
- 拖拽Dialog
- 拖拽Select
- 拖拽看板
- 列表拖拽
- SplitPane
- Dropzone
- Sticky
- CountTo
- 综合实例
- 错误日志
- Dashboard
- 引导页
- ECharts 图表
- Clipboard(剪贴复制)
- Markdown2html
项目的目录结构
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── plop-templates # 基本模板
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
安装
# 克隆项目
git clone https://github.com/PanJiaChen/vue-element-admin.git
# 进入项目目录
cd vue-element-admin
# 安装依赖
npm install
# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 本地开发 启动项目,启动完成后会自动打开浏览器访问 http://localhost:9527
npm run dev
中文
在v4.1.0+版本之后,默认 master 将不再提供国际化。如果你有国际化需求的请使用 i18n 分支,它与 master 同步更新。
修改 @/lang/index.js 文件,将默认语言由 en 改为 zh。
布局
Layout 对应代码:@/layout
app-main(主体区域)对应代码:@/layout/components/AppMain
这里在 app-main 外部包了一层 keep-alive 主要是为了缓存 <router-view> 的,配合页面的 tabs-view 标签导航使用。
路由和侧边栏
本项目侧边栏和路由是绑定在一起的,只要在 @/router/index.js 下面配置对应的路由,侧边栏就能动态的生成了。
配置路由时的配置项:
//当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
hidden: true // (默认 false)
//当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
redirect: 'noRedirect'
//当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
//只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
//若你想不管路由下面的 children 声明的个数都显示你的根路由
//你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
alwaysShow: true
name: 'router-name' //设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
meta: {
roles: ['admin', 'editor'] //设置该路由进入的权限,支持多个权限叠加
title: 'title' //设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name' //设置该路由的图标
noCache: true //如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
}
路由配置的完整示例:
{
// 你也可以在侧边栏中配置一个外链,只要你在 path 中填写了合法的 url 路径,当你点击侧边栏的时候就会帮你新开这个页面。
path: '/permission',
component: Layout,
redirect: '/permission/index', //重定向地址,在面包屑中点击会重定向去的地址
hidden: true, // 不在侧边栏线上
alwaysShow: true, //一直显示根路由
meta: { roles: ['admin','editor'] }, //你可以在根路由设置权限,这样它下面所以的子路由都继承了这个权限
children: [{
path: 'index',
component: ()=>import('permission/index'),
name: 'permission',
meta: {
title: 'permission',
icon: 'lock', //图标
role: ['admin','editor'], //或者你可以给每一个子路由设置自己的权限
noCache: true // 不会被 <keep-alive> 缓存
}
}]
}
你可以在Sidebar/index.vue中设置unique-opened来控制侧边栏,是否只保持一个子菜单的展开。
原则上有多少级路由嵌套就需要多少个<router-view>。如有三级路由嵌套的情况下,不要忘记还要手动在二级目录的根文件下添加一个 <router-view>。
本项目中也封装了一个面包屑导航,它也是和路由是绑定在一起的,是通过 watch $route 变化动态生成的。
权限验证
该项目中权限的实现方式是:通过获取当前用户的权限去比对路由表,生成当前用户具的权限可访问的路由表,通过 router.addRoutes 动态挂载到 router 上。
现在路由层面权限的控制代码都在 @/permission.js 中。
可以使用指令权限 v-permission
来简单快速的实现按钮级别的权限判断。实例:
<template>
<!-- Admin can see this -->
<el-tag v-permission="['admin']">admin</el-tag>
<!-- Editor can see this -->
<el-tag v-permission="['editor']">editor</el-tag>
<!-- Editor can see this -->
<el-tag v-permission="['admin','editor']">Both admin or editor can see this</el-tag>
</template>
<script>
// 当然你也可以为了方便使用,将它注册到全局
import permission from '@/directive/permission/index.js' // 权限判断指令
export default{
directives: { permission }
}
</script>
可以使用全局权限判断函数 checkPermission() 。实例:
<template>
<el-tab-pane v-if="checkPermission(['admin'])" label="Admin">Admin can see this</el-tab-pane>
<el-tab-pane v-if="checkPermission(['editor'])" label="Editor">Editor can see this</el-tab-pane>
<el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor">Both admin or editor can see this</el-tab-pane>
</template>
<script>
import checkPermission from '@/utils/permission' // 权限判断函数
export default{
methods: {
checkPermission
}
}
</script>
快捷导航(标签栏导航)
由于目前 keep-alive 和 router-view 是强耦合的, keep-alive 的 include 默认是优先匹配组件的 name ,所以在编写路由 router 和路由对应的 view component 的时候一定要确保 两者的 name 是完全一致的。
当在声明路由时添加了 Affix 属性,则当前tag会被固定在 tags-view中(不可被删除)。实例:
{
path: '',
component: Layout,
redirect: 'dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: {
title: 'dashboard',
icon: 'dashboard',
noCache: true,
affix: true
}
}
]
}
新增一个页面
- 在 @/router/index.js 中增加你需要添加的路由。
- 在 @/views 文件下 创建对应的文件夹,一般性一个路由对应一个文件(该模块下的功能组件或者方法就建议在本文件夹下创建一个utils或components文件夹,各个功能模块维护自己的utils或components组件)。
- 在 @/api 文件夹下创建本模块对应的 api 服务。
- 新增组件:在全局的 @/components 只会写一些全局的组件;每个页面或者模块特定的业务组件则会写在当前 views 下面。
- 新增样式:全局的 @/style 放置一下全局公用的样式,每一个页面的样式就写在当前 views下面,请记住加上scoped 或者命名空间,避免造成全局的样式污染。
样式
CSS 文件中的选择器是全局生效的,存在全局污染问题。vue 提供了 scoped 可以很方便的解决这个问题:只要加上 <style scoped> 这样 css 就只会作用在当前组件内了。
本项目所有全局样式都在 @/src/styles 目录下设置。
├── styles
│ ├── btn.scss # 按钮样式
│ ├── element-ui.scss # 全局自定义 element-ui 样式
│ ├── index.scss # 全局通用样式
│ ├── mixin.scss # 全局mixin
│ ├── sidebar.scss # sidebar css
│ ├── transition.scss # vue transition 动画
│ └── variables.scss # 全局变量
推荐:全局样式都写在 src/styles 目录下,每个页面自己对应的样式都写在自己的 .vue 文件之中。
和服务端进行交互
本项目中,一个完整的前端 UI 交互到服务端处理流程是这样的:
- UI 组件交互操作;
- 调用统一管理的 api service 请求函数;
- 使用封装的 request.js 发送请求;
- 获取服务端返回;
- 更新 data;
统一管理的 api service 请求函数都放在 @/src/api 文件夹中,并且一般按照 model 纬度进行拆分文件。
封装的 request.js 是基于 axios 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。
使用实例:
// api/article.js
import request from '../utils/request';
export function fetchList(query) {
return request({
url: '/article/list',
method: 'get',
params: query
})
}
// views/example/list
import { fetchList } from '@/api/article'
export default {
data() {
list: null,
listLoading: true
},
methods: {
fetchData() {
this.listLoading = true
fetchList().then(response => {
this.list = response.data.items
this.listLoading = false
})
}
}
}
Mock Data
在v4.0版本之后,在本地会启动一个mock-server来模拟数据,线上环境还是继续使用mockjs来进行模拟。
不管是本地还是线上所有的数据模拟都是基于mockjs生成的,所以只要写一套 mock 数据,就可以在多环境中使用。
添加mock数据,只需要找到 src/api/demo 对应的 mock 文件夹 mock/demo.js,创建一个能拦截 api 的路由的 mock 接口:
{
// url 必须能匹配你的接口路由(支持正则)
url: '/article/[A-Za-z0-9]/comments',
type: 'get', // 必须和你接口定义的类型一样
response: (req, res) => {
// mock 返回的结果
return {
code: 20000,
data: {
status: 'success'
}
}
}
}
构建,发布和环境变量
通过 vue.config.js 文件来控制打包参数。打包命令:
# 打包正式环境
npm run build:prod
发布部署时可能会发现资源路径不对 ,只需修改 vue.config.js 文件资源路径 publicPath 属性即可。
所有测试环境或者正式环境变量的配置都在 .env.development等 .env.xxxx文件中。
环境变量必须以VUE_APP_为开头,可以在全局使用: console.log(process.env.VUE_APP_xxxx)
。
除了 VUE_APP_* 变量之外,在你的应用代码中始终可用的还有两个特殊的变量:
- NODE_ENV - 会是 “development”、“production” 或 “test” 中的一个。具体的值取决于应用运行的模式。
- BASE_URL - 会和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。
其他
- 跨域问题常见解决方案:
- cors (Cross Origin Resource Sharing 跨域资源共享);
- 在 dev 开发模式下可以下使用 webpack 的 proxy ,在生产环境中需要使用 nginx 进行反向代理;
- ESLint 的所有的配置文件都在 .eslintrc.js 中,取消 ESLint 校验只需设置 lintOnSave: false 即可;
- 图表推荐 ECharts,功能齐全,社区 demo 也丰富: gallery ;
- 图标使用方式:
<svg-icon icon-class="password" /> // icon-class 为 icon 的名字
,需要改变颜色时改变 fill 属性即可; - 如果没有所需的图标,可以到 iconfont.cn 上(或者其它 svg 图标网站)选择并生成自己的 svg 图标,并放到 src/icons/svg 文件夹之中;
相关概念
HTML DOM
HTML DOM 是:HTML 的标准对象模型,HTML 的标准编程接口,W3C 标准。
HTML DOM 定义了所有 HTML 元素的对象和属性,以及访问它们的方法。换言之,HTML DOM 是关于如何获取、修改、添加或删除 HTML 元素的标准。
Node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型。
Node.js 是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。
NPM
NPM的全称是Node Package Manager,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。
Modern JavaScript with ES2015/16
ECMAScript,简称ES,是由ECMA国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)按照ECMA-262和ISO/IEC 16262标准制定的一种脚本语言规范。
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。其他的实现还有JScript、ActionScript。
这三种语言还提供了ECMA规范外的额外功能。
很久以前,所有浏览器中运行的JavaScript版本基于 ECMAScript 3。版本4因为特性蠕动(feature creep)被取消了(他们试图一次添加很多特性)。ES5是JavaScript的一个巨大版本。ES2015起,标准制定委员会决定每年更新一个版本,避免版本迭代间隔太久,也可以加快反馈速度。
ECMAScript 6(以下简称ES6)是在2015年发布的,官方名称即ECMAScript 2015。也就是说,ES6就是ES2015。
ES7 是 ECMA-262 标准第 7 版的简称,官方名称即 ECMAScript 2016,简称 ES2016。
ES6 相对于上一个版本 ES5.1 变化较大,增加了很多新特性:
- const 与 let 变量
- 模板字面量
- 解构数组/对象
- 对象字面量简写法
- for…of循环
- 展开运算符
- 剩余参数(可变参数)
- 箭头函数
- javascript标准函数this
- 箭头函数和this
- 默认参数函数
- 默认值与解构
- Javascript类
- super 和 extends
各大浏览器的最新版本,对 ES6 的支持可以查看https://kangax.github.io/compat-table/es6/。
更多参考:
Babel - Learn ES2015
《ECMAScript 6 入门》
AMD规范,commonJS规范,和CMD规范
Nodejs 环境所使用的模块系统就是基于CommonJs规范实现的,我们现在所说的CommonJs规范也大多是指Node的模块系统。
CommonJs是同步加载模块,适合在服务器端使用。
AMD,即 (Asynchronous Module Definition),这种规范是异步的加载模块。
CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。
AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块(性能影响很小可以忽略),却带来开发的很大便利性。
JSX和React
JSX是JavaScript XML,是React提供的语法糖(Syntactic Sugar,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用),能让我们可以在JS中写html标记语言。
JSX是一种JavaScript的语法扩展,运用于React架构中,其格式比较像是模版语言,但事实上完全是在JavaScript内部实现的。
元素是构成React应用的最小单位,JSX就是用来声明React当中的元素,React使用JSX来描述用户界面。
更多参见:https://reactjs.org/docs/introducing-jsx.html
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。
使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
更多参见: React官网 和 React中文官网 。
Flux和Redux
Flux是Facebook用户建立客户端Web应用的前端架构, 它通过利用一个单向的数据流补充了React的组合视图组件,这更是一种模式而非正式框架,你能够无需许多新代码情况下立即开始使用Flux。
一个Flux应用由三大部分组成:
- dispatcher— —负责分发事件
- store— —负责保存数据,同时响应事件并更新数据
- view— —负责订阅store中的数据,并使这些数据渲染相应的页面。
Flux的核心思想就是数据和逻辑永远单向流动,在Flux应用中,数据从action到dispatcher,再到store,最终到view的路线是单向不可逆的。
Redux 是 React 生态环境中最流行的 Flux 实现。
TypeScript
TypeScript是一种由微软开发的自由和开源的编程语言。
它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
它可以编译成纯JavaScript。
更多参见: TypeScript官网 和 TypeScript中文官网 。
webpack
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块(不止JavaScript模块,以及通过loader支持任何技术栈的模块),然后将所有这些模块打包成一个或多个 bundle(bundle:包,例如,将多个js脚本打包在一起的产物:一个大js文件)。
更多参考: webpack官网 和 webpack中文官网
Babel
Babel 是一个 JavaScript 编译器。
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
REPL
“读取-求值-输出”循环(英语:Read-Eval-Print Loop,简称REPL)是一个简单的,交互式的编程环境或解释器。
比如,Node 自带了交互式解释器,可以很好的调试JavaScript代码。
SSR
SSR(服务端渲染)顾名思义就是将页面在服务端渲染完成后在客户端直接展示。
优势主要在于:
- 更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
- 更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。
传统的SPA模式即客户端渲染的模式。例如,有一个html模板页,然后通过webpack打包生成一堆js、css等等资源文件。然后塞到index.html中。
用户输入url访问页面 -> 先得到一个html模板页 -> 然后通过异步请求服务端数据 -> 得到服务端的数据 -> 渲染成局部页面 -> 用户。
SSR模式即服务端渲染模式。
用户输入url访问页面 -> 服务端接收到请求 -> 将对应请求的数据渲染完一个网页 -> 返回给用户。
Git Hooks
Git 可以在特定的动作发生时触发自定义脚本,这一类动作称作钩子;
钩子分为 Client-Side Hooks 和 Server-Side Hooks 两类,也就是客户端钩子和服务端钩子。
其中客户端钩子在本地触发,比如提交时;
而服务端钩子则在 Git 服务器中触发,比如接收到来自客户端的推送时。
Promise
ES6 Promise 是异步编程的一种解决方案,比传统的解决方案- -回调函数和事件- - 更合理和更强大。
Promise 简单说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提共统一的API,各种异步操作都可以用同样的方法进行处理。