Vue全家桶系~1.Vue2基础(兼容)
Vue基础学习
HTML+CSS+JS基础文档:https://developer.mozilla.org/zh-CN/docs/Web
Vue3官网文档:https://cn.vuejs.org/ | Vue2文档:https://v2.cn.vuejs.org/v2/guide
个人建议:对于小白和新手,以及只会HTML+CSS+JS基础的人来说,别上来就搞那一套高度封装的开发方式,开发是很方便,调bug也是非常麻烦的。先以这种脚本引入+HTML的混合开发入手,熟悉之后再用组件化开发方式
这样做两个好处:1.原来HTML+JQ的后端开发程序员,用这种开发方式反而更容易入手、2.市面上很多Vue2的项目,不至于直接懵圈
PS:我下面案例只是使用Vue2的脚本引入的方式便捷开发,语法和知识点都是参考最新Vue3最新文档(贴的地址也都是Vue3的文档)
如果有了前端开发基础,比如开发的时候早就抛弃HTML这种老一辈开发方式,已经有TS、Vue之类的基本经验 ==> 【
跳过这篇文章
,看下篇】
1.环境配置
1.1.IDE配置(必配)
VSCode官方插件:Vue Language Features (Volar)
TypeScript支持:
TypeScript Vue Plugin (Volar)
Vue快速开发:Vue VSCode Snippets
(输入缩写快速生成代码段)
错误高亮提示:Error Lens
1.2.第一个demo
练手先使用传统的开发方式:把vue当做一个脚本js文件引入,然后再开发(官方也推荐这样入门)
开发的时候可以引用开发版vue.js(更多友好提示),发布的时候替换为vue.mini.js(更轻量级)
首先说下大致流程:
1首先要在HTML文件里面创建一个存放宿主文件的标签(Vue所有DOM操作都是这个里面进行)可以是id,也可以是class
<div id="app"> </div>
2然后引入vuejs(Vue2基础语法和Vue3基本上一样)
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.9/vue.js"></script>
3接着就要在script标签里面实例化vue对象了:
<script>
// 3.创建一个vue示例
const app = new Vue({
el: '#app' // 【必须】指定宿主id或者class
}
});
</script>
下面进行案例实战,需求:在HMTL页面里面显示小明的自我介绍(变量赋值),1s后补充下介绍(变量修改)
<!-- 1.创建一个宿主文件 -->
<div id="app"> 自我介绍:{{name}} </div>
<!-- 2.引入vue.js -->
<script src="../assets/vue2.js"></script>
<script>
// 3.创建一个vue示例
const app = new Vue({
el: '#app', // 【必须】指定宿主id或者class
data() {// 输入vdata可以快速生成
return {
name: '大家好,我叫小明'// 变量初始化赋值
}
}
});
// 4.一秒后修改下文本内容
setTimeout(() => {
// 直接变量修改,不用DOM操作了
app.name = '谢谢大家'; // 变量修改
}, 1000);
</script>
输出:
自我介绍:大家好,我叫小明(刚开始)
自我介绍:谢谢大家(1s后)
Vue不只是一个模板引擎,它其实是响应式的,数据和DOM已经关联了。要验证也很简单,控制台改变下变量,页面就实时改变了:
2.模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
2.1.文本渲染(文本插值)
数据绑定最常见的形式就是使用“Mustache”语法{{ 变量 }}
的文本插值,这种方式分两种:
- 当数据改变时,插值文本跟着改变(常用):
<span>Message: {{ msg }}</span>
- 能执行一次性的插值,,当数据改变时,内容不会更新
<span v-once>这个将不会改变: {{ msg }}</span>
- 注意:这会影响到该节点上的其它数据绑定
贴一下第一个demo的例子:
<!-- 1.创建一个宿主文件 -->
<div id="app"> <span>{{name}}</span> </div>
<!-- 2.引入vue.js -->
<script src="../assets/vue2.js"></script>
<script>
// 3.创建一个vue示例
const app = new Vue({
el: '#app', // 【必须】指定宿主id或者class
data() {// 输入vdata可以快速生成
return {
name: '大家好,我叫小明'
}
}
});
// 4.一秒后修改下文本内容
setTimeout(() => {
// 直接变量修改,不用DOM操作了
app.name = '谢谢大家';
}, 1000);
</script>
如果其他不变,就是把<span>{{name}}</span>
改成:<span v-once>{{name}}</span>
,你会发现只显示第一次赋值的内容
app.name的变量其实已经修改了,但是页面文本内容因为设置了v-once,不会修改
补充:HTML渲染(v-html)
双大括号会将数据解释为普通文本,为了输出HTML,可以使用v-html
指令:<span v-html="变量"></span></p>
示例:
<div id="app">
<!-- 默认是文本 -->
<p>{{htmlone}}</p>
<!-- 想渲染html就要这样写 -->
<p v-html="htmltwo"></p>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
htmlone: '<h2>我是HTML</h2>',
htmltwo: '<h2>我是HTML</h2>'
}
},
})
</script>
效果:
这个 p
的内容将会被替换成为 property 值 htmltw
,直接作为 HTML——会忽略解析 property 值中的数据绑定。注意,你不能使用 v-html
来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。
你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。
2.2.属性绑定(v-bind)
1.常用绑定案例
HTML特性不能用Mustache语法(双大括号),要使用v-bind
指令:v-bind:属性值="变量名"
举个title属性值的例子:<p v-bind:title="title1">鼠标移上去,停顿几秒</p>
Vue还提供了一种简写方式:
<p :title="title2">鼠标移上去,停顿几秒</p>
完整案例:
<div id="app">
<p v-bind:title="title1">
鼠标移上去,停顿几秒看看
</p>
<!-- 可以缩写 -->
<p :title="title2">
鼠标移上去,停顿几秒看看
</p>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
title1: '我是一个标题1',
title2: '我是一个标题2'
}
},
})
</script>
2.布尔类型属性
布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上,如果 btnDisabled
的值是 null
、undefined
或 false
, disabled
attribute直接不会出现在 <button>
中
<button :disabled="btnDisabled">按钮</button>
当btnDisabled
为真值或一个空字符串 (即 <button disabled="">
) 时,元素会包含这个 disabled
attribute。而当其为其他假值时 attribute 将被忽略。
3.动态绑定多个值
如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
通过不带参数的 v-bind
,你可以将它们绑定到单个元素上:
<div v-bind="objectOfAttrs"></div>
案例:
<div id="app">
<div v-bind="objectOfAttrs">F12查看我的源码</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
objectOfAttrs: {
id: 'container',
class: 'wrapper'
}
}
},
})
</script>
效果:
扩展:JavaScript 表达式
这边贴一下官方的文档:https://cn.vuejs.org/guide/essentials/template-syntax.html#using-javascript-expressions
对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中
案例:
<div id="app">{{num + 1}}</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
num: 3
}
},
})
</script>
结果是4
每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return
后面。
下面的例子都是无效的:
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
更多参考:https://cn.vuejs.org/guide/essentials/template-syntax.html#using-javascript-expressions
2.3.条件渲染(v-if)
-
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。 -
v-else
相当于为v-if
添加一个“else 区块”- 使用
v-else
元素必须跟在一个v-if
或者v-else-if
元素后面,否则它将不会被识别
- 使用
-
v-else-if
提供的是相应于v-if
的“else if 区块”- 使用
v-else-if
的元素必须紧跟在一个v-if
或一个v-else-if
元素后面
- 使用
举个例子:
<div id="app">
<div v-if="str === 'A'"> A </div>
<div v-else-if="str === 'B'"> B </div>
<div v-else-if="str === 'C'"> C </div>
<div v-else> other </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
str: 'D'
}
},
});
</script>
输出:other
Vue3新增了v-show:https://cn.vuejs.org/guide/essentials/conditional.html#v-show
v-if
是惰性渲染的:如果在初次渲染时条件值为 false,元素是不存在的。
v-show
是通过css属性display
控制元素显示,无论初始条件如何,始终会被渲染。总的来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
2.4.列表渲染(v-for)
同时使用 v-if
和 v-for
是不推荐的,因为这样二者的优先级不明显(Vue2和Vue3两者优先级恰恰相反)
1.遍历数组
v-for
指令基于一个数组来渲染一个列表。 v-for
指令需要使用item in items
形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名(用item of items
迭代遍历也行)【items是一个数组,而不能是一个方法(可以是计算属性)】
eg:
<p v-for="item in items"> {{ item }} </p>
示例:
<!-- div#app -->
<div id="app">
<!--快速生成: ul>li*2 -->
<ul>
<!-- vfor -->
<li v-for="item in items">
{{ item }}
</li>
</ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
items: ['狮子头', '韭菜', '鸡腿']
}
},
})
</script>
效果:
2.第二参数
还可以(value,index)
的方式遍历:
<!-- div#app -->
<div id="app">
<ul>
<li v-for="(value,index) in items">
{{index}}.{{value}}
</li>
</ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
items: ['狮子头', '韭菜', '鸡腿'],
}
},
})
</script>
效果:
3.遍历对象
举个遍历小明个人信息的案例:
<div id="app">
<ul>
<li v-for="(value,key,index) in items">
{{index}}.{{key}}:{{value}}
</li>
</ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
items:{
'name':'小明',
'age':'32',
'gender':'男'
}
}
},
})
</script>
效果:
如果你直接遍历,则只会输出value值。我们只修改上面代码的html部分:
<div id="app">
<ul>
<li v-for="value in items">
{{value}}
</li>
</ul>
</div>
效果:
如果想要获取key和value,可以写2个参数:v-for="(value,key) in items
<div id="app">
<ul>
<li v-for="(value,key) in items">
{{key}}:{{value}}
</li>
</ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
items: {
'name': '小明',
'age': '32',
'gender': '男'
}
}
},
})
</script>
效果:
4.对象列表★
来个实际的案例,v-if + v-for
:遍历班上的同学,并显示他们的个人信息:
<div id="app">
<p v-if="models.length==0">该班级没有学生</p>
<ul v-else>
<li v-for="model in models" :key="model.id">
{{ model.id }}.{{model.name}}.{{model.age}}.{{model.gender}}
</li>
</ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app', data() {
return {
models: [
{ 'id': 1, 'name': '小明', 'age': '32', 'gender': '男' },
{ 'id': 2, 'name': '小华', 'age': '22', 'gender': '男' },
{ 'id': 3, 'name': '小花', 'age': '28', 'gender': '女' }
]
}
},
})
</script>
效果:
PS:如果models的数值是空数组,就会显示:该班级没有学生
4.总结扩展
如果遍历的是列表,就两个参数,第1个是value值,第2个是index。
如果遍历的是对象,第1个参数是value值,第2个是key,第3个是index。
为了v-for
有更高的渲染性能,需要给 Vue 一个提示,需要为每项提供一个唯一 key
属性(DOM中没有key属性,是给vue用的)eg:
建议尽可能在使用
v-for
时提供key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
<!-- 当你使用 <template v-for> 时,key 应该被放置在这个 <template> 容器上: -->
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
key
并不仅与 v-for
特别关联,它还具有其它用途
key
绑定的值期望是一个基础类型的值,例如字符串或 number类型。不要用对象作为v-for
的 key
更多用法:https://cn.vuejs.org/guide/essentials/list.html#displaying-filtered-sorted-results
key文档:https://cn.vuejs.org/guide/essentials/list.html#maintaining-state-with-key
2.5.事件处理(v-on)
官方文档:https://cn.vuejs.org/guide/essentials/event-handling.htm
1.简单调用
先来个简单案例:单击后调用show方法:v-on:click="show"
完整写法:<div id="app"><button v-on:click="show">点我弹框</button></div>
简写用法:<div id="app"><button @click="show">点我弹框</button></div>
const app = new Vue({
el: '#app',
methods: {
show() {
alert('v-on test');
}
},
});
效果:
2.按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键,Vue 为一些常用的按键提供了别名:
.enter
.tab
.delete
(捕获“Delete”和“Backspace”两个按键).esc
.space
.up
.down
.left
.right
系统按键修饰符:
系统按键修饰符和常规按键不同。与
keyup
事件一起使用时,该按键必须在事件发出时处于按下状态。换句话说,keyup.ctrl
只会在你仍然按住ctrl
但松开了另一个键时被触发。若你单独松开ctrl
键将不会触发。
.ctrl
.alt
.shift
.meta
:微软的Windows 键、苹果的Command 键
来个案例:
Alt+Enter键触发事件:@keyup.alt.enter="xxx"
,Ctrl+Click按键触发:@click.ctrl="xxx"
<div id="app">
<input type="text" @keyup.alt.enter="show('Alt + Enter')" value="Alt + Enter 试试?">
<button @click.ctrl="show('点击 + Ctrl')">Ctrl+单击试试?</button>
<!-- @keyup.ctrl.click这样写是没用的 -->
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
methods: {
show(msg) { alert(msg); }
},
});
</script>
.exact 修饰符,允许控制触发一个事件所需的确定组合的系统按键修饰符。 ==> 就是只按下了什么键
<!-- 当按下 Ctrl 时,即使同时按下 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
这些修饰符将处理程序限定为由特定鼠标按键触发的事件
3.事件修饰符(了解)
专注于数据逻辑而不用去处理 DOM 事件的细节,Vue 为 v-on
提供了事件修饰符。
事件修饰符官方文档:https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers
修饰符是用 .
表示的指令后缀,包含以下这些:
使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。eg:使用
@click.prevent.self
会阻止元素及其子元素的所有点击事件的默认行为,而@click.self.prevent
则只会阻止对元素本身的点击事件的默认行为。
.stop
.prevent
.self
.capture
.once
.passive
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
.capture
、.once
和 .passive
修饰符与原生 addEventListener
事件相对应:
.passive
修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
2.6.表单绑定(v-model)
可以用 v-model 指令在表单 <input>
、 <textarea>
及 <select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素
v-model
会忽略任何表单元素上初始的value
、checked
或selected
attribute。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 中使用响应式系统的 API来声明该初始值。
看个简单v-model的案例:(v-model后面的item要先定义下)
<!-- div#app>input -->
<div id="app">
<input v-model="item" type="text" @keyup.enter="addItem" />
<button @click="addItem">按钮添加</button>
<div v-if="items.length==0">
食堂里暂时没有菜了
</div>
<!-- v-if尽量不和v-for嵌套使用 -->
<div v-else>
<p v-for="name in items">
{{ name }}
</p>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
item: '', // 变量要定义一下
items: ["狮子头", "韭菜", "鸡腿"]
}
},
methods: {
addItem() {
if (this.item == '') { return; }
this.items.push(this.item); // 数组中加一个值
this.item = ''; // 清空文本框内容
}
},
})
</script>
效果:在文本框输入内容直接回车,或者按后面的按钮,就可以添加内容
更多可以查看文档:https://cn.vuejs.org/guide/essentials/forms.html
2.7.样式绑定(:class)
主要是Class
和Style
,平时class用的比较多,Style如果要用可以直接绑定到一个样式对象(比较直观)
代码:
:class="{类名:bool}
、
1.简单案例
先来个简单案例:首先列出学生列表,然后点到哪个学生,哪个学生名字变红
:class="{active:selectedStudent==name}
:设置一个类active
,当变量selectedStudent
和当前name
相同的时候生效
@click="selectedStudent=name
:当li
被单击的时候,把name
赋值给selectedStudent
<div id="app">
<div v-if="students.length==0">这个班级还没有学生</div>
<div v-else>
<ul>
<li v-for="name in students" :key="name" :class="{active:selectedStudent==name}"
@click="selectedStudent=name">
{{ name }}
</li>
</ul>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
student: '', // v-model的变量
selectedStudent: '',// 初始化
students: ['张三', '李四', '王二', '赵五', '老六']
}
},
})
</script>
点了老六一下的效果:
2.多个class值
你可以在对象中写多个字段来操作多个 class。此外,:class
指令也可以和一般的 class
attribute 共存。eg:
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
如果isActive是true,hasError是false,最后显示出来就是这样的:
<div class="static active"></div>
3.Sytle案例
列出学生列表,然后点到哪个学生,哪个学生背景变黄
background-color
这种有-
的在js中容易有问题,所以需要'xx'
引号包裹,或者写成backgroundColor
驼峰命名的样子,然后Vue会帮我们转换
<div id="app">
<div v-if="students.length==0">这个班级还没有学生</div>
<div v-else>
<ul>
<!-- background-color这种有-的在js中容易有问题,所以需要引号包裹,或者写成backgroundColor驼峰命名的样子,然后Vue会帮我们转换 -->
<!-- @click="selectedStudent=name:当li被单击的时候,把name赋值给selectedStudent -->
<li v-for="name in students" :key="name"
:style="{'background-color':selectedStudent==name?'yellow':'transparent'}"
@click="selectedStudent=name">
{{ name }}
</li>
</ul>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
student: '', // v-model的变量
selectedStudent: '',// 一定要先初始化
students: ['张三', '李四', '王二', '赵五', '老六']
}
},
})
</script>
更多参考官方文档:https://cn.vuejs.org/guide/essentials/class-and-style.html
2.8.计算属性(computed)
对于任何复杂逻辑,你都应当使用计算属性
1.表达式实现
先看一个简单案例引入一下:现在有个数组,里面数据有点错乱,需要处理后显示
这个案例没有使用计算属性,是表达式的方法实现的
split(',')
:以,
进行字符串分割,结果返回一个数组reverse()
:把分割后的数组翻转下join('-')
:把翻转后的数组重新以-连接为一个字符串
<div id="app">
<ul>
<li v-for="str in students" :key="str">
{{ str.split(',').reverse().join('-') }}
</li>
</ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
students: ['男,23,张三,1', '男,22,李四,2', '女,30,王二,3']
}
},
});
</script>
效果:
2.计算属性改写★
这种明显就违背了Vue的初衷,表达式明显过于复杂,而且这样效率也低(数据量大的时候还会有性能瓶颈)下面改写一下:
<div id="app">
<ul>
<li v-for="str in updateStudents" :key="str">
{{ str }}
</li>
</ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
students: ['男,23,张三,1', '男,22,李四,2', '女,30,王二,3']
}
},
computed: {
updateStudents() {
return this.students.map(s => s.split(',').reverse().join('-'));
}
},
});
</script>
在Vue.js中,计算属性的使用方式是像数据属性一样使用,而不是作为函数调用
PS:不能通过
{{ newStr(str) }}
的方式来调用计算属性
3.计算属性 vs 方法
计算属性在数据不变的情况下,是有缓存的,能够提升效率。上面方法也可以用函数改写,但是更推荐计算属性的方式:
两者逻辑类似,就是少了缓存(计算属性值会基于其响应式依赖被缓存,它仅会在其响应式依赖更新时才重新计算)
<div id="app">
<ul>
<li v-for="str in students" :key="str">
{{ newStr(str) }}
</li>
</ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
students: ['男,23,张三,1', '男,22,李四,2', '女,30,王二,3']
}
},
methods: {
newStr(str) {
return str.split(',').reverse().join('-');
}
},
});
</script>
更多计算属性可以参考官方文档:https://cn.vuejs.org/guide/essentials/computed.html
2.9.侦听器(watch)
我们用一个案例来看下watch的一些特性:
PS:真实使用的时候
{{studentCount}}
不会通过这种方式实现,直接写{{students.length}}
即可实时更新
1.watch不立刻执行的特性
<div id="app">
<input v-model="student" type="text" @keyup.enter="addStudent" />
<p>{{studentCount}}</p>
<ul>
<li v-for="(item,index) in students" :key="index">
{{index+1}}.{{ item }}
</li>
</ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
student: '', // v-model初始化
studentCount: 0, // 初始化
students: ['张三', '李四', '露西', '王五']
}
},
methods: {
addStudent() {
this.students.push(this.student);
this.student = '';
}
},
watch: {
// 不加特殊声明,变化之后才会执行
students(newValue, oldValue) {
this.studentCount = newValue.length;
}
},
});
</script>
你会发现刚运行的时候并没有统计学生数量
这是因为watch
不特殊处理的话只会在数据改变的时候触发回调函数,现在我们新增一个学生看下效果:
数据一改变,立马就更新了
2.让watch立刻执行
如果你想立刻执行,就用watch-option的方式来指定一下immediate:true
,代码和上面一样,就watch变下:
watch: {
students: {
immediate: true,
// deep: true, // 一般不启用,影响性能(特殊情况可以使用)
handler(newValue, oldValue) {
this.studentCount = newValue.length;
}
}
},
这样一打开就自动计算好了:
3.深层侦听器
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。
用watch-option的方式来指定一下
deep: true
(数据嵌套太深默认的watch是检测不到的,这时候你如果要侦听就开下,要考虑性能)
更多参考官方文档:https://cn.vuejs.org/guide/essentials/watchers.html
★ 计算属性 VS 侦听器 ★
-
计算属性场景:多个值的改变影响一个值
- 能用computed实现的,全部使用computed,不去使用watch
-
侦听器的场景:一个值的改变影响多个值
- 侦听器提供了通用方法,适合执行异步操作、较大开销的操作
举一个官方的侦听器案例:
<div id="watch-example">
<p>
提出一个问题,回答 yes or no
<input v-model="question">
</p>
<!-- 提示语 -->
<p>{{ answer }}</p>
</div>
<script src="../assets/vue2.js"></script>
<script src="../assets/axios.1.3.6.js"></script>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.js"></script> -->
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: '提出一个问题,以英文的问号结尾'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = '稍等,我去问下,不要打字了哈~';
this.getAnswer();
}
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') == -1) {
this.answer = '请在最后加一个英文的?';
return
}
this.answer = '思考ing...'
console.log(this);
var vm = this;
// axios里面的this就不是vm了
axios.get('https://yesno.wtf/api')
.then(function (response) {
console.log(response.data)
console.log(this);
vm.answer = response.data.answer;
})
.catch(function (error) {
vm.answer = '没法访问API,错误信息:' + error;
})
}
}
})
</script>
2.10.生命周期
Vue2和Vue3生命周期的钩子其实是有变化的,但是我们经常使用的没什么变换,这边贴一下Vu3的生命周期
Vue2和Vue3这块是大体一样的:
beforeCreate > created > beforeMount > mounted > beforeUpdate > updated
(销毁有些变化)
跑个测试案例看下:
<div id="app">
{{testData}}
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
testData: '初始化数据', // 初始化
}
},
beforeCreate() { console.log('beforeCreate'); },
// 【常用】组件实例已创建,尚未挂载,dom不存在
created() { console.log('created ' + this.$el); },
beforeMount() { console.log('beforeMount'); },
// 【常用】挂载结束,dom已经存在
mounted() {
console.log('mounted ' + this.$el);
setTimeout(() => {
this.testData = '我被修改了';
}, 2000);
},
beforeUpdate() { console.log('beforeUpdate'); },
updated() { console.log('updated'); }
});
</script>
执行效果:created的时候没有dom,mounted的时候已经有dom了
如果不进行数据更新,下面两个方法是不会执行的:这个模拟了一个2s延迟的更新,于是就触发了
应用场景
- beforecreate // 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
- created // 组件初始化完毕,各种数据可以使用,常用于异步数据获取
- beforeMounted // 未执行染、更新,dom未创建
- mounted // 初始化(挂载)结束,dom已创建,可用于获取访问数据和dom元素
- beforeupdate // 更新前,可用于获取更新前各种状态
- updated // 更新后,所有状态已是最新
- BeforeUnmount // 销毁前,可用于一些定时器或订阅的取消(组件还在)
- Unmounted // 组件已销毁,作用同上(组件不在了)
更多参考官方文档:https://cn.vuejs.org/guide/essentials/lifecycle.html
生命周期钩子:https://cn.vuejs.org/api/composition-api-lifecycle.html
2.11.组件基础
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
全局注册组件:Vue.component(name, options)
Vue.component('my-component-name', {
// ... options ...
})
1.子组件中的局部变量案例
我们先写个普通的功能:点击按钮数字+1
<div id="app">
<div>
{{count}}
<button @click="count++">点我+1</button>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
count: 0
}
},
})
</script>
效果:单击一下数字就会+1
下面把这个案例改写成组件的方式(count这个变量主要是在组件中使用,那就可以单独封装在组件里面)
<div id="app">
<my-button></my-button>
</div>
<script src="../assets/vue2.js"></script>
<script>
// 组件的名称最好是这种中划线的方式Vue(开头大写)
Vue.component('my-button', {
data() {
return {
count: 0 // 这个变量和该组件强关联,定义在组件里面比较合适
}
},
// template里面的模板最外面用个单独的div比较好
template: `
<div>
{{count}}
<button @click="count++">点我+1</button>
</div>
`
}
);
// 组件定义后再实例化,如果在前面实例化,页面会不正常
const app = new Vue({ el: '#app' });
</script>
几个注意事项:
- 组件的名称最好是中划线的方式
Vue.component
(V开头大写,c开头小写)
template
里面的模板字符串,最外层要加个父标签/根标签,比如用个单独的div
效果:单击一下数字就会+1
如果你页面放好几个组件,也是可以的,而且相互不干扰:
<div id="app">
<my-button></my-button>
<my-button></my-button>
<my-button></my-button>
</div>
任意点击后效果:
2.父组件传递数据给子组件
一个展示学生列表的案例,这个是改成组件之前的代码:
<div id="app">
<li v-for="name in students" :key="name">
{{ name }}
</li>
</div>
<script src="../assets/vue2.js"></script>
<script>
// 组件定义后再实例化,如果在前面实例化,页面会不正常
const app = new Vue({
el: '#app',
data() {
return {
students: ["张三", "李四", "王二"]
}
}
});
</script>
输出:
改成组件的方式(把students的数据传递到组件里面)
<div id="app">
<!-- 两个变量名不一定要完全一样,这边只是方便理解 -->
<my-list :students="students"></my-list>
</div>
<script src="../assets/vue2.js"></script>
<script>
Vue.component('my-list', {
props: { // 对传过来的students进行约束和默认值设置(也可以直接props:['students'])
students: {
type: Array, // 设置students的数据类型是数组
default: [] // 默认值是空列表
},
},
template:
`<ul>
<li v-for="name in students" :key="name">
{{ name }}
</li>
</ul>`
})
// 组件定义后再实例化,如果在前面实例化,页面会不正常
const app = new Vue({
el: '#app',
data() {
return {
students: ["张三", "李四", "王二"]
}
},
})
</script>
输出效果和上面一样:
3.子组件传递数据给父组件
比如现在要输入一个学生名称(子组件中的变量)然后数据(students)是在父组件中的,那就需要把每次新增的student传递给父组件
案例改组件前的实现:两块内容,一个是文本框用来新增学生名、另一个是列表来列举学生
<div id="app">
<input v-model="student" type="text" @keyup.enter="addStudent" />
<ul>
<li v-for="name in students" :key="name">
{{ name }}
</li>
</ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
student: '',
students: ['张三', '李四', '王二']
}
},
methods: {
addStudent() {
this.students.push(this.student);
this.student = '';
}
},
});
</script>
改成组件的方式:
$emit
:触发当前实例上的事件(附加参数都会传给监听器回调)【记不得可以想提交是submit,事件提交就是emit】
list部分和上面一样,重点说下input的事件监听:如果不传参可以不自定义事件,传参就可以这样写:
<!-- `add-student`是自定义事件,当触发就会调用父容器中的`addStudent`方法 -->
<enter-input @add-student="addStudent"></enter-input>
自定义组件:
Vue.component('enter-input', {
data() {
return {
student: ''
}
},
methods: {
addStudent() {
// 注意:事件名称定义时不要有大写字母出现
// add-student是自定义事件,this.student是传给父容器addStudent的值
this.$emit('add-student', this.student);
this.student = ''; // 清空当前姓名
}
},
template:
`<input v-model="student" type="text" @keyup.enter="addStudent" />`
})
完整代码:
<div id="app">
<!-- 监听组件事件 -->
<enter-input @add-student="addStudent"></enter-input>
<my-list :students="students"></my-list>
</div>
<script src="../assets/vue2.js"></script>
<script>
Vue.component('enter-input', {
data() {
return {
student: ''
}
},
methods: {
addStudent() {
// 注意:事件名称定义时不要有大写字母出现
// add-student是自定义事件,this.student是传给addStudent的值
this.$emit('add-student', this.student);
this.student = ''; // 清空当前姓名
}
},
template:
`<input v-model="student" type="text" @keyup.enter="addStudent" />`
})
Vue.component('my-list', {
props: {
students: {
type: Array,
default: []
},
},
template:
`<ul>
<li v-for="name in students" :key="name">
{{ name }}
</li>
</ul>`
})
const app = new Vue({
el: '#app',
data() {
return {
students: ['张三', '李四', '王二']
}
},
methods: {
addStudent(student) {
this.students.push(student);
}
},
});
</script>
4.自定义组件使用v-model
先回顾下vue的v-model怎么使用的:
<div id="app">
<div><input v-model="name" type="text" /></div>
<div>{{name}}</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
name: '小明'
}
},
})
</script>
当改变文本框内容时,文本内容立刻改变:
双向绑定的这个v-model其实只是一个语法糖,背后相当于这样的:
PS:为什么event要加个
$
==> 你不加他到底是字符串,还是vdata中的变量呢?vue就不知道了
<input type="text" value="name" @input="name=$event.target.value">
如果我们自定义的组件,在绑定v-model的时候是不会双向绑定的,需要自己处理下(value
和input
)
-
自定义组件可以直接写
v-model="name"
-
在自定义组件时设置下
props: ['name']
-
方法里面添加一个变量修改的返回方法:
this.$emit('input', e.target.value);
-
template里面代码:
<input :value="name" @input="onInput" type="text" />
完整代码示意:
<div id="app">
<my-input v-model="name"></my-input>
<div>{{name}}</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
Vue.component('my-input',
{
props: ['name'],
methods: {
onInput(e) {
this.$emit('input', e.target.value);
}
},
template:
`<div>
<input :value="name" @input="onInput" type="text" />
</div>`
})
const app = new Vue({
el: '#app',
data() {
return {
name: '小明'
}
},
})
</script>
当改变文本框内容时,文本内容立刻改变:
5.插槽分发内容(含多个)
对于插槽分发,vue提供了 <slot>
元素来给组件便捷的传递内容
这个类比弹框提示,弹框这个组件是子组件封装好的,内容是由父组件传过来的(父组件内容显示在子组件中)
5.1.封装前的弹框
先写一个弹框显示和关闭的案例:
.message-box {
width: 300px;
padding: 10px 20px;
color: white;
background: rgb(2, 214, 133);
}
.message-box-close {
float: right;
color: white;
}
代码部分:
<div id="app">
<div class="message-box" v-show="isShow">
{{msg}}
<span class="message-box-close" @click="isShow=false">X</span>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
isShow: true, // 是否显示弹框
msg: '操作成功!' // 弹框提示内容
}
},
})
</script>
打开后弹框默认是显示的,点击X后就关闭:
5.2.子组件版弹框
下面我们进行改写下:
PS:不能直接在子组件里面这样写:
<span class="message-box-close" @click="show=false">X</span>
这样容易导致父子组件show变量不一致,控制台也会警告提示:只要父组件重新渲染,该值就会被覆盖
,所以还是要让父组件修改
子组件中的$emit('close',false)
是触发自定义事件close,并把false传递给事件调用的函数。之前我们是一个单独的函数然后用参数接收的,这边父组件可以通过$event
来接收传递的值
<div id="app">
<message-box :show="isShow" @close="isShow=$event">{{msg}}</message-box>
</div>
<script src="../assets/vue2.js"></script>
<script>
Vue.component('message-box', {
props: ['show'],
template: `
<div class="message-box" v-show="show">
<!-- 相当于占位符 -->
<slot></slot>
<span class="message-box-close" @click="$emit('close',false)">X</span>
</div>`
})
const app = new Vue({
el: '#app',
data() {
return {
isShow: true, // 是否显示弹框
msg: '操作成功!' // 弹框提示内容
}
},
})
</script>
结果和上面一样,默认是显示的,点击X后就关闭:
5.3.精简具名插槽
Vue提供了一种简化的方式:xxx.sync
来实现(相当于帮你写了自定义事件,事件的名称是固定的 @update:xxx
)
子组件只需要微调:<message-box :show.sync="isShow">{{msg}}</message-box>
Template也微调下:@click="$emit('update:show',false)"
完整代码如下:
<div id="app">
<message-box :show.sync="isShow">{{msg}}</message-box>
</div>
<script src="../assets/vue2.js"></script>
<script>
Vue.component('message-box', {
props: ['show'],
template: `
<div class="message-box" v-show="show">
<!-- 相当于占位符 -->
<slot></slot>
<span class="message-box-close" @click="$emit('update:show',false)">X</span>
</div>`
})
const app = new Vue({
el: '#app',
data() {
return {
isShow: true, // 是否显示弹框
msg: '操作成功!' // 弹框提示内容
}
},
})
</script>
5.4.多位插槽案例
如果一个组件中需要占位多个坑位,可以在一个 <template>
元素上使用 v-slot
指令
PS:一个不带
name
的<slot>
默认的名字是default
还是上面的例子,如果我们现在除了提示语外,还要有个提示的标题,封装前写法如下:
<div id="app">
<div class="message-box" v-show="isShow">
{{msgTitle}}
<strong>{{msgContent}}</strong>
<span class="message-box-close" @click="isShow=false">X</span>
</div>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
isShow: true,
msgTitle: '温馨提示:',
msgContent: '操作成功'
}
},
})
</script>
样式微调如下:
.message-box {
width: 300px;
padding: 10px 20px;
color: white;
background: rgb(2, 214, 133);
}
.message-box-close {
float: right;
color: white;
}
打开页面进行提示,单击x则被关闭
封装成子组件则和上面有些不同,子组件里面要这样写:template v-slot:名字
PS:要为具名插槽传入内容,我们需要使用一个含
v-slot
指令的<template>
元素,并将目标插槽的名字传给该指令
<message-box :show.sync="isShow">
<template v-slot:title>{{msgTitle}}</template>
<template v-slot:content>{{msgContent}}</template>
</message-box>
也可以简写:v-slot
有对应的简写 #
,因此 <template v-slot:title>
可以简写为 <template #title>
<message-box :show.sync="isShow">
<template #title>{{msgTitle}}</template>
<template #content>{{msgContent}}</template>
</message-box>
然后Template里面是这样的:
<div class="message-box" v-show="show">
<slot name="title"></slot>
<strong>
<slot name="content"></slot>
</strong>
<span class="message-box-close" @click="$emit('update:show',false)">X</span>
</div>
代码如下:
<div id="app">
<message-box :show.sync="isShow">
<template v-slot:title>{{msgTitle}}</template>
<template v-slot:content>{{msgContent}}</template>
</message-box>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
Vue.component('message-box', {
props: ['show'],
template:
`<div class="message-box" v-show="show">
<slot name="title"></slot>
<strong>
<slot name="content"></slot>
</strong>
<span class="message-box-close" @click="$emit('update:show',false)">X</span>
</div>`
})
const app = new Vue({
el: '#app',
data() {
return {
isShow: true,
msgTitle: '温馨提示:',
msgContent: '操作成功'
}
},
})
</script>
6.组件知识概述
Q:谈一下你对Vue组件化的理解?
A:组件化是Vue的精髓,Vue应用就是由一个个组件构成的。Vue的组件化涉及到的内容非常多,可以从以下几点进行阐述:
-
组件定义:组件是可复用的 Vue 实例,准确讲它们是VueComponent的实例,继承自Vue
-
组件优点:组件化可以增加代码的复用性、可维护性和可测试性
-
使用场景:
- 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等
- 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件
- 页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件
-
组件应用:
- 定义:
Vue.component()
,components
选项,sfc
- 分类:有状态组件,
functional
,abstract
- 通信:
props
,$emit()
/$on()
,provide
/inject
- 不考虑耦合性可以使用:
$children
/$parent
/$root
/$attrs
/$listeners
- 不考虑耦合性可以使用:
- 内容分发:
<slot>
,<template>
,v-slot - 使用及优化:
is
,keep-alive
,异步组件
- 定义:
-
组件本质:组件的本质是产生虚拟DOM
- Vue中的组件经历如下过程:组件配置 =>
VueComponent
实例 =>render()
转换为方法 =>Virtual DOM
=>DOM
- Vue中的组件经历如下过程:组件配置 =>
3.一个vue3的案例
如果还是这种混合开发的风格,Vu3和Vue2差不多,就是引用的JS变成了Vue3的,然后创建语句变成了下面这个:
const app = Vue.createApp({}) // 创建app
app.mount('#app') // 挂载
来个案例:通过两个按钮来控制count数值的加和减
<div id="app">
<h3>计数:{{count}}</h3>
<button @click="addCount">++</button>
<button @click="subCount">--</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- <script src="../assets/vue3.js"></script> -->
<script>
const app = Vue.createApp({ // 创建app对象
data() {
return {
count: 0
}
},
methods: {
addCount() {
this.count++;
},
subCount() {
this.count--;
}
},
})
app.mount('#app') // 挂载
</script>
Vue基础部分先到这,后面就不是这种传统的混合开发了,以上内容只是为了之前混合开发的朋友做过渡所写,Vue3内容请看下篇(完)