Appearance
进阶
1 条件渲染
条件渲染:
v-if
写法:
(1).v-if="表达式"
(2).v-else-if="表达式"
(3).v-else="表达式"
适用于:切换频率较低的场景。
特点:不展示的 DOM 元素直接被移除。
注意: v-if 可以和:v-else-if、v-else 一起使用,但要求结构不能被"“打断”。
v-show
写法: v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉
备注:使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。
html<body> <div id="root"> <h2>当前N的值是:{{n}}</h2> <button @click="n++">点我n+1</button> <!-- 使用v-show做条件渲染 条件为假时隐藏元素display:none--> <!-- <h2 v-show="true">下午好,大西瓜</h2> --> <!-- <h2 v-show="1===1">下午好,大西瓜</h2> --> <!-- 使用v-if做条件渲染 条件为假时,移除元素自身--> <!-- <h2 v-if="true">下午好,大西瓜</h2> <h2 v-if="1===2">下午好,大西瓜</h2> --> <!-- v-else he v-else-if --> <!-- <h2 v-if="n===1">angular</h2> <h2 v-else-if="n===2">React</h2> <h2 v-else>下午好,大西瓜</h2> --> <!-- template与v-if配合使用 --> <template v-if="n===3"> <h2>下午好,大西瓜</h2> <h2>Vue学习中...</h2> <h2>悄悄看烤盐的小姐姐</h2> </template> </div> <script> Vue.config.productionTip = false new Vue({ el: '#root', data: { n: 1, }, }) </script> </body>
2. 列表渲染
2.1 基本列表
v-for 指令:
- 用于展示列表数据
- 语法: v-for="(item,index) in xxx" :key="yyy"
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
html
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,i) of persons" :key="i">{{p.name}}----{{p.age}}</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,key) of car" :key="key">{{key}}----{{value}}</li>
</ul>
<!-- 遍历字符串 -->
<h2>遍历字符串</h2>
<ul>
<li v-for="(value,index) of str" :key="index">{{index}}----{{value}}</li>
</ul>
<!-- 遍历指定次数 -->
<h2>遍历指定次数</h2>
<ul>
<li v-for="(value,index) of 5" :key="index">{{index}}----{{value}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: 001, name: '张三', age: 18 },
{ id: 002, name: '李四', age: 21 },
{ id: 003, name: '王五', age: 20 },
],
car: {
brand: '奥迪',
model: 'RS 7',
price: 1600000,
},
str: '冰冻大西瓜',
},
})
</script>
</body>
2.2 Key 的原理
面试题: react. vue 中的 key 有什么作用?(key 的内部原理)
虚拟 DOM 中 key 的作用:
key 是虚拟 DON 对象的标识,当状态中的数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后 Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较,比较规则如下:
对比规则:
(1).旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
① 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM !
② 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM.
(2).旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
创建新的真实 DOM,随后渲染到到页面。
用 index 作为 key 可能会引发的问题:
① 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实 DOM 更新==〉界面效果没问题,但效率低。
② 如果结构中还包含输入类的 DOM:
会产生错误 DOM 更新==>界面有问题。
开发中如何选择 key? :
① 最好使用每条数据的唯一标识作为 key,比如 id、手机号、身份证号、学号等唯一值。
② 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。
html
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">点我添加老刘</button>
<ul>
<li v-for="(p,i) of persons" :key="p.id">
{{p.name}}----{{p.age}}
<input type="text" />
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: 001, name: '张三', age: 18 },
{ id: 002, name: '李四', age: 21 },
{ id: 003, name: '王五', age: 20 },
],
},
methods: {
add() {
const p = { id: 004, name: '老刘', age: 40 }
this.persons.unshift(p)
},
},
})
</script>
</body>
2.3 列表过滤
html
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<input type="text" v-model="keyworld" placeholder="请输入名字" />
<ul>
<li v-for="(p,i) of filPersons" :key="i">{{p.name}}----{{p.age}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyworld: '',
persons: [
{ id: 001, name: '马冬梅', age: 18 },
{ id: 002, name: '周冬雨', age: 21 },
{ id: 003, name: '周杰伦', age: 20 },
{ id: 004, name: '温兆伦', age: 20 },
],
// filPersons: [],
},
// 用watch实现
//#region
// watch: {
// keyworld: {
// immediate: true,
// handler(val) {
// this.filPersons = this.persons.filter((p) => {
// return p.name.indexOf(val) !== -1
// })
// },
// },
// },
//#endregion
computed: {
filPersons() {
return this.persons.filter(p => {
return p.name.indexOf(this.keyworld) !== -1
})
},
},
})
</script>
</body>
2.4 列表排序
html
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<input type="text" v-model="keyworld" placeholder="请输入名字" />
<button @click="sortType=2">年龄升序</button>
<button @click="sortType=1">年龄降序</button>
<button @click="sortType=0">原来顺序</button>
<ul>
<li v-for="(p,i) of filPersons" :key="p.id">{{p.name}}----{{p.age}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
keyworld: '',
sortType: 0, // 0为原序,1为降序,2为升序
persons: [
{ id: 001, name: '马冬梅', age: 18 },
{ id: 002, name: '周冬雨', age: 21 },
{ id: 003, name: '周杰伦', age: 20 },
{ id: 004, name: '温兆伦', age: 20 },
],
},
computed: {
filPersons() {
const arr = this.persons.filter(p => {
return p.name.indexOf(this.keyworld) !== -1
})
// 判断是否需要排序
if (this.sortType) {
arr.sort((p1, p2) => {
// sort()函数,后减前为降序,前减后为升序
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr
},
},
})
</script>
</body>
2.5 更新时的一个问题
html
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<button @click="updateMei">更新马冬梅信息</button>
<ul>
<li v-for="(p,i) of persons" :key="p.id">{{p.name}}----{{p.age}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: 001, name: '马冬梅', age: 18, gender: '女' },
{ id: 002, name: '周冬雨', age: 21, gender: '女' },
{ id: 003, name: '周杰伦', age: 20, gender: '男' },
{ id: 004, name: '温兆伦', age: 20, gender: '男' },
],
},
methods: {
updateMei() {
// this.persons[0].name = "马老师" // 有效
// this.persons[0].age = 50 // 有效
// this.persons[0].gender = "男" // 有效
// Vue无法检测到这样的修改
this.persons[0] = { id: 001, name: '马老师', age: 50, gender: '男' }
},
},
})
</script>
</body>
2.6 Vue 检测数据改变的原理-对象
html
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
<script>
// 阻止Vue启动时提示
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
address: '北京',
},
})
</script>
</body>
2.7 模拟一个数据监测
2.7.1 使用 setInterval()
html
<script>
let data = { name: '尚硅谷', address: '北京' }
let tmp = '尚硅谷'
setInterval(() => {
if (data.name !== tmp) {
console.log('name被改了')
tmp = data.name
}
}, 500)
</script>
这里如果不使用 tmp 变量而是在判断中直接写 data.name!=’尚硅谷’,则改变 data.name,每隔 500ms 就会调用方法
2.7.2 使用 Object.defineProperty()
html
<script>
let data = { name: '尚硅谷', address: '北京' }
Object.defineProperty(data, 'name', {
get() {
return data.name
},
set(val) {
data.name = val
},
})
</script>
直接在 data 中 defineProperty 会造成 get 和 set 的无限递归调用自身。
2.7.3 使用 Observer() 自定义方法
html
<script>
let data = { name: '尚硅谷', address: '北京' }
// 创建一个监视的实例对象,用于监视data属性的变化
const obs = new Observer(data)
console.log(obs)
function Observer(obj) {
// 汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
// 遍历
keys.forEach(k => {
Object.defineProperty(this, k, {
get() {
return obj[k]
},
set(val) {
obj[k] = val
},
})
})
}
</script>
这里仅仅模拟了一层,在 Vue 中更加完善,只要是对象,不管有几层都可以监视(使用递归)
2.8 Vue.set 的使用
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值。
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如
this.myObject.newProperty = 'hi'
)
注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
html
<body>
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr />
<h1>学生信息</h1>
<button @click="addgender">点我添加性别</button>
<h2>学生姓名:{{student.name}}</h2>
<h2>学生年龄:真实{{student.age.rage}},显示{{student.age.sAge}}</h2>
<h2 v-if="student.gender">性别:{{student.gender}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f,index) in student.friends">{{f.name}}--{{f.age}}</li>
</ul>
</div>
<script>
// 阻止Vue启动时提示
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
address: '北京',
student: {
name: 'tom',
age: {
rAge: 25,
sAge: 22,
},
friends: [
{ name: 'Jerry', age: 26 },
{ name: 'Tony', age: 24 },
],
},
},
methods: {
addgender() {
// 两种方式都可以
// Vue.set(this._data.student, "gender", "男")
this.$set(this.student, 'gender', '男')
},
},
})
</script>
</body>
2.9 Vue 检测数据改变的原理-数组
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()
、concat()
和 slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:
javascript
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
也可以利用 vm.$set()或者 Vue.set()去修改
html
<body>
<div id="root">
<h1>学生信息</h1>
<h2>学生姓名:{{student.name}}</h2>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in student.hoppy" :key="index">{{h}}</li>
</ul>
</div>
<script>
// 阻止Vue启动时提示
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
student: {
name: 'tom',
hoppy: ['篮球', '跑步', '自习'],
},
},
})
</script>
</body>
2.10 总结 Vue 数据检测
Vue 监视数据的原理:
vue 会监视 data 中所有层次的数据。
如何监测对象中的数据?
通过 setter 实现监视,且要在 new Vue 时就传入要监测的数据。
(1) 对象中后追加的属性,Vue 默认不做响应式处理
(2) 如需给后添加的属性做响应式,请使用如下 API:
Vue.set(target. propertyName/index, value)
或vm.$set(target.propertyName/index,value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1) 调用原生对应的方法对数组进行更新。
(2) 重新解析模板,进而更新页面。
在 Vue 修改数组中的某个元素一定要使用如下方法:
- 使用这些 API:
push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
Vue.set()
或者vm.$set()
- 使用这些 API:
特别注意: **Vue.set()
**或者**vm.set()
** 不能给 vm 或者 vm 的根数据对象(_data),添加属性!!!
3. 收集表单数据
收集表单数据:
若:<input type="text"/>
,则 v-model 收集的是 value 值,用户输入的就是 value 值。
若:<input type="radio"/>
,则 v-model 收集的是 value 值,且要给标签配置 value 值。
若:<input type="checkbox"/>
1.没有配置 input 的 value 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
2.配置 input 的 value 属性:
(1)v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
(2)v-model 的初始值是数组,那么收集的的就是 value 组成的数组
备注: v-model 的三个修饰符:
1.lazy:失去焦点再收集数据
2.number:输入字符串转为有效的数字
3.trim:输入首尾空格过滤
html
<body>
<div id="root">
<form @submit.prevent="submit">
账户:
<input type="text" v-model="userINfo.account" />
<br />
<br />
密码:
<input type="password" v-model="userINfo.password" />
<br />
<br />
性别: 男
<input type="radio" name="gender" v-model="userINfo.gender" value="male" />
女
<input type="radio" name="gender" v-model="userINfo.gender" value="female" />
<br />
<br />
年龄:
<input type="number" v-model.number="userINfo.age" />
<br />
<br />
爱好: 篮球
<input type="checkbox" v-model="userINfo.hobby" value="Basketball" />
台球
<input type="checkbox" v-model="userINfo.hobby" value="Billiards" />
自习
<input type="checkbox" v-model="userINfo.hobby" value="Learning" />
<br />
<br />
所属校区:
<select v-model="userINfo.city">
<option value="beijing">北京校区</option>
<option value="shanghai">上海校区</option>
<option value="shenzhen">深圳校区</option>
<option value="wuhan">武汉校区</option>
</select>
<br />
<br />
<!-- v-model.lazy 懒加载 输入框失去焦点后保存内容 -->
其他信息:
<textarea v-model.lazy="userINfo.other"></textarea>
<br />
<br />
<input type="checkbox" v-model="userINfo.agree" />
阅读并接受
<a href="http://www.atguigu.com">《用户协议》</a>
<br />
<br />
<button>提交</button>
</form>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
userINfo: {
account: '',
password: '',
gender: '',
age: 0,
hobby: [],
city: '',
other: '',
agree: false,
},
},
methods: {
submit() {
console.log(JSON.stringify(this.userINfo))
},
},
})
</script>
</body>
4. 过滤器
html
<body>
<div id="root">
<h2>显示格式化后的世界</h2>
<!-- 计算属性的实现 -->
<h3>现在是{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是{{getFmtTime()}}</h3>
<!-- 过滤器实现 -->
<h3>现在是{{time | timeFormater}}</h3>
<!-- 过滤器实现(传参) -->
<h3>现在是{{time | timeFormater("MM/DD/YYYY HH:mm:ss") }}</h3>
<h3>现在是{{time | timeFormater | mysplice}}</h3>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
time: new Date(),
},
// 计算属性的实现
computed: {
fmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
},
},
// methods实现
methods: {
getFmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
},
},
// 过滤器实现
filters: {
// Es6 默认形参特性
timeFormater(val, fmt = 'YYYY年MM月DD日 HH:mm:ss') {
return dayjs(val).format(fmt)
},
mysplice(val) {
return val.slice(0, 4)
},
},
})
</script>
</body>
5. 内置指令
已经学过的指令:
v-bind :单向绑定解析表达式,可简写为:XXX
v-model :双向数据绑定
v-for :遍历数组/对象/字符串
v-on :绑定事件监听,可简写为@
v-if :条件渲染(动态控制节点是否存存在)
v-else :条件渲染(动态控制节点是否存存在)
v-show :条件渲染(动态控制节点是否展示)
5.1 v-text 指令
v-text 指令:
作用:向其所在的节点中渲染文本内容。
与插值语法的区别:v-text 会替换掉节点中的内容,{{xx}}则不会。
推荐使用插值语法,更加灵活。
html
<body>
<div id="root">
<div>你好,{{name}}</div>
<div v-text="name">你好,</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '大西瓜',
},
})
</script>
</body>
5.2 v-html 指令
v-html 指令:
作用:向指定节点中渲染包含 html 结构的内容。
与插值语法的区别:
2.1 v-html 会替换掉节点中所有的内容,{{xx}}则不会。
2.2 v-html 可以识别 html 结构。
严重注意: v-html 有安全性问题!!!!
3.1 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。
3.2 一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!
html
<body>
<div id="root">
<strong>插值语法显示:</strong>
<div>你好,{{name}}</div>
<strong>v-text显示:</strong>
<div v-html="name">你好,</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '<h1>大西瓜</h1>',
},
})
</script>
</body>
5.3 v-cloak 指令
v-cloak 指令(没有值):
本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。
使用 css 配合 v-cloak 可以解决网速慢时页面展示出{{xxx}}的问题。
html
<script src="../js/vue.js"></script>
<style>
[v-cloak]{
display: none;
}
</style>
<title>v-cloak指令</title>
</head>
<body>
<div id="root">
<div v-cloak>你好,{{name}}</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: "#root",
data: {
name: "大西瓜"
}
})
</script>
</body>
5.4 v-once 指令
v-once 指令:
1.v-once 所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。
html
<body>
<div id="root">
<h2 v-once>初始化n的值是:{{n}}</h2>
<h2>当前n的值是:{{n}}</h2>
<button @click="n++">点我n++</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1,
},
})
</script>
</body>
5.5 v-pre 指令
v-pre 指令:
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
html
<body>
<div id="root">
<h2 v-pre>Vue其实很简单</h2>
<h2 v-pre>当前n的值是:{{n}}</h2>
<button v-pre @click="n++">点我n++</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1,
},
})
</script>
</body>