Appearance
Vue的基本语法
学习目标
- 【了解】什么是Vue
- 【了解】Vue和传统DOM操作的区别
- 【了解】Vue的学习内容
- 【掌握】Vue的模板语法
- 【掌握】Vue的常见指令
- 【掌握】Vue的常见配置选项
1.Vue是什么?
从今天开始,我们正式进入到Vue框架的学习,我们之前学习过一个相对轻量的库jQuery,而框架是一个更大的概念,它提供了一套完整的解决方案让我们能高效地开发web应用,一起看看官网对它的定义:
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
一起来看看其中几个重要描述:
Vue是用来构建用户界面的 前端的核心工作就是构建页面,Vue给我们提供了一套自己的模板语法,按照它的语法,我们会发现在构建页面的效率上比传统的DOM操作要高效很多,我们不用过多关注Vue内部是如何实现其中的构建工作,学习Vue其实非常轻松。
Vue是一套渐进式框架,Vue的内容学习是循序渐进,逐渐丰富的,如下图所示,每完成一个色块内容的学习,我们都能完成一些开发工作,当我们学习完整个生态内容的时候,开发复杂的项目也不在话下了。
Vue构建的是单页应用(SPA),Vue最终构建的项目只有一个页面,这是和传统开发的显著不同,单页应用的用户体验更加优秀,更符合现代前端组件化的开发思想,主流框架基本都实现了对单页应用的支持。
2.Vue的学习内容
2.1版本介绍
当前Vue已经发布到了v3.x版本,但市场主流的开发版本仍然是v2.6版本,所以我们的课程内容会从v2版本开始,v2升级到v3版本中优化了很多语法的书写,所以为了更好的适应市场要求,v2和v3版本的语法都必须掌握,当前课程阶段我们从v2.6版本开始学习,之后会单独来学习v3版本的新语法。
2.2生态介绍
我们已经知道Vue是一个渐进式框架,我们需要学习整个生态系统的知识体系,现在来介绍一下它。
- Vue.js 这是Vue的核心语法,用来构建web应用的视图。
- VueComponent 现代化的前端都采用组件化的开发思想,Vue同样提供了组件化开发的解决方案。
- Vue-Cli 官方提供的脚手架工具,用来构建大型的前端项目,基于webpack来构建。
- VueRouter 官方提供的路由器,实现单页应用的页面跳转。
- VueX 官方提供的状态管理工具,实现复杂应用的数据管理。
这些生态里的知识我们会在接下来的课程中逐一学习,最终完成大型的实战级项目。
3.Vue的模板语法
3.1 Vue初体验
我们先来感受一下Vue和传统DOM的区别,相信体验完后你便会爱上使用这个框架,在Vscode编辑器中新建一个html文件,通过<script>
标签引入vue的在线文件。
js
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
我们要完成这样一个案例:
页面上有一个计数器,初始值为0,放置一个按钮button,点击按钮,页面上的计算器自增1
使用传统的DOM操作,代码应该是这样的:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>0</div>
<button>按钮</button>
<!-- <script src="/vue.js"></script> -->
<script>
document.querySelector('button').onclick=function(){
document.querySelector('div').innerText = Number(document.querySelector('div').innerText)+1
}
</script>
</body>
</html>
使用Vue来操作,代码是这样的(大家现在不用纠结其中的一些代码书写方式):
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{count}}</div>
<button @click='count++'>按钮</button>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data:{
count:0
}
})
</script>
</body>
</html>
大家体会一下,你会发现Vue在实现这个功能的时候把传统的DOM操作都"去掉了",确实如此,我们不用自己来完成复杂的DOM操作,我们只关注数据本身的变化,这是Vue的核心理念,开发者只需要遵照Vue的语法操作数据控制视图即可,DOM操作由框架本身帮我们完成了,是不是非常强大!
3.2 Vue和视图的关联
我们当前课程阶段的Vue统一指代V2.6版本,此后不再赘述。
1.我们要使用Vue,第一步需要实例化Vue这个构造函数,引入vue.js文件为我们当前作用域提供了这个构造函数。
2.注意在上面的例子中我们使用了2个很奇怪的语法和
@click='count++'
这显然不是HTML的标准语法,这是Vue独有的,最终这个语法肯定需要被编译,所以出现了这些特殊的区域就是Vue的实例需要管理的区域,所以我们在刚才的结构外部套上了一个id为app的容器,那如何让Vue实例和这个容器关联起来呢,我们在实例化Vue时传入了一个配置选项,其中有一个el属性,就是依赖它我们将Vue实例和要操作的视图区域关联起来了!
3.3 Vue中的插值语法
仍然是刚才的例子,我们需要用一个数据来控制计数器的变化,这样的数据我们需要放到一个名为data的配置项中,如果你的视图中需要使用指定数据,记得把它放到data中,那么如何在html中使用它呢,你需要把对应的数据用双大括号包裹起来,我们把这个叫做插值表达式(mustaches)
插值中除了直接放置data中的数据外,还可以放置表达式,刚才的count可以写成一个三元表达式。
html
<div>{{count%2===0?'偶数':'奇数'}}</div>
3.4 vue中的事件绑定
继续回顾刚才的例子,我们通过@click='count++'
语法实现了对于点击事件的绑定。
在Vue中,可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。v-on
可以简写为@
。所以上面的语法应该不难理解,通过绑定点击事件,实现count的自增,然后视图随之更新。
我们再来实现一个简单的事件绑定,点击按钮后弹出一个弹窗,显示hello world
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button v-on:click='alert("hello wolrd")'>弹窗按钮</button>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
})
</script>
</body>
</html>
按照第一个例子的思路,你应该会这么来书写代码,但是你会发现浏览器报错了!提示我们Property or method "alert" is not defined on the instance but referenced during render
,问题在哪?
如果事件执行的代码变得复杂的时候,就不能直接接在指令后面,可以在指令后定义一个调用方法的名称,然后在配置选项methods中实现这个方法,应该这样做:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button v-on:click='alert'>弹窗按钮</button>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
methods: {
alert(){//这里的alert是任意定义的方法名称
alert('hello world')
}
},
})
</script>
</body>
</html>
我们之后都会这么做,把所有事件的回调方法都定义在methods这个配置选项中。
我们继续讨论事件绑定中的一些细节,现在的案例是这样的,页面上有2个按钮(say hi 和 say hello),我希望用一个方法来完成这2个弹窗,相信很多同学想到了传递参数,那么应该如何做呢?请看以下代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
methods: {
say(msg){
alert('say'+msg)
}
},
})
</script>
</body>
</html>
这时可能有的同学又有一个问题,DOM事件中的那个事件对象(event)去哪儿呢,如果需要用到,那应该怎么办呢?在Vue中,你需要手动地传入一个特殊变量$event
来指代原始的事件对象。
3.5 为什么在HTML监听事件?
大家可能注意到这种事件监听的方式违背了关注点分离 (separation of concern) 这个长期以来的优良传统。但不必担心,因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图模板 上,它不会导致任何维护上的困难。实际上,使用 v-on
有几个好处:
- 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
- 因为你无须在 JavaScript 里手动绑定事件,你的配置项代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
- 当一个 Vue实例被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
4.Vue的常见指令
我们刚才已经学习了一个指令v-on
,它可以用来绑定DOM事件,并且可以被简写为@
,接着再来看一些常见指令。
4.1 v-bind(用于属性绑定)
v-bind指令可以将data中的数据绑定到HTML模板中来。
一起来看这个例子,页面上有一个<a>
标签用作跳转,现在我不希望将它的href属性固定,我将他存放到data的数据中用作动态展示,这时便需要使用v-bind指令了。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<a v-bind:href="url">跳转到百度</a>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data:{
url:'http://www.baidu.com'
}
})
</script>
</body>
</html>
例子中的v-bind:href="url"
可以进一步简写为:href="url"
4.2 v-if(用于条件渲染)
v-if
指令可以条件性地渲染一块内容,这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="isshow=!isshow">点击切换标题展示</button>
<h1 v-if="isshow">这是一个动态展示的标题</h1>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
isshow: true,//isshow用来控制h1标题的渲染
},
})
</script>
</body>
</html>
和分支语句类似的,v-else
指令也可以起到else的作用,但是v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。仍然是上面的示例:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="isshow=!isshow">点击切换标题展示</button>
<h1 v-if="isshow">标题1</h1>
<h1 v-else>标题2</h1>
<!-- 标题1和标题2只会展示1个 -->
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
isshow: true,//isshow用来控制标题的切换
},
})
</script>
</body>
</html>
因为 v-if
是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if
。最终的渲染结果将不包含 <template>
元素。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="isshow=!isshow">点击切换整个区域展示</button>
<template v-if="isshow">
<h1>标题1</h1>
<p>这是段落1</p>
<p>这是段落2</p>
</template>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
isshow: true, //isshow用来控制标题和段落的切换
},
})
</script>
</body>
</html>
4.3 v-show(用于条件展示)
另一个用于根据条件展示元素的选项是 v-show
指令。它的用法跟v-if
基本一样,上面的标题切换的案例同样可以用v-show
指令来替换。
html
<h1 v-show="isshow">标题1</h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 一个CSS属性display
。
4.4 v-if 和 v-show的区别
再来总结一下上面2个指令的区别:
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
4.5 v-for(用于循环渲染)
我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。循环渲染的时候必须给每个元素加上唯一的key值。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<!-- items是需要循环展示的数组 item表示每个具体数组元素的别名 -->
<li v-for="item in items" :key="item.message">{{ item.message }}</li>
</ul>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
items: [{ message: 'Foo' }, { message: 'Bar' }], //循环展示的数据都是数组
},
})
</script>
</body>
</html>
当然,你也可以用 of
替代 in
作为分隔符。
html
<li v-for="item of items" :key="item.message">{{ item.message }}</li>
绝大多数情况下,v-for
指令都被用来渲染数组,也可以用来渲染对象和数字。
渲染对象的写法:
html
<ul id="app" class="demo">
<li v-for="(value,key,index) in object">
{{ value }}
{{ key }}
{{ index }}
</li>
</ul>
new Vue({
el: '#app',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
渲染数字的写法:
html
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
4.6 v-for 和 v-if使用的问题
在vue中,如果v-for
和v-if
2个指令放在一个元素上,v-for
的优先级会更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中,大多数情况下,请不要这么做,你可以将v-if
放到v-for
的外层元素上,这样会根据v-if
后的条件选择性的执行循环,性能更好。
html
<ul v-if="todos.length"> //如果todos数组没有内容 就不会渲染其中的结构
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
4.7 v-html 和 v-text(用于插值)
我们之前学过利用插值表达式来渲染标签内部的内容,还可以是用v-text
指令来代替。但如果要更新标签内部的部分内容,仍然需要使用插值表达式来完成。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用插值表达式和使用v-text指令的效果是一致的 -->
<div>{{msg}}</div>
<div v-text="msg"></div>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data:{
msg:"hello world"
}
})
</script>
</body>
</html>
v-text
指令是用来更新元素内部的textContext
内容,v-html
指令可以更新元素内部的innerHTML
html
<div id="app">
<div v-html="msg"></div>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data:{
msg:"<em>hello world </em>"
}
})
</script>
在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用 v-html
,永不用在用户提交的内容上。
html
<div id="app">
<div v-html="msg"></div>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data:{
msg:'<a href="javascript:alert(1)">来点我呀</a>'//msg的标签内容会被执行 跳转到存在安全隐患的位置
}
})
</script>
指令部分先学习这么多,之后会接着学习。
5.Vue的常见配置选项
我们再来回顾一下之前学习过的配置选项。
el
是用来匹配作用的视图容器。
data
是用来存放视图模板中需要使用的数据,在之前的案例中,data是一个对象的形式,但在之后的案例中,我们统一把data写成一个函数的形式,这更符合Vue的设计规范(Vue3版本和组件中强制要求写成函数式)。
methods
是用来存放视图模板中需要使用的方法。
接着来介绍更多的配置选项
5.1 计算属性
我们来完成这么一个需求,希望将msg这个字符串翻转一下输出为olleh,按照我们之前所说的,可以在模板内部书写表达式,所以可以这么来做:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 希望将msg这个字符串翻转一下输出为olleh 我们可以直接使用表达式来完成 -->
{{msg.split('').reverse().join('')}}
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
msg:'hello'
}
},
})
</script>
</body>
</html>
但这么做并不是vue推荐的,我们尽量需要简写插值模板里的内容,我们可以把它提取出来,放到计算属性这个配置选项中,所以我们可以这么来做:
html
<div id="app">
{{reverse}}
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
msg:'hello'
}
},
computed:{
//计算属性中 必须以返回值来表达最终的结果 访问data中的数据必须通过this来完成 配置选项之间的数据访问都必须通过this
reverse(){
return this.msg.split('').reverse().join('')
}
}
})
</script>
总结一下,如果一些数据需要通过data选项中的原始数据经过复杂计算而来,那么我们便可以使用计算属性来配置这些数据,计算属性中的数据也可以直接在模板中使用。
5.2 计算属性和方法的比较
上面的例子,其实我们使用methods配置项也能同样完成。
html
<div id="app">
<!-- 使用方法记得加上()来调用结果 -->
{{reverse()}}
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
msg:'hello'
}
},
methods: {
reverse(){
return this.msg.split('').reverse().join('')
}
},
})
</script>
那么为什么我们还需要计算属性这个配置选项?因为计算属性中存在缓存机制,只有当计算属性依赖的原始数据发生改变时,计算属性才会重新计算,所以计算属性的性能是更高的,而普通的方法做不到这一点。
示例中count数据变更后,每一次视图模板都会重新更新,方法会多次调用,但是计算属性却只执行了一次,显然性能更好。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用方法记得加上()来调用结果 -->
{{reverse1()}}
{{reverse2}}
<button @click="count++">{{count}}</button>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
msg:'hello',
count:0
}
},
methods: {
reverse1(){
console.log("方法执行了")
return this.msg.split('').reverse().join('')
}
},
computed:{
reverse2(){
console.log("计算属性执行了")
return this.msg.split('').reverse().join('')
}
}
})
</script>
</body>
</html>
5.3 侦听属性
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当data中的某一项数据发生变化希望视图也做出对面响应的时候,推荐使用侦听属性。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<button @click="count++">{{count}}</button>
</div>
<script src="/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
msg: '偶数',
count: 0,
}
},
watch: {
//watch配置选项中书写需要监听的data数据源 函数体中书写数据源发生变化时执行的逻辑
count() {
if (this.count % 2 === 0) {
this.msg = '偶数'
} else {
this.msg = '奇数'
}
},
},
})
</script>
</body>
</html>