介绍
Vue.js 是什么
是一套用于构建用户界面的渐进式框架;自底向上逐层应用,它的设计思想、编码技巧也被众多的框架借鉴、模仿。Vue 的核心库只关注视图层
历史介绍
Vue 1.0
设计思想
是一种“渐进式框架”,淡化框架本身的主张,降低框架作为工具的复杂度,从而降低对使用者的要求
主要改进
-
提供指令的缩写:针对v-bind和v-on提供缩写形式
<!-- v-bind --> <div v-bind:class="box">全写</div> <div :class="box">缩写</div> <!-- v-on --> <button v-on:click="btn">全写</button> <button @click="btn">缩写</button>
-
清理精简所提供的接口
-
提高初始化的渲染效率:将v-repeat指令换成了v-for指令。同时优化了这个指令的渲染,效率提升一倍
-
两个官方工具的增强:vue-loader和vuetify
Vue 2.0
性能上的改进
用一个fork自snabbdom的轻量 Vitual DOM实现对渲染层进行了重写
在其上层,Vue的模板编译器能够在编译时做一些智能的优化处理,例如分析并提炼出静态子树以避免界面重绘时不必要的比对
有新的渲染层
服务器上的渲染
Vue2.0支持服务器渲染(SSR),并且是流式的,可以做组件级的缓存,这使得极速渲染成为可能
Vue3.0
3.0进行了非常大的重构,源码使用TypeScript重写,目前的代码98%以上使用TypeScript编写
前端框架与库的区别
-
jQuery库->DOM(操作DOM)+请求
art-template库->模板引擎
库=小而精
框架=全方面功能齐全=简易的DOM体验+发请求+模板引擎+路由功能
-
KFC的世界里,库就是一个小套餐,框架就是全家桶
-
代码不同
- 一般使用库的代码,是调用某个函数,我们自己来把控库的代码
- 一般使用框架,其框架在帮我们运行我们编写好的代码
- 框架:初始化自身的一些行为
- 执行你所编写的代码
- 施放一些资源
声明式渲染
vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
文本插值(设置定时器和清除定时)
<div id="counter">
Counter: {{ counter }}<br>
<button id="btn" @click="clear()">stopTimer</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const Counter = {
data() {
return {
counter: 0
}
},
mounted() {
this.timer = setInterval(() => {
this.counter++
}, 1000)
},
methods: {
clear() {
window.clearInterval(this.timer);
console.log(this.timer);
}
}
}
Vue.createApp(Counter).mount('#counter')
</script>
绑定元素的 attribute(悬浮出现信息)
v-bind
attribute 被称为指令,指令带有前缀 v-
,以表示它们是 Vue 提供的特殊 attribute
<div id="bind-message" class="demo">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const bindMessage = {
data() {
return {
message: "加载页面" + new Date().toLocaleString()
}
}
}
Vue.createApp(bindMessage).mount('#bind-message')
</script>
处理用户输入v-on&&v-model
v-on
用 v-on
指令添加一个事件监听器,通过它调用在实例中定义的方法
<div id="event-handling">
<p>{{ message }}</p>
<button v-on:click="reverseMessage()">反转 Message</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const EventHandling ={
data(){
return{
message:"hello Vue.js"
}
},
methods: {
reverseMessage(){
this.message=this.message
.split('')
.reverse()
.join('')
}
}
}
Vue.createApp(EventHandling).mount('#event-handling')
v-model
v-model
指令,它能轻松实现表单输入和应用状态之间的双向绑定
<div id="binding">
<p>{{ message }}</p>
<input v-model="message" />
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const vmodelBind={
data(){
return {
message:"hello vue"
}
}
}
Vue.createApp(vmodelBind).mount('#binding')
</script>
条件与循环v-if
例如:控制切换一个元素是否显示,不仅可以把数据绑定到 DOM 文本或 attribute,还可以绑定到 DOM 的结构,可以在 Vue 插入/更新/移除元素时自动应用过渡效果
<div id="vIf">
<span v-if="seen">现在你看到我了</span><br>
<button @click="change">change</button>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const con={
data(){
return{
seen:true
}
},
methods: {
change(){
this.seen=!this.seen
}
}
}
Vue.createApp(con).mount('#vIf')
</script>
v-for
v-for
指令可以绑定数组的数据来渲染一个项目列表
<div id="list-rendering">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
const ListRendering = {
data() {
return {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
}
}
Vue.createApp(ListRendering).mount('#list-rendering')
组件化应用构建
-
允许我们使用小型、独立和通常可复用的组件构建大型应用
-
组件本质上是一个具有预定义选项的实例
-
创建组件过程
// 创建 Vue 应用 const app = Vue.createApp(...) // 定义名为 todo-item 的新组件 app.component('todo-item', { template: `<li>This is a todo</li>` }) // 挂载 Vue 应用 app.mount(...)
子单元通过 prop 接口与父单元进行了良好的解耦
应用 & 组件实例
根组件
传递给 createApp
的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。
一个应用需要被挂载到一个 DOM 元素中。例如,如果我们想把一个 Vue 应用挂载到 <div id="app"></div>
,我们应该传递 #app
const RootComponent = { /* 选项 */ }
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
mount返回的是根组件实例;通常会使用vm
这个变量名标识组件实例
组件实例 property
在 data
中定义的 property 是通过组件实例暴露的;也暴露了一些内置property,如 $attrs
和 $emit
组件实例的所有 property,无论如何定义,都可以在组件的模板中访问。
const app = Vue.createApp({
data() {
return {
count: 4
}
}
})
const vm = app.mount('#app')
console.log(vm.count)//4
还有各种其他的组件选项:methods
,props
,computed
,inject
和 setup
生命周期钩子
组件创建时的初始化过程:需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等,也会运行一些叫做生命周期钩子的函数
-
created钩子
const app = Vue.createApp({ data() { return { count: 1 } }, created() { // `this` 指向 vm 实例 console.log('count is: ' + this.count) //"count is: 1" } }) const vm = app.mount('#app')
-
其他钩子:
mounted
、updated
和unmounted
在选项property或回调上不能使用this箭头函数并没有 this
,,this
会作为变量一直向上级词法作用域查找,直至找到为止
模板语法
允许开发者声明式地将 DOM 绑定至底层组件实例的数据
插值
文本插值
形式:Mustache 标签将会被替代为对应组件实例中 msg
property 的值
<p>Using mustaches: {{ msg }}</p>
v-once
:只渲染元素和组件一次,当数据改变时,插值处的内容不会更新。用于优化更新性能。
原始 HTML
双大括号会将数据解释为普通文本,而非 HTML 代码,所以我们得使用v-html
动态渲染任意的 HTML 容易导致XSS攻击
<div id="example1" class="demo">
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
</div>
const RenderHtmlApp = {
data() {
return {
rawHtml: '<span style="color: red">This should be red.</span>'
}
}
}
Vue.createApp(RenderHtmlApp).mount('#example1')
Attribute
对于布尔 attribute (它们只要存在就意味着值为 true
),值是 null
或 undefined
没有作用
避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写
使用 JavaScript 表达式
每个绑定都只能包含单个表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('')}}
<div v-bind:id="'list-' + id"></div>
指令
指令 (Directives) 是带有 v-
前缀的特殊 attribute
指令的职责
当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
参数
一些指令能够接收一个“参数”,v-bind
指令可以用于响应式地更新 HTML attribute;v-on
指令,它用于监听 DOM 事件
动态参数
在指令参数中使用 JavaScript 表达式,方法是用方括号括起来
<a v-bind:[attributeName]="url"> ... </a>
可以使用动态参数为一个动态的事件名绑定处理函数
修饰符
修饰符 (modifier) 是以半角句号.
指明的特殊后缀;例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
缩写
v-bind
缩写
<!-- 完整语法 -->
<a v-bind:href="url"> ... </a>
<!-- 缩写 -->
<a :href="url"> ... </a>
<!-- 动态参数的缩写 -->
<a :[key]="url"> ... </a>
v-on
缩写
<!-- 完整语法 -->
<a v-on:click="doSomething"> ... </a>
<!-- 缩写 -->
<a @click="doSomething"> ... </a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
Data Property 和方法
Data Property
data
选项是一个函数,返回一个对象,该对象的任何顶级 property 也直接通过组件实例暴露出来
- 修改
vm.count
的值也会更新$data.count
- 修改
$data.count
的值也会更新vm.count
方法methods
Vue 自动为 methods
绑定 this
,以便于它始终指向组件实例;
methods
和data property
一样可以在组件的模板中被访问;
通常被当做事件监听使用。
防抖和节流
Vue 没有内置支持防抖和节流,但可以使用 Lodash 等库来实现。
计算属性和侦听器
计算属性
对于任何包含响应式数据的复杂逻辑,通常使用计算属性
<div id="computed-basics">
<p>书的数量:</p>
<span>{{ publishedBooksMessage }}</span>//YES
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data() {
return {
author: {
name: 'John Doe',
books: ['语文','数学','英语']
}
}
},
computed: {
// 计算属性的 getter
publishedBooksMessage() {
// `this` points to the vm instance
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}).mount('#computed-basics')
</script>
计算属性缓存 vs 方法
不同的是计算属性是基于它们的反应依赖关系缓存的。
计算属性只在相关响应式依赖发生改变时它们才会重新求值(author.books
不变,多次访问publishedBookMessage
计算属性会立即返回之前的计算结果)
计算属性的 Setter
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
侦听器
watch
选项提供了一个更通用的方法,来响应数据的变化。
当需要在数据变化时执行异步或开销较大的操作时,watch
方式最有用
使用 watch
选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script>
const watchExampleVM = Vue.createApp({
data() {
return {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
}
},
watch: {
// whenever question changes, this function will run
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.get('https://yesno.wtf/api')
.then(response => {
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
}).mount('#watch-example')
</script>
计算属性 vs 侦听器
观察和响应当前活动的实例上的数据变动:侦听属性
<div id="demo">{{ fullName }}</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const vm = Vue.createApp({
data() {
return {
firstName: 'Foo',
lastName: 'Bar'
}
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
}).mount('#demo')
</script>
Class 与 Style 绑定
绑定 HTML Class
对象语法
可绑定到data property里面也可以绑定到返回对象的计算属性computed里面
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
data() {
return {
isActive: true,
hasError: false
}
}
//渲染的结果为:<div class="active"></div>
数组语法
<div :class="[activeClass, errorClass]"></div>
<div :class="[isActive ? activeClass : '', errorClass]"></div>
在组件上使用
绑定内联样式
对象语法
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
数组语法
自动添加前缀
多重值
条件渲染
v-if
:用于条件性地渲染一块内容v-else
来表示v-if
的“else 块”v-show
用于根据条件展示元素的选项是v-show
指令(带有v-show
的元素始终会被渲染并保留在 DOM 中)- 注意,
v-show
不支持<template>
元素,也不支持v-else
。 v-if
和v-for
一起使用,v-if
优先级高,这意味着v-if
将没有权限访问v-for
里的变量
- 注意,
v-if 和 v-show的区别
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if
是惰性的,在渲染条件为假时,什么也不做,直到为真时才会开始渲染条件快v-show
不管条件是什么都会渲染,并且只是简单的基于css进行切换- 若切换频繁,则
v-show
较好,若运行条件很少,则v-if
较好
列表渲染
- 用
v-for
把一个数组对应为一组元素
-
v-for="item in items" //第二个参数作为索引 v-for="(item, index) in items"
-
在
v-for
里使用对象-
v-for="value in myObject" //第二个的参数为 property 名称 v-for="(value, name) in myObject" //第三个参数作为索引 v-for="(value, name, index) in myObject"
-
-
维护状态
-
Vue 正在更新使用
v-for
渲染的元素列表时,它默认使用“就地更新”的策略。 -
只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
-
-
数组更新检测
- 变更方法:
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
- example1.items.push({ message: ‘Baz’ })
- 替换数组:
filter()
、concat()
和slice()
- 变更方法:
-
<template>
中使用v-for
事件处理
监听事件v-on
可以使用 v-on
指令 (通常缩写为 @
符号) 来监听 DOM 事件,并在触发事件时执行一些 JavaScript
用法:
v-on:click="methodName"
@click="methodName"
事件处理方法
在methods里面写事件处理方法
内联处理器中的方法
-
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法
-
用特殊变量
$event
把它传入方法-
@click="warn('Form cannot be submitted yet.', $event methods: { warn(message, event) { // now we have access to the native event if (event) { event.preventDefault() } alert(message) } }
-
多事件处理器
事件处理程序中可以有多个方法,这些方法由逗号运算符分隔
<button @click="one($event), two($event)">Submit</button>
事件修饰符
修饰符是由点开头的指令后缀来表示的。
.stop
、.prevent
、.capture
、.self
、.once
、.passive
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>
按键修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
处理函数只会在 $event.key
等于 'PageDown'
时被调用
按键别名
.enter
、.tab、
、.delete
(捕获“删除”和“退格”键)、.esc
、.space
、.up
、.down
、.left
、.right
、
系统修饰键
.ctrl
、.alt
、.shift
、.meta
、
.exact
修饰符
允许你控制由精确的系统修饰符组合触发的事件
表单输入绑定
基础用法
可以用 v-model 指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
- 文本 (Text)
<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>
-
多行文本 (textarea)
-
复选框 (Checkbox)
-
单选框 (Radio)
-
选择框 (Select)
修饰符
.lazy
:在“change”时而非“input”时更新(v-model.lazy=“msg”).number
:自动将用户的输入值转为数值类型(v-model.number=“age”).trim
:自动过滤用户输入的首尾空白字符(v-model.trim=“msg”)
组件基础
因为组件是可复用的组件实例,所以它们与 new Vue 接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
组件的复用
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册
全局注册的组件可以在随后创建的 app
实例模板中使用,也包括根实例组件树中的所有子组件的模板中。
通过 Prop 向子组件传递数据
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
我们可以使用 v-bind
来动态传递 prop。
<div id="blog-posts-demo">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
:id="post.id"
></blog-post>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data() {
return {
posts: [{id: 1,title: 'My journey with Vue'},{id: 2,title: 'Blogging with Vue'},{id: 3,title: 'Why Vue is so fun'}]
}
}
})
app.component('blog-post', {
props: ['title','id'],
template: `<h4>{{id}}-{{ title }}</h4>`
})
app.mount('#blog-posts-demo')
</script>
监听子组件事件
- 父级组件通过
v-on
或@
监听子组件实例的任意事件 - 子组件通过调用内建的
$emit
并传入事件名称来触发一个事件 - 通过
@enlarge-text="postFontSize += 0.1"
监听器,父级将接收事件并更新postFontSize
值
使用事件抛出一个值
- 子组件使用
$emit
的第二个参数来抛出这个值(@click="$emit('enlarge-text', 0.1)"
) - 父级组件通过
$event
访问到被抛出的这个值(@enlarge-text="postFontSize += $event"
)
通过插槽分发内容
Vue 自定义的 <slot>
元素实现分发内容
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})