Vue框架使用小结
从三个部分复习:
- 基本使用、组件使用
- 高级特性
- Vuex和Vue-router使用
一、Vue使用
1.vue基本使用
vue基本使用不会还找什么工作?在这就梳理一下知识点
(1)指令插值
插值表达式 动态属性 v-html
<template>
<div>
<p>文本插值 {{message}}</p>
<p>JS 表达式 {{flag?'yes':'no'}}//(只能是表达式,不能是 js 语句)</p>
<p :id="dynamicId">动态属性 id</p>
<hr/>
<p v-html="rawHtml">
<span>有 xss 风险</span>
<span>【注意】使用v-html之后,将会覆盖子元素</span>
</p>
<!-- 其他常用指令后面讲 -->
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello vue',
flag: true,
rawHtml: '指令-原始html <b>加粗</b> <i>斜体</i>',
dynamicId: `id-${Date.now()}`
}
}
}
</script> (2)computed watch
computed有缓存,data不变,则不会重新计算<template>
<div>
<p>num {{num}}</p>
<p>double1 {{double1}}</p>
<input v-model="double2"/>
</div>
</template>
<script>
export default {
data() {
return {
num: 20
}
},
computed: {
double1() {
return this.num * 2
},
double2: {//double2是被双向绑定的 所以一定要设置get set两个方法
get() {
return this.num * 2
},
set(val) {
this.num = val/2
}
}
}
}
</script> watch 聆听引用类型的时候要深度聆听,而且拿不到引用类型的OldVal
使用handler进行深度聆听 可以监听到数组、对象的变化 不然无法聆听到引用类型内部的变化
//深度监听代码
watch:{
b:{
handler(val,oldVal){
return ''
},
deep:true//true 深度监听
}
} <template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
info: {
city: '北京'
}
}
},
watch: {
name(oldVal, val) {
// eslint-disable-next-line
console.log('watch name', oldVal, val)
// 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
// eslint-disable-next-line
console.log('watch info', oldVal, val)
// 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
},
deep: true // 深度监听
}
}
}
</script> (3)class和style
使用动态属性 使用驼峰式写法
<template>
<div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
<p :class="[black, yellow]">使用 class (数组)</p>
<p :style="styleData">使用 style</p>
</div>
</template>
<script>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc' // 转换为驼峰式
}
}
}
}
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style> (4)条件渲染
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: 'a'
}
}
}
</script> v-if v-else可以使用变量 也可以使用===表达式
注意区分v-if 和 v-show
v-if渲染的结果只有一个 但是v-show会渲染出所有条件,只不过把不符合条件的设为style="display:none"
如果是频繁的切换,那么v-show更加合适 一次性选择v-if就够用
(5)循环列表渲染
<template>
<div>
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
<p>遍历对象</p>
<ul>
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} - {{key}} - {{val.title}}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
//数据结构中,最好有id 方便使用key
listArr: [
{ id: 'a', title: '标题1' },
{ id: 'b', title: '标题2' },
{ id: 'c', title: '标题3' }
],
listObj: {
a: { title: '标题1' },
b: { title: '标题2' },
c: { title: '标题3' },
}
}
}
}
</script> 使用v-for也可以遍历对象
key很重要,但key不能乱写
注:v-for v-if不能一起使用!!!
(6)事件
<template>
<div>
<p>{{num}}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
//这里写了什么参数 下面接收就行了
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log('event', event, event.__proto__.constructor)
// event是MouseEvent 就是一个原生的 event 对象
// eslint-disable-next-line
console.log(event.target)
// eslint-disable-next-line
console.log(event.currentTarget)
// 注意,事件是被注册到当前元素的,和 React 不一样
this.num++
//一些结论
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
// eslint-disable-next-line
console.log(event.target)
this.num = this.num + val
},
loadHandler() {
// do some thing
}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener('load', this.loadHandler)
}
}
</script> event是一个原生的event对象,没有经过任何的修饰 并且,event的target和currentTarget是相同的
(记住 要和React做对比)
一些很方便的事件修饰符
还有很方便的按键修饰符
(7)表单
<template>
<div>
<p>输入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多个复选框 {{checkedNames}}</p>
<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>
<p>单选 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
</script> 表单使用考察的可能性不大 但是实际中真的很常用哈 记住表达都用v-model来双向绑定 2.组件通讯
首先,把实例代码放出来
父组件 index.vue
<template>
<div>
<Input @add="addHandler"/>//聆听add事件 并接收到子组件传来的this.title
<List :list="list" @delete="deleteHandler"/>
//在这两个子组件上 聆听两个事件
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
}
]
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
})
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id)
}
}
}
</script> 子组件input
<template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
//聆听click事件 触发addTitle
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: ''
}
},
methods: {
addTitle() {
// 向外触发add事件 并传递this.title
this.$emit('add', this.title)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
}
}
}
</script> 子组件List
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">删除</button>
//同理 触发deleteItem函数 并传递参数
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},//向外触发delete事件并传递参数
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
}
},
mounted() {
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script> (1)props $emit
父组件给子组件传值:
如上面实例中的index给List组件传递list这个数组——通过v-bind给子组件绑定属性,子组件通过props接收后使用
注:props接收数据有几种不同的写法
props:[list]//最简单的写法
props:{
list:{
type:Array,
default() {
return []
}
}
}//复杂一些 指定类型和默认值
props:{
list:Array
}//只指定类型 子组件给父组件传值:如上面实例 首先,要在父组件中聆听事件,并写methods函数 子组件发生点击这些简单事件时 ,会在事件处理程序中调用this.$emit( )向外触发事件
this.$emit('delete', id)//delete 父组件聆听的事件 id子组件给父组件传递的值 说白了,this.$emit有点类似事件冒泡,但它在冒泡途中,进行了一些包装,等到了父组件的时候,变了一个模样。它不能像冒泡那样自然进行,而是子组件上触发的事件,通过this.$emit去触发父组件上的另一个事件。父组件再调用对应的事件处理程序,就实现了——子组件改变父组件的值。
(2)兄弟组件通讯
兄弟组件通讯有两种思路:
一是之前在去哪儿网项目里用到的:https://blog.nowcoder.net/n/8dfaf3ba5eb847cea7cdcd9bcb40f06e
思路是:子组件1——>父组件——>子组件2
这里就copy一下之前写的——实现点击字母表 城市list就滚动到对应的数字
思路:对字母表组件绑定一个点击事件 父组件获得每次点击时的字母值 并将这个字母值传递给List组件
即 兄弟组件传值:字母表——>City.vue——>List.vue
| 1.对字母表组件 绑定一个点击事件 |
| 2 每次点击都向外触发事件change |
| 3.在City.vue中监听事件change change事件触发函数 handleLetterChange 获取letter的值 以属性的形式 获取传来的值 并传给List.vue List.vue接收 |
| 4.在List接收到传来的Letter值后,当字母值改变,List的显示也应该改变 也就是通过watch 实现: 给每个字母绑定ref 获取$refs这个数组的第一个值(即当前字母项)使用scroll插件的函数实现跳转效果 取 取[0]值 便取到了真正的元素 使用watch 滚动到对应的界面 |
这个思路非常干脆,不但能用在兄弟组件上,还能用在离得很远的组件上
以todolist为例
| 在list组件上绑定一个自定义事件 mounted() {
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
}, 该事件调用list中的方法addTitleHandler methods: {
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
}
} | ||
| 在input组件中聆听点击事件 被点击后调用addTitle事件处理程序 <button @click="addTitle">add</button>而在addTitle中 调用了自定义事件onAddTitle methods: {
addTitle() {
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
} | ||
| list组件中的 addTitleHandler方法因为被调用 | ||
| 要注意:this.$emit 和 event.$emit之间的区别 event值得是单独创建的一个Vue实例
import event from './event' | ||
| 最最重要的一点 及时销毁自定义的event事件 ! beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
} |
(3)组件生命周期
单个组件的生命周期| 挂载阶段 | beforeCreate created——此时VUE实例已经初始化结束 但还没有渲染 beforeMount mounted——此时已经渲染结束 |
| 更新阶段 | beforeUpdate updated |
| 销毁阶段 | beforeDestroy——解除绑定 销毁啊子组件 以及watch destroyed |
还是todolist的例子:
- created:先创建父组件 再创建子组件
- mounted:先渲染子组件 再渲染父组件
- beforeUpdate:先预备更新父组件 再预备更新子组件
- Updated:先更新子组件 再更新父组件
- beforeDestroy:先预备销毁父组件 再预备销毁子组件
- Destroyed:先销毁子组件 再销毁父组件
3.Vue高级特性
不是很常用,但是用的时候必须要知道
| 自定义v-modle |
| $nextTick |
| slot |
| 动态、异步组件 |
| keep-alive |
| mixin |
(1)自定义v-modle
在父组件,使用双向数据绑定:<CustomVModel v-model="name"/>
<div>
<!-- 自定义 v-model -->
<<p>{{name}}</p>
<CustomVModel v-model="name"/>
</div> //CustomVModel
<template>
<!-- 例如:vue 颜色选择 在v-model输入色号 就能显示出来颜色-->
<input type="text"
:value="uname"
@input="updateVal($event.target.value)">
<!--
1. 上面的 input使用的是动态属性:value 而不是 v-model
2. 上面的 change 和 model.event 要对应起来
3. 上面的text1属性和下面的text1要对应起来
-->
</template>
<script>
export default {
model: {
prop: 'uname',
// 随便命名事件,对应下面$emit即可
event: 'changeXXX'
},
props: {
uname: {
type: String,
default: 'tom'
}
},
methods: {
updateVal(val){
this.$emit('changeXXX',val)
}
}
</script> 上面代码定义了一个methods来触发事件,和下面这段代码的@input="$emit('change1', $event.target.value) 一个意思
<template>
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
>
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
</script> 关于v-model的深入理解可以参考:https://blog.csdn.net/liu_jun_tao/article/details/90232658
下面做一些关于这篇文章的摘录和思考:
现成的v-model也是一个语法糖,它内里真正的实现形式是:
<input type="text"
:value="message"
@input="message = $event.target.value"> - 将输入框的值绑定到message变量上,这只是单向的,改变message的值可以改变input的value,但是改变input的输入还不能改变message。
- 监听input事件,当输入类内容时改变message变量,从而实现了双向绑定。
所以,在我们自定义v-model组件时,就需要显示的来声明这些默认的定义,也就是我们在model选项内声明:
model: { //重点就是这里哈
prop: 'name', // prop的值
event: 'change'//聆听的事件
} (2)$nextTick
首先,要记住Vue是异步渲染的框架 在data改变之后 DOM并不会立刻渲染 而$nexiTick会在DOM渲染之后被触发,以获取最新的DOM节点
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// Vue是异步渲染,而$nextTick在DOM渲染完才回调
//所以可以通过$nextTick拿到最新的结果
// 上端代码修改了三次 但页面渲染时会将data的修改做整合
//随意,最终多次data修改只会渲染一次 这也只有异步能够实现
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log( ulElem.childNodes.length )
})
}
}
}
</script>
这里包含的小知识点: | 在vue中想要拿到DOM元素 就给该标签设置ref属性 <ul ref="ul1"> 通过this.$ref.ul1就可以获取该标签的DOM节点 |
(3)slot
插槽,也就是父组件要往子组件里插点什么
| 基本使用 | //父组件中
<SlotDemo :url="website.url"
{{website.title}}
</SlotDemo>
//子组件中
<template>
<a :href="url">
<slot>
默认内容,即父组件没设置内容时,这里显示
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
}
</script> 把父组件中的website.title 通过<slot> 插入到子组件中非常简单 |
| 作用域插槽 | //父组件中
<SlotDemo :url="website.url"
<template v-slot="slotProps">//就是多搞一个template
{{slotProps.slotData.title}}
</template>
</SlotDemo>
//子组件中
<template>
<a :href="url">
<slot :slotData="website">//就是增加一个:slotData属性
{{website.subTitle}} //父组件不传内容时显示 '轻量级富文本编辑器'
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
data() {
return {
website: {
url: 'http://wangEditor.com/',
title: 'wangEditor',
subTitle: '轻量级富文本编辑器'
}
}
}
}
</script> 简单地说 基本使用时是把父组件的内容插到子组件里 作用域插槽就是子组件非得想用自己的内容 要在父组件里显示自己的 |
| 具名插槽 | |
(4)动态组件
用法———— :is = "component-name"
适用于需要根据数据,动态渲染的场景,即组件类型不确定的时候 比如一个新闻页面 text /image /video 各个组件的顺序是不一定的,无法确定,这个时候就需要动态组件啦
<template>
<div v-for="(val,key) in newsData" :key="key">
<component :is="val.type">//动态组件
</template>
<script>
export default{
data() {
return {
newsData:{
1:{type:'text'},
2:{type:'text'},
3:{type:'image'}
}}}}
</script> (5)*异步组件
常见!常考!
实际开发中,经常有很多体积较大的组件,对于这些大组件就要使用异步组件。什么时候用什么时候加载
异步组件引入时不用import 而是在component组件注册中引入:
components:{
FormDeom:() => import ('...路径')
} (6)缓存组件
用keep-alive缓存组件 适用于频繁切换,不需要重复渲染的场景 也是Vue中常见的性能优化方法 例子:
如果不用keep-alive 由A切换为B的时候,A就会被destroy
使用keep-alive 谁都不会被destroy 切换时也不用重新渲染
这里就要和v-show做个对比:
v-show是通过原生的CSS中的display=none来控制 适用于非常简单的情况
而keep-alive是在框架层次做JS的渲染
注意:在Actions里才能做异步操作 












