开始Vue,抓紧咯
本文大部分内容来自于Vue官方中文文档,掺杂了自己的浅陋理解,仅用作个人学习使用,各位小伙伴不要被我不严谨的措辞、类比误导了
介绍
Vue.js是什么
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
开始
将 Vue.js 添加到项目中主要有四种方式:
- 在页面上以 CDN 包的形式导入。
- 下载 JavaScript 文件并自行托管。
- 使用 npm安装它。
- 使用官方的 CLI 来构建一个项目,它为现代前端工作流程提供了功能齐备的构建设置 (例如,热重载、保存时的提示等等)。
CDN
<script src="https://unpkg.com/vue@next"></script>
这样会直接使用最近版本
但是对于生产环境,最好使用明确的版本号和文件,防止盲目使用最新版本造成破坏
自行托管
下载.js文件,并按照使用普通js那样使用它,具体方式类似于CDN
npm
建议大型应用使用npm,可以配合模块打包器使用
# 最新稳定版
$ npm install vue@next
Vue 还提供了编写单文件组件的配套工具。如果你想使用单文件组件,那么你还需要安装 @vue/compiler-sfc
:
$ npm install -D @vue/compiler-sfc
命令行工具 CLI
Vite
声明式渲染
核心是一个允许采用模板语法声明式地将数据渲染进DOM的系统
文本插值:
<div id="hello">
{{msg}}:{{count}}
</div>
<script>
const msg={
data(){
return {
msg:"hello",
count:0
}
},
mounted() {
setInterval(() =>{
this.count++
},1000)
}
}
Vue.createApp(msg).mount("#hello")
</script>
数据和DOM建立了关联,所有东西都是响应式的。
指令绑定:
<div id="bind-attribute">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
<script>
const AttributeBinding = {
data() {
return {
message: 'You loaded this page on ' + new Date().toLocaleString()
}
}
}
Vue.createApp(AttributeBinding).mount('#bind-attribute')
</script>
v-bind
attribute 被称为指令。指令带有前缀 v-
。它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 title
attribute 和当前活跃实例的 message
property 保持一致”。
处理用户输入
为了让用户和应用进行交互,我们可以用 v-on
指令添加一个事件监听器,通过它调用在实例中定义的方法
<div id="bind-attribute">
<p>{{message}}</p>
<button v-on:click="func">反转</button>
</div>
<script>
const AttributeBinding = {
data() {
return {
message: 'You loaded this page on ' + new Date().toLocaleString()
}
},
methods: {
func(){
this.message=this.message.split('').reverse().join('')
//字符串切分-反转数组-字符数组转字符串
}
}
}
Vue.createApp(AttributeBinding).mount('#bind-attribute')
</script>
你没有触碰dom,dom的操作都由Vue处理
v-model实现双向绑定
<div id="hello">
{{msg}}:{{count}}
<br>
<input type="text" v-model="msg">
</div>
<script>
const msg={
data(){
return {
msg:"hello",
count:0
}
},
mounted() {
setInterval(() =>{
this.count++
},1000)
}
}
Vue.createApp(msg).mount("#hello")
</script>
双向绑定,input变化时msg也会同步改变
条件、循环
v-if
<div id="hello">
{{msg}}:{{count}}
<br>
<input type="checkbox" v-model="seen">
<br>
<span v-if="seen">你看到了我</span>
</div>
<script>
const msg={
data(){
return {
msg:"hello",
count:0,
seen:true
}
},
mounted() {
setInterval(() =>{
this.count++
},1000)
}
}
Vue.createApp(msg).mount("#hello")
</script>
v-for
配合列表处理数组
<div id="hello">
{{msg}}:{{count}}
<ol>
<li v-for="todo in todos">
{{todo.text}}
</li>
</ol>
</div>
<script>
const msg={
data(){
return {
msg:"hello",
count:0,
todos:[
{text:"1"},
{text:"2"},
{text:"3"}
]
}
},
mounted() {
setInterval(() =>{
this.count++
},1000)
}
}
Vue.createApp(msg).mount("#hello")
</script>
组件化应用
页面可以抽象出一颗祖建树
Vue中,组件本质上是一个具有与定义选项的实例
创建组件:创建一个组件对象,之后定义在父级组件即可。
<div id="todo-list-app">
<ol>
<!--这里似乎将todo-item绑定到了TodoItem组件,更改名字后就绑定不上了-->
<!--key不知道是什么,不绑定也可以显示-->
<!--todo应该是对应props里的todo属性-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
<script>
const TodoItem = {
props: ['todo'],
template: `<li>{{ todo.text }}</li>`
}
const TodoList = {
data() {
return {
groceryList: [
{ id: 0, text: 'Veasdgetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
},
components: {
TodoItem
}
}
const app = Vue.createApp(TodoList)
app.mount('#todo-list-app')
</script>
应用与组件实例
创建一个应用实例
每个应用都是从createApp
创建一个应用实例开始的。他用于注册一个“全局”组件。
const app = Vue.createApp({
/* 选项 */
})
const app = Vue.createApp({})
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
应用实例暴露的大多数方法都会返回该同一实例,允许链式:
Vue.createApp({})
.component('SearchInput', SearchInputComponent)
.directive('focus', FocusDirective)
.use(LocalePlugin)
根组件
传递给 createApp
的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。
一个应用需要被挂载到一个 DOM 元素中。例如,如果你想把一个 Vue 应用挂载到 <div id="app"></div>
,应该传入 #app
const RootComponent = {
/* 选项 */
}
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
与大多数应用方法不同的是,mount
不返回应用本身。相反,它返回的是根组件实例。
组件实例 property
在前面的指南中,我们认识了 data
property。在 data
中定义的 property 是通过组件实例暴露的:
const app = Vue.createApp({
data() {
return { count: 4 }
}
})
const vm = app.mount('#app')
console.log(vm.count) // => 4
还有各种其他的组件选项,可以将用户定义的 property 添加到组件实例中,例如 methods
,props
,computed
,inject
和 setup
。我们将在后面深入讨论它们。组件实例的所有 property,无论如何定义,都可以在组件的模板中访问。
Vue 还通过组件实例暴露了一些内置 property,如 $attrs
和 $emit
。这些 property 都有一个 $
前缀,以避免与用户定义的 property 名冲突。
生命周期钩子
每个组件在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
比如created钩子就可以在实例创建后执行代码。代码中使用this指向当前活动实例
Vue.createApp({
data() {
return { count: 1}
},
created() {
// `this` 指向 vm 实例
console.log('count is: ' + this.count) // => "count is: 1"
}
})
不要使用箭头函数,因为他无法使用this。
个人理解:类似于aop或者代理模式,给你在封装好的组件各个时期添加代码的功能,编程的本质是套衣服。
生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IPeXxIT-1643967326448)(https://v3.cn.vuejs.org/images/lifecycle.svg)]
模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层组件实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应性系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
## 插值
文本
双大括号,最基本最常见
会去寻找实例property中对应的值,并且自动绑定。
你可以使用 v-once
指令插一个不会更新的值。需要注意这个对其他数据的绑定影响
原始HTML
双大括号不会将内容解释为HTML,假如想输出HTML,请使用v-html
指令
只对可信内容进行HTML插值,不要将用户提供的内容进行插值
Attribute
使用v-bind
指令对属性插值,大括号不起作用
<div v-bind:id="dynamicId"></div>
如果绑定的值是 null
或 undefined
,那么该 attribute 将不会被包含在渲染的元素上。
### 使用js表达式
事实上,我们的插值可以是一个js表达式
但是要注意语句与表达式的区别
{{var x = 1}} //语句 不可以
{{x = 1}} //表达式 可以
指令
指令 (Directives) 是带有 v-
前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-for
和 v-on
是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind
指令可以用于响应式地更新 HTML attribute,下面例子中的href就是v-bind的一个参数
<a v-bind:href="url"> ... </a>
还有一个v-on的例子,它对应的是DOM的事件名
<a v-on:click="doSomething"> ... </a>
动态参数
可以使用js表达式代替参数
<a v-bind:[attributeName]="url"> ... </a>
假如你有一个attributeName
的值为"href"
,那么等价于v-bind:href
修饰符
修饰符 (modifier) 是以半角句号 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
:
<form v-on:submit.prevent="onSubmit">...</form>
1
在接下来对 v-on
和 v-for
等功能的探索中,你会看到修饰符的其它例子
缩写
不写v-bind和v-on
<!-- 完整语法 -->
<a v-bind:href="url"> ... </a>
<!-- 缩写 -->
<a :href="url"> ... </a>
<!-- 动态参数的缩写 -->
<a :[key]="url"> ... </a>
<!-- 完整语法 -->
<a v-on:click="doSomething"> ... </a>
<!-- 缩写 -->
<a @click="doSomething"> ... </a>
<!-- 动态参数的缩写 -->
<a @[event]="doSomething"> ... </a>
tips
对动态参数值约定
动态参数预期会求出一个字符串,null
例外。这个特殊的 null
值可以用于显式地移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式约定
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>
变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>
JavaScript 表达式
模板表达式都被放在沙盒中,只能访问一个受限的全局变量列表,如 Math
和 Date
。你不应该在模板表达式中试图访问用户定义的全局变量。
Data Property 和方法
Data Property
组件的data是一个函数,会在创建时调用,他需要返回一个对象
他们只会在首次创建时添加,因此你需要保证你用的proerty都在data返回值里,可以考试使用null等占位值。
假如在后续时添加data的新property,vue的响应系统不会跟踪它。
不要使用_和$开头的变量,这会导致和内部API冲突
方法
使用methods为组件添加方法,他是一个包含所需方法的对象
定义时也不要使用箭头函数,这会导致VUE无法注入this
注意了,这里函数内部假如使用axios等库再写函数时,vue就无法保证能将this注入进去了,所以需要使用匿名函数
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.get('https://yesno.wtf/api')
.then(response => {//这里一定要使用箭头函数!不然this无法获取到vue对象
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
模版中也支持js表达式,你可以在那些地方调用方法
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
防抖与节流
是什么
防抖:发生事件x秒后执行y方法,每次重新触发事件清零计时,也就是在最后一次触发后的x秒执行y方法。
比如搜索联想,在文本改变后1s进行联想。假如你在1s内持续输入,则不会联想。当你停止输入1s以上时才会联想。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKSuiwqS-1643967326449)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220203003254704.png)]
节流:发生事件x秒后执行y方法,x秒内重复触发事件不会重复执行,也就是每x秒最多进行一次操作
搜索联想就不能用这个了,不然持续输入的话,会每一秒联想一次。
常见场景比如即时查询,不管用户查询多少次,你定一个额度,一段时间最多返回一次结果即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O13H0rdn-1643967326450)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220203003719241.png)]
Vue解决方案
没有内置,可以使用Lodash等库来实现
如果一个组件只用一次,你可以直接在methods实现自己的防抖
但是复用时会导致防抖共享。需要彼此独立时,可以在生命周期钩子实现
app.component('save-button', {
created() {
// 使用 Lodash 实现防抖
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 移除组件时,取消定时器
this.debouncedClick.cancel()
},
methods: {
click() {
// ... 响应点击 ...
}
},
template: `
<button @click="debouncedClick">
Save
</button>
`
})
计算属性,侦听器
计算属性
模版里的表达式比较复杂时,会失去其简单和声明式的特征。应当使用计算属性代替
<div id="hello">
{{msg}}:{{count}}
<br>
<span v-if="countCheck"> count < 20 </span>
<span v-else> count &rt; 20 </span>
</div>
<script>
const msg={
data(){
return {
msg:"hello",
count:0,
countBool:false
}
},
mounted() {
setInterval(() =>{
this.count++
},100)
},
computed:{
countCheck(){
return this.count<20
}
}
}
Vue.createApp(msg).mount("#hello")
</script>
这里声明了countCheck
计算属性。
在上面的例子中countCheck
计算属性依赖于count,当count修改时也会动态的修改所有绑定的countCheck
。
countCheck
是一个计算属性的getter,不要混淆计算属性与函数。调用时,不要使用function()的函数调用方式,而要使用function这样的函数名。
计算属性PK方法
你也可以在methods里定义一个方法,之后调用方法来实现计算属性。这在实现上来看是一样的,但是计算属性有它自己的缓存(类似于记忆化搜索)。计算属性只会在相应的响应式依赖发生时重新求值。
computed: {
now() {
return Date.now()
}
}
Date.now ()
不是响应式依赖,这个计算属性也就不会执行,假如你真的要实现这样的操作的话,建议使用函数。
Setter
可以显性的声明一个Setter。
<div id="hello">
<input type="text" v-model="inputV" @change="change"/>
<br><span>{{TestSetter}}</span>
</div>
<script>
var vm=Vue.createApp({
data(){
return {
inputV:0,
Value :0
}
},
methods: {
change(){
this.TestSetter = this.inputV
}
},
computed:{
TestSetter : {
get(){
return this.Value
},
set(newValue){
this.Value=newValue
}
}
}
}).mount("#hello")
</script>
上面这堆东西其实可以简化成:
<div id="hello">
<input type="text" v-model="TestSetter"/>
<br><span>{{TestSetter}}</span>
</div>
<script>
var vm=Vue.createApp({
data(){
return {
Value :0
}
},
computed:{
TestSetter : {
get(){
return this.Value
},
set(newValue){
this.Value=newValue
}
}
}
}).mount("#hello")
</script>
可见,计算属性其实是一个很灵活的东西,他大体上就是一个数据一样使用,而非把它当作函数。
侦听器
侦听器顾名思义,目前看起来侦听器和一个定义了setter的计算属性用法类似
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
侦听器需要定义在watch
里,和需要监听的属性同名。
可以带0、1、2个参数,第一个参是新值,第二个参是旧值。
可以声明为对象类型,可以带immediately,deep等参数,详情百度
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</div>
<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: {
// 每当 question 发生变化时,该函数将会执行
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.get('https://yesno.wtf/api')
.then(response => {//这里一定要使用箭头函数!不然this无法获取到vue对象
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
}).mount('#watch-example')
</script>
计算属性PK侦听器
看着用…差不多
用错了顶多就是丑点,丑就丑吧,怕啥…
Class 与 Style 绑定
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind
处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind
用于 class
和 style
时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
绑定HTML Class
对象语法
我们可以传给 :class
(v-bind:class
的简写) 一个对象,以动态地切换 class:
<div :class="{ active: isActive }"></div>
上面的语法表示 active
这个 class 存在与否将取决于对象属性isActive
的真假
你可以在对象中传入更多字段来动态切换多个 class。此外,:class
指令也可以与普通的 class
attribute 共存。当有如下模板:
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
和如下 data:
data() {
return {
isActive: true,
hasError: false
}
}
渲染的结果为:
<div class="static active"></div>
<!--多个类名,html允许,不是父子关系-->
当 isActive
或者 hasError
变化时,class 列表将相应地更新。例如,如果 hasError
的值为 true
,class 列表将变为 "static active text-danger"
。
你也可以直接将这个对象对象在data中定义
<div :class="classObject"></div>
data() {
return {
classObject: {
active: true,
'text-danger': false
}
}
}
渲染的结果和上面一样。
我们也可以在这里绑定一个返回对象的计算属性,通过这个计算属性的getter为模版返回一个具体对象。这是一个常用且强大的模式:
<div :class="classObject"></div>
data() {
return {
isActive: true,
error: null
}
},
computed: {
classObject() {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
数组语法
我们可以把一个数组传给 :class
,以应用一个 class 列表:
<div :class="[activeClass, errorClass]"></div>
data() {
return {
activeClass: 'active',
errorClass: 'text-danger'
}
}
渲染的结果为:
<div class="active text-danger"></div>
如果你想根据条件切换列表中的 class,可以使用三元表达式:
<div :class="[isActive ? activeClass : '', errorClass]"></div>
这样写将始终添加 errorClass
,但是只有在 isActive
为真时才添加 activeClass
。
不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:
<div :class="[{ active: isActive }, errorClass]"></div>
在组件上使用
这个章节假设你已经对 Vue 组件有一定的了解。当然你也可以先跳过这里,稍后再回过头来看。
当你在带有单个根元素的自定义组件上使用 class
attribute 时,这些 class 将被添加到该元素中。此元素上的现有 class 将不会被覆盖。
例如,如果你声明了这个组件:
const app = Vue.createApp({})
app.component('my-component', {
template: `<p class="foo bar">Hi!</p>`
})
然后在使用它的时候添加一些 class:
<div id="app">
<my-component class="baz boo"></my-component>
</div>
HTML 将被渲染为:
<p class="foo bar baz boo">Hi</p>
对于带数据绑定 class 也同样适用:
<my-component :class="{ active: isActive }"></my-component>
当 isActive 为 truthy[1] 时,HTML 将被渲染成为:
<p class="foo bar active">Hi</p>
如果你的组件有多个根元素,你需要定义哪些部分将接收这个 class。可以使用 $attrs
组件 property 执行此操作:
<div id="app">
<my-component class="baz"></my-component>
</div>
const app = Vue.createApp({})
app.component('my-component', {
template: `
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
`
})
你可以在非 Prop 的 Attribute 小节了解更多关于组件属性继承的信息。
绑定内联样式
对象语法
:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式或短横线分隔来命名:
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data() {
return {
activeColor: 'red',
fontSize: 30
}
}
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div :style="styleObject"></div>
data() {
return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
}
同样的,对象语法常常结合返回对象的计算属性使用。
数组语法
:style
的数组语法可以将多个样式对象应用到同一个元素上:
<div :style="[baseStyles, overridingStyles]"></div>
自动添加前缀
在 :style
中使用需要一个 vendor prefix(浏览器引擎前缀) 的CSS属性时,Vue 将自动侦测并添加相应的前缀。Vue 是通过运行时检测来确定哪些样式的 property 是被当前浏览器支持的。如果浏览器不支持某个 property,Vue 会进行多次测试以找到支持它的前缀。
多重值
可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex
。
条件渲染
v-if
v-if只会在表达式为真是被渲染,也有对应的v-else-if和v-else。
可以添加一个<template>
包裹多个元素,统一使用v-if分组,最终的渲染结果不会包含<template>
v-show
v-show会始终保留在dom中,仅仅只是被隐藏了,而v-if如果没有满足条件,他是不会被渲染的。
v-show没有else,也不支持<template>
v-if
vs v-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
不要在同一个元素上一起使用v-for和v-if,v-if优先级更高,一般会达不到想要的结果,可以使用外层的<template>
包裹v-for
列表渲染
用 v-for
把一个数组映射为一组元素
我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items 是源数据数组,而 item
则是被迭代的数组元素的别名。
<ul id="array-rendering">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
Vue.createApp({
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
}).mount('#array-rendering')
在 v-for
块中,我们可以访问所有父作用域的 property。v-for
还支持一个可选的第二个参数,即当前项的索引。
<ul id="array-with-index">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
Vue.createApp({
data() {
return {
parentMessage: 'Parent',
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
}).mount('#array-with-index')
你也可以用 of
替代 in
作为分隔符,因为它更接近 JavaScript 迭代器的语法:
在 v-for
里使用对象
你也可以用 v-for
来遍历一个对象的 property。
<ul id="v-for-object" class="demo">
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
Vue.createApp({
data() {
return {
myObject: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
}).mount('#v-for-object')
维护对象
Vue渲染时使用就地更新策略,比如你itrems[0]和items[1]调换了,他是在0那里覆写1的数据,1覆写0的数据,而不是更改dom
默认很高效,但是有些情况不适用。
除非非常需要性能,否则建议分配一个唯一的key
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
数组更新检测
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所这些包裹的方法也会同步除法视图更新,可以在js里直接调用。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如
filter()
、concat()
和slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:example1.items = example1.items.filter(item => item.message.match(/Foo/))
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
人话:就是替换数组也会被vue监听到,而且使用了启发式方法,提高了效率。
显示过滤/排序后的结果
如何过滤结果?
计算属性,getter返回一个过滤后的数组
多重嵌套中无法使用计算属性,可以用方法来进行(记得吗,方法和计算属性挺像的,我们还区分过)
在 v-for
里使用值的范围
之前都是foreach迭代,这个对应普通循环
<div id="range" class="demo">
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>
在组件上使用 v-for
这部分内容假定你已经了解组件相关知识。你也完全可以先跳过它,以后再回来查看。
在自定义组件上,你可以像在任何普通元素上一样使用 v-for
<my-component v-for="item in items" :key="item.id"></my-component>
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 props:
<my-component
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
></my-component>
不自动将 item
注入到组件里的原因是,这会使得组件与 v-for
的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。
下面是一个简单的 todo 列表的完整例子:
<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>
<todo-item
v-for="(todo, index) in todos"
:key="todo.id"
:title="todo.title"
@remove="todos.splice(index, 1)"
></todo-item>
</ul>
</div>
const app = Vue.createApp({
data() {
return {
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() {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
app.component('todo-item', {
template: `
<li>
{{ title }}
<button @click="$emit('remove')">Remove</button>
</li>
`,
props: ['title'],
emits: ['remove']
})
app.mount('#todo-list-example')
事件处理
监听事件
使用v-on
或者@
来监听DOM
事件处理方法
直接写太乱了,在组件里添加一个methods来定义他自己的方法,并当作参数给予。
可以带一个参数event,是DOM的原生event
内联处理器方法
<div id="inline-handler">
<button @click="say('hi')">Say hi</button>
<button @click="say('what')">Say what</button>
</div>
Vue.createApp({
methods: {
say(message) {
alert(message)
}
}
}).mount('#inline-handler')
太神经病了,谁会这么写…而且还很难处理返回值。
多事件处理器
绑定多个,逗号分隔,也没什么新东西
事件修饰符
event.preventDefault()
取消事件
event.stopPropagation()
阻断传播,防止其夫事件处理程序执行。
我们可以同样使用这两个方法处理,但是操作DOM是不优雅的。
v-on
有事件修饰符帮助完成这些操作
.stop
防止事件冒泡,只执行子节点行为,不上升到父节点.prevent
阻止默认事件,比如submit不会提交.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>
按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许为 v-on
或者 @
在监听键盘事件时添加按键修饰符:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
你可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
<input @keyup.page-down="onPageDown" />
在上述示例中,处理函数只会在 $event.key
等于 'PageDown'
时被调用。
按键别名
Vue 为最常用的键提供了别名:
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta
win-win标键 mac-command键
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
### .exact修饰符
.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Ctrl 和 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!--ctrl+click ctrl+shift+click 都可以触发-->
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!--ctrl+click可以触发-->
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
表单输入绑定
基础用法
使用v-model
实现双向绑定。其实他只是个语法糖。他会忽略属性中的初始值,只会绑定成data中的初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
对于需要输入法的文字,不会在组织输入法时响应,假如想的话,需要使用input事件监听器和value进行绑定。
文本
直接v-model绑定
多行文本
使用Textarea绑定多行文本
插值在textarea中无用。
checkbox
前面试过了,绑定到一个布尔值
可以绑定到一个数组,只有为true的会存在里面。checkbox的value对应名字
<body>
<div id="v-model-multiple-checkboxes">
<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>
<script>
Vue.createApp({
data() {
return {
checkedNames: []
}
}
}).mount('#v-model-multiple-checkboxes')
</script>
</body>
radio
类似于checkbox绑定数组的操作,但是这里不是数组而是单值
<body>
<div id="v-model-radiobutton">
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<br />
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<br />
<span>Picked: {{ picked }}</span>
</div>
<script>
Vue.createApp({
data() {
return {
picked: ''
}
}
}).mount('#v-model-radiobutton')
</script>
</body>
下拉选择框
<body>
<div id="v-model-select" class="demo">
<select v-model="selected">
<option disabled value="">选一个</option>
<option>选项1</option>
<option>选项2</option>
<option>选项3</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
<script>
Vue.createApp({
data() {
return {
selected: ''
}
}
}).mount('#v-model-select')
</script>
</body>
值绑定
我们可以利用v-bind将值绑定到一个动态属性上
复选框
<body>
<div id="v-model-select" class="demo">
<input type="checkbox" v-model="toggle" :true-value="yesValue" :false-value="noValue" />
{{toggle}}
</div>
<script>
Vue.createApp({
data() {
return {
toggle: '',
yesValue:'yyes',
noValue:'noo'
}
}
}).mount('#v-model-select')
</script>
</body>
根据是否选择,去访问false-value或者true-value绑定的值(不带:可以直接指定一个字符串),之后通过v-model递给toggle
注意,没有被选中的复选框不会被提交,也就是可能存在false-value和true-value都没被提交的情况。如果一定要提交一个值,使用单选。
单选框
使用v-bind绑定到value属性即可。
<input type="radio" v-model="pick" v-bind:value="a" />
// 当选中时
vm.pick === vm.a
下拉选择框
一样的,option里使用v-bind绑定到value属性即可。
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步。你可以添加 lazy
修饰符,从而转为在 change
事件之后进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="text" />
当输入类型为 text
时这通常很有用。如果输入类型是 number
,Vue 能够自动将原始字符串转换为数字,无需为 v-model
添加 .number
修饰符。如果这个值无法被 parseFloat()
解析,则返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg" />
组件基础
基本示例
// 创建一个Vue 应用
const app = Vue.createApp({})
// 定义一个名为 button-counter 的新全局组件
app.component('button-counter', {
data() {
return {
count: 0
}
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
})
app.mount('#components-demo')
<div id="components-demo">
<button-counter></button-counter>
</div>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8CCXZNv-1643967326452)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220204150737091.png)]
相当于仍过去了一段模版,可以有自己的数据,属性,css等。
复用
随便复用,各自独立
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ea5fGISt-1643967326453)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220204150841487.png)]
组织
嵌套组件树
通过 Prop 向子组件传递数据
我们一定需要一个向子组件传递数据的方式。
Prop 是你可以在组件上注册的一些自定义属性,就像input标签的type。为了给博文组件传递一个标题,我们可以用 props
选项将其包含在该组件可接受的 prop 列表中:
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-post-demo')
<div id="blog-post-demo" class="demo">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
这样我们就可以动态通过v-bind绑定了。
监听子组件事件
假如我们的子组件中有一个按钮和他的点击事件,我们要如何安排这个组件呢?
在组件中,使用@click="$emit(‘test2’)"可以将test2绑定到这个click事件上。
test2哪里来呢?在html书写组件时写上就好了
<button-test @test2="count++"></button-test>
<div id="test">{{count}}
<button-test @test2="count++"></button-test>
</div>
<script>
const app=Vue.createApp({
data(){
return {count:0}
}
})
app.component('button-test',{
template:`<button @click="$emit('test2')">dasdssadascsaa</button>`
})
app.mount("#test")
</script>
我们可以在组件的 emits
选项中列出已抛出的事件:
app.component('blog-post', {
props: ['title'],
emits: ['enlargeText']
})
使用事件抛出值
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 <blog-post>
组件决定它的文本要放大多少。这时可以使用 $emit
的第二个参数来提供这个值:
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>
然后当在父级组件监听这个事件的时候,我们可以通过 $event
访问到被抛出的这个值:
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
或者,如果这个事件处理函数是一个方法:
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
那么这个值将会作为第一个参数传入这个方法:
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
组件上使用v-model
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
<custom-input v-model="searchText"></custom-input>
在该组件中实现 v-model
的另一种方法是使用 computed
property 的功能来定义 getter 和 setter。get
方法应返回 modelValue
property,set
方法应该触发相应的事件。
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})
插槽分发内容
和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
<alert-box>
Something bad happened.
</alert-box>
有可能出现一些问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KOkPY4rV-1643967326454)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220204171533797.png)]
这可以通过使用 Vue 的自定义 <slot>
元素来实现,在模板里使用slot标签当作占位符
动态组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5cGgqFo-1643967326455)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220204171648563.png)]
如何实现切换效果?
就是不要写组建名字的标签,而是写component,之后在is属性里动态切换组件id。
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component :is="currentTabComponent"></component>
在上述示例中,currentTabComponent
可以包括:
- 已注册组件的名字,或
- 一个组件选项对象
解析模板的注意事项
如果想在 DOM 中直接书写 Vue 模板,Vue 将不得不从 DOM 中获取字符串。这会因为浏览器的原生 HTML 解析行为而导致一些小问题。