【前端面试专题】【3】Vue2
Vue 2.x
Vue 基本使用
指令、插值
- 插值、表达式
- 指令、动态属性
- v-html:会有 XSS 风险,会覆盖子组件
<template><div><p>文本插值 {{ mesage }}</p><p>JS表达式 {{ flag ? 'yes' : 'no' }}</p> <!-- 只能是表达式,不能是 js 语句 --><p :id="dynamicId">动态属性 id</p><p v-html="rawHtml"><span>【注意】使用 v-html 之后,将会覆盖子元素</span></p></div>
</template><script>
export default {data() {return {message: 'hello',flag: true,rawHtml: '<b>这是加粗</b><i>这是斜体</i>',dynamicId: `id-${Date.now()}`}}
}
</script><style scoped></style>
computed 和 watch
- computed 有缓存,data 不变则不会重新计算
- watch 如何深度监听?
- watch 监听引用类型,拿不到 oldVal
<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},// v-model 绑定的话需要写 get setdouble2: {get() {return this.num * 2},set(val) {return this.num = val / 2}}}
}
</script><style scoped></style>
<template><div><input v-model="name" /><input v-model="info.city" /></div>
</template><script>
export default {data() {return {name: 'Jae',info: {city: 'xx'}}},computed: {},watch: {name(oldVal, val) {console.log('watch name', oldVal, val) // 值类型,可以正常拿到 oldVal 和 val},info: {handler(oldVal, val) {console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal。因为指针相同,此时已经指向了新的 val},deep: true // 深度监听}}
}
</script><style scoped></style>
class 和 style
- 使用动态属性
- 使用驼峰式写法
<template><div><p :class="{ black: isBack, yellow: isYellow }">使用 class</p><p :class="[ black, yellow ]">使用 class(数组)</p><p :style="styleData">使用 style</p></div>
</template><script>
export default {data() {return {isBack: true,isYellow: true,black: 'black',yellow: 'yellow',styleData: {fontSize: '40px', // 驼峰color: 'red',backgroundColor: '#ccc' // 驼峰}}}
}
</script><style scoped></style>
条件渲染
- v-if v-else 的用法,可使用变量,也可以使用 === 表达式
- v-if 和 v-show 的区别
- v-if 和 v-show 的使用场景
<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><style scoped></style>
如果页面上需要频繁切换渲染,用 v-show
;如果渲染的次数不频繁,用 v-if
循环(列表)渲染
- 如何遍历对象?—— 也可以用v-for
- key 的重要性
- v-for 和 v-if 不能一起使用(v-for 优先级高,会执行 n 次循环后再进行 n 次 v-if 判断)
<template><div><p>遍历数组</p><ul><li v-for="(item, index) in arrList" :key="item.id">{{ index }} - {{ item.id }} - {{ item.title }}</li></ul><p>遍历对象</p><ul><li v-for="(val, key, index) in objList" :key="key">{{ index }} - {{ key }} - {{ val.title }}</li></ul></div>
</template><script>
export default {data() {return {flag: false,arrList: [{ id: '1', title: 'title1' },{ id: '2', title: 'title2' },{ id: '3', title: 'title3' }],objList: {a: { title: 'title1' },b: { title: 'title2' },c: { title: 'title3' }}}}
}
</script><style scoped></style>
事件
- event 参数,自定义参数
- 事件修饰符,按键修饰符
- 事件被绑定到哪里?
<template><div><p>{{ num }}</p><button @click="handleClickIncre1">+1</button><button @click="handleClickIncre2(2, $event)">+2</button><!-- 事件修饰符 --><!-- 阻止单击事件继续传播 --><a @click.stop="doSomeThing"></a><!-- 提交事件不再重载页面 --><form @submit.prevent="onSubmit"></form><!-- 修饰符的串联 --><a @click.stop.prevent="doSomeThing"></a><!-- 只有修饰符 --><form @submit.prevent></form><!-- 添加事件监听器时使用事件捕获模式,即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 --><div @click.capture="doSomeThing"></div><!-- 只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的 --><div @click.self="doSomeThing"></div><!-- 按键修饰符 --><button @click.ctrl="onClick">A</button><!-- 有且只有 Ctrl 被按下时才触发 --><button @click.ctrl.exact="onClick">A</button><!-- 没有任何系统修饰符被按下时才触发 --><button @click.exact="onClick">A</button></div>
</template><script>
export default {data() {return {num: 0}},methods: {handleClickIncre1(event) {// 原生的 event 对象 console.log('event', event, event.__proto__.constructor) // event PointerEvent {...} PointerEvent() { [native code] }console.log(event.target) // <button>+1</button>console.log(event.currentTarget) // 事件是被注册到当前元素的this.num ++},handleClickIncre2(val, event) {console.log(event.target) // <button>+2</button>this.num = this.num + val}}
}
</script><style scoped></style>
表单
- v-model
- 常见表单项 textarea checkbox radio select
- 修饰符 lazy number trim
<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>{{ desc }}</textarea> --><textarea v-model="desc"></textarea><p>复选框 {{ checked }}</p><input type="checkbox" v-model="checked" /><p>多个复选框 {{ checkedNames }}</p><input type="checkbox" id="jae" value="Jae" v-model="checkedNames" /><label for="jae">Jae</label><input type="checkbox" id="tom" value="Tom" v-model="checkedNames" /><label for="tom">Tom</label><input type="checkbox" id="amy" value="Amy" v-model="checkedNames" /><label for="amy">Amy</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: 'Jae',age: 22,desc: 'hello',checked: true,checkedNames: [],gender: 'male',selected: '',selectedList: []}}
}
</script><style scoped></style>
Vue 组件使用
props 和 $emit
代码示例:
<!-- index.vue -->
<template><div><Input @add="addHandler" /><List :list="list" @delete="deleteHandler" /></div>
</template><script>
import Input from './input'
import List from './List'export default {name: 'ComponentsDemo',components: {Input,List},data() {return {list: [{id: 'id-1',title: '标题1'}]}},methods: {addHandler(title) {this.list.push({id: `id-${Date.now()}`,title})},deleteHandler(id) {console.log(1);this.list = this.list.filter(item => item.id !== id)}}
}
</script>
<!-- Input.vue -->
<template><div><input type="text" v-model="title"><button @click="addTitle">add</button></div>
</template><script>
export default {name: 'InputName',data() {return {title: ''}},methods: {addTitle() {this.$emit('add', this.title)this.title = ''}}
}
</script>
<!-- List.vue -->
<template><div><ul><li v-for="item in list" :key="item.id">{{ item.title }}<button @click="deleteItem(item.id)">删除</button></li></ul></div>
</template><script>
export default {name: 'ListName',props: {list: {type: Array,default: () => []}},data() {return {}},methods: {deleteItem(id) {this.$emit('delete', id)}}
}
</script>
组件间通讯 - 自定义事件
// event.js
import Vue from 'vue'export default new Vue()
<!-- Input.vue -->
<script>
import event from './event'
export default {name: 'InputName',data() {return {title: ''}},methods: {addTitle() {event.$emit('inputAddTitle', this.title)}}
}
</script>
<!-- List.vue -->
<script>
import event from './event'
export default {mounted() {event.$on('inputAddTitle', this.addTitleHandler)},beforeDestroy() {// 及时销毁,否则可能造成内存泄漏event.$off('inputAddTitle', this.addTitleHandler)},methods: {addTitleHandler(title) {console.log('input组件emit过来的:', title)}}
}
</script>
组件生命周期
1)单个组件
- 挂载阶段(beforeCreate、created、beforeMount、mounted)
- 更新阶段(beforeUpdate、updated)
- 销毁阶段(beforeDestroy、destroyed)
2)父子组件
参考上方 《prop 和 $emit》中的代码示例:
<!-- index.vue -->
created() {console.log('index.vue created')
},
mounted() {console.log('index.vue mounted')
}
<!-- List.vue -->
created() {console.log('List.vue created')
},
mounted() {console.log('List.vue mounted')
}
输出为:
index.vue created
List.vue created
List.vue mounted
index.vue mounted
即:父组件比子组件先创建,但是父组件要等子组件渲染完才能渲染
Vue 高级特性
自定义 v-model
<!-- index.vue -->
<template><div><!-- 自定义 v-model --><p>{{ text }}</p><CustomVModel v-model="text" /></div>
</template><script>
import CustomVModel from './CustomVModel.vue'
export default {name: 'AdvancedUse',components: {CustomVModel},data() {return {text: 'Jae'}}
}
</script>
<!-- CustomVModel.vue -->
<template><div><input type="text" :value="text" @input="$emit('change', $event.target.value)"></div>
</template><script>
export default {model: {prop: 'text', // 对应 props 中的 textevent: 'change'},props: {text: {type: String,default: ''}}
}
</script>
$nextTick
- Vue 是异步渲染
- data 改变之后,DOM 不会立刻渲染
- $nextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点
代码示例:
<template><div><ul ref="ul"><li v-for="(item, index) in list" :key="index">{{ item }}</li></ul><button @click="add">添加</button></div>
</template><script>
export default {name: 'NextTick',data() {return {list: [1, 2, 3]}},methods: {add() {this.list.push(`${Date.now()}`)this.list.push(`${Date.now()}`)this.list.push(`${Date.now()}`)const ulEle = this.$refs.ul // 获取 DOM 元素console.log(ulEle.childNodes.length)}}
}
</script>
可以看出这个输出结果不太对,一开始是3个,点击添加后添加了3个,怎么输出长度还是3呢?这就是因为此时输出的 DOM 节点还是上一次的,还未更新,如果需要得到正常的结果,就需要加上 $nextTick
确保 DOM 渲染完成:
this.$nextTick(() => {const ulEle = this.$refs.ulconsole.log(ulEle.childNodes.length)
})
slot
基本使用
代码示例:
<!-- index.vue -->
<template><div><SlotDemo :url="website.url">{{ website.title }}</SlotDemo></div>
</template><script>
import SlotDemo from './SlotDemo.vue'
export default {name: 'AdvancedUse',components: {SlotDemo},data() {return {website: {url: 'https://www.bing.com',title: '搜索引擎',subTitle: '比百度好用的搜索引擎'}}}
}
</script>
<!-- SlotDemo.vue -->
<template><div><a :href="url"><slot>默认内容,即父组件没设置内容时,显示的东西</slot></a></div>
</template><script>
export default {name: 'SlotDemo',props: {url: {type: String,defalut: ''}},data() {return {}}
}
</script>
如果将 index.vue 中 SlotDemo 组件修改一下:
<!-- SlotDemo.vue -->
<SlotDemo :url="website.url" />
作用域插槽
代码示例:
<!-- index.vue -->
<template><div><ScopedSlotDemo :url="website.url"><!-- 我想在这里显示子组件中 website 中的 title,怎么获取得到? --></ScopedSlotDemo></div>
</template><script>
import ScopedSlotDemo from './ScopedSlotDemo.vue'
export default {name: 'AdvancedUse',components: {ScopedSlotDemo},data() {return {}}
}
</script>
<!-- ScopedSlotDemo.vue -->
<template><div><a :href="url"><slot :slotData="website"> <!-- 将 website 放在 slotData 中,这个名字可以随便取 -->{{ website.subTitle }} <!-- 默认值显示 subTitle,即父组件不传值 --></slot></a></div>
</template><script>
export default {props: {url: {type: String,defalut: ''}},data() {return {website: {url: 'https://www.baidu.com',title: '某搜索引擎',subTitle: '比bing不好用的搜索引擎'}}}
}
</script>
在父组件中的插槽接收即可:
<!-- index.vue -->
<template><div><ScopedSlotDemo :url="website.url"><template v-slot="{ slotData }"> <!-- 这里是解构写法,原来是 v-slot="slotProps",这个 slotProps 也可以自己取名 -->{{ slotData.title }} <!-- 如果上面是用 slotProps 接收,那这里就是写成 slotProps.slotData.title --></template></ScopedSlotDemo></div>
</template><script>
import ScopedSlotDemo from './ScopedSlotDemo.vue'
export default {name: 'AdvancedUse',components: {ScopedSlotDemo},data() {return {website: {url: 'https://www.bing.com',title: '搜索引擎',subTitle: '比百度好用的搜索引擎'}}}
}
</script>
具名插槽
<!-- NamedSlot.vue -->
<div><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer>
</div>
<!-- index.vue -->
<NamedSlot><template #header><h1>将插入 header slot 中</h1></template><p>将插入 main slot 中,即默认的未命名的 slot</p><template #header><h2>将插入 footer slot 中</h2></template>
</NamedSlot>
动态、异步组件
动态组件
:is="componentName"
用法- 需要根据数据,动态渲染的场景。即组件类型不确定
<template><div><!-- 动态组件 --><component :is="componentName" /></div>
</template><script>
import NextTick from './NextTick.vue'
export default {name: 'AdvancedUse',components: {NextTick},data() {return {componentName: 'NextTick'}}
}
</script>
异步组件
- import() 函数
- 按需加载,异步加载大组件
<template><div><!-- 异步组件 --><NextTick v-if="showCom" /><button @click="showCom = true">show Component</button></div>
</template><script>
// import NextTick from './NextTick.vue' 不要同步引进来
export default {name: 'AdvancedUse',components: {NextTick: () => import ('../components/NextTick') // 动态引入},data() {return {showCom: false}}
}
</script>
keep-alive
- 缓存组件
- 频繁切换,不需要重复渲染(比如 tabs 标签页)
- Vue 常见性能优化
<!-- index.vue -->
<template><div><button @click="changeState('A')">A</button><button @click="changeState('B')">B</button><button @click="changeState('C')">C</button><keepAliveStateA v-if="state === 'A'" /><keepAliveStateB v-if="state === 'B'" /><keepAliveStateC v-if="state === 'C'" /></div>
</template><script>
import keepAliveStateA from './keepAliveStateA'
import keepAliveStateB from './keepAliveStateB'
import keepAliveStateC from './keepAliveStateC'
export default {name: 'KeepAlive',components: {keepAliveStateA,keepAliveStateB,keepAliveStateC},data() {return {state: 'A'}},methods: {changeState(state) {this.state = state}}
}
</script>
<!-- keepAliveStateA.vue -->
<template><div>state A</div>
</template><script>
export default {data() {return {}},computed: {},watch: {},created() {},mounted() {console.log('A mounted')},destroyed() {console.log('A destroyed')},
}
</script>
<!-- keepAliveStateB.vue -->
<template><div>state B</div>
</template><script>
export default {data() {return {}},computed: {},watch: {},created() {},mounted() {console.log('B mounted')},destroyed() {console.log('B destroyed')},
}
</script>
<!-- keepAliveStateC.vue -->
<template><div>state C</div>
</template><script>
export default {data() {return {}},computed: {},watch: {},created() {},mounted() {console.log('C mounted')},destroyed() {console.log('C destroyed')},
}
</script>
如果不需要每次切换的时候都销毁组件,该怎么做呢?使用 keep-alive
:
<template><div><button @click="changeState('A')">A</button><button @click="changeState('B')">B</button><button @click="changeState('C')">C</button><keep-alive><keepAliveStateA v-if="state === 'A'" /><keepAliveStateB v-if="state === 'B'" /><keepAliveStateC v-if="state === 'C'" /></keep-alive></div>
</template>
</script>
mixin
- 多个组件有相同的逻辑,抽离出来
- mixin并不是完美的解决方案,会有一些问题
- Vue3 提出的 Composition API 旨在解决这些问题
<!-- index.vue -->
<template><div><p>{{ name }} {{ age }} {{ city }}</p><button @click="showName">显示姓名</button></div>
</template><script>
import myMixin from './mixin'
export default {mixins: [myMixin], // 可添加多个data() {return {name: 'Jae',age: 22}},mounted() {console.log('index mounted')},
}
</script>
// mixin.js
export default {data() {return {city: 'XXX'}},mounted() {console.log('mixin mounted')},methods: {showName() {console.log(this.name)}}
}
mixin 的问题
- 变量来源不明确,不利于阅读
- 多 mixin 可能造成命名冲突
- mixin 和组件可能出现多对多的关系,复杂度较高
Vuex 的使用
Vuex 基本概念
- state
- getters
- action
- mutation
用于 Vue 组件
- dispatch
- commit
- mapState
- mapGetters
- mapActions
- mapMutations
Vue-router 的使用
路由模式(hash、H5 history)
- hash 模式(默认),如 http://xxx.com/#/detail/10
- H5 history 模式,如 http://xxx.com/detail/20
- 后者需要 server 端支持,一般无特殊需求选则前者
路由配置(动态路由、懒加载)
const router = new VueRouter({routes: [// 动态路径参数,以冒号开头{ path: '/user/:id', component: User }]
})
export default new VueRouter({routes: [{path: '/',component: () => import('./../components/home')}]
})