————————————————————————————————
<template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<keep-alive> <!-- tab 切换 -->
<KeepAliveStageA v-if="state === 'A'"/>
<KeepAliveStageB v-if="state === 'B'"/>
<KeepAliveStageC v-if="state === 'C'"/>
</keep-alive>
</div>
</template>
<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'
export default {
components: {
KeepAliveStageA,
KeepAliveStageB,
KeepAliveStageC
},
data() {
return {
state: 'A'
}
},
methods: {
changeState(state) {
this.state = state
}
}
}
</script> (7)mixin
当多个组件有相同的逻辑时,使用mixin可以抽离出共同逻辑
<template>
<div>
<p>{{name}} {{major}} {{city}}</p>
<button @click="showName">显示姓名</button>
</div>
</template>
<script>
import myMixin from './mixin'
export default {
mixins: [myMixin], // 可以添加多个,会自动合并起来
data() {
return {
name: '双越',
major: 'web 前端'
}
},
methods: {
},
mounted() {
// eslint-disable-next-line
console.log('component mounted', this.name)
}
}
</script> myMixin代码: export default {
data() {
return {
city: '北京'
}
},
methods: {
showName() {
// eslint-disable-next-line
console.log(this.name)
}
},
mounted() {
// eslint-disable-next-line
console.log('mixin mounted', this.name)
}
}
说白了,mixins就是一个引用,把多个组件公用的方法函数放在mixin中,然后在使用时进行引入 但mixin也存在不少问题,比如可能造成命名冲突啊

用于Vue组件
(8)Vuex知识点
之前在去哪儿网项目里做非兄弟组件传值时用过了Vuex
理念:把公用的数据放在一个仓库里 从而实现读取和改变(整个流程是单向的)
Vuex的核心是store 可以简单的把store看作是一个仓库
| state | 是一个单一状态数 可以看作是所有组件公用的data 保存了公共数据 |
| getters | 可以理解为store的计算属性 |
| actions | 和mutatiohs类似 不同在于
|
| mutations | 可以理解为store的methods 保存着更改数据的回调函数 其包含两个参数: 第一个参数是state, 第二参数是payload |
| Modules | 就是store的模块化 |
| dispatch | 含有异步操作,例如向后台提交数据,写法: this.$store.dispatch('action方法名',值) |
| commit | 同步操作,写法:this.$store.commit('mutations方法名',值) |
| mapState | 这几个map前缀的可以说就是映射 在没有mapState的时候 引入state 需要 this.$store.state.xxx 有了mapState以后 直接 import {mapState} from 'vuex'
export default {
name: 'home',
computed: ...mapState(['nickname','age','gender'])
} 咔咔的 就映射过来了 |
| mapGetters | 同理 |
| mapAcionts | 映射到methods里 methods:{
...mapActions(['getUserInfo'])
} |
| mapMutations | 映射到methods里 参数可以在调用方法时在吸写入 methods:{
...mapMutations(['addAge'])
} |
为了加深理解,这里把之前项目中的代码copy一下
——————————————————————————————————————————
使用store中的数据
通过store.state使用存储的数据
1.在 src下 创建store(仓库)
写index.js
(Vue)里在使用插件都是用Vue.use来实现的
导出Vuex创建的Store
仓库里面有个state 存储着全局公用的数据
2.在main中引入store
在创建根实例时传入store
3.在需要使用公共数据的组件里使用
改state中的数据
要实现List和Search中点击城市 就改变公共数据中city的值
1.首先以List组件为例
增加click事件 调用方法 传入此时的值
组件事件的处理程序调用 dispatch 方法 dispath执行对象就是Actions
2.在store的index文件中 创建名为changeCity的actions:
ctx表示上下文 并用用ctx调用commit commit执行对象是mutations
3.创建名为changeCity的mutations
在这个mutations中写入方法 改变state数据
所以,修改数据的整个流程为:
| 组件内使用Dispatch方法,调用Actions |
| Actions使用commit方法,调用Mutations |
| mutations内修改state数据 |
时刻记着这张图!在dispatch后的操作都是在store内进行的
—————————————————————————————————————————— 找到了一个比较通俗易懂的文章 可以参考一下:https://www.jianshu.com/p/120eaf50331c
(9)Vue-router
之前项目用路由配置时的代码copy一下:
step1:
在路由配置文件里添加配置
需要import City
step2:找到需要跳转页面的子块 添加router-link 和 to 属性 就能跳转到City这个组件了
| 路由模式 | hash模式 H5 history H5需要serve端配合 |
| 路由配置 | 动态配置: 懒加载:和之前异步组件非常类似 |