题目
v-show 和 v-if 的区别
为何 v-for 中要用 key
描述 vue 组件生命周期(有父子组件的情况)
vue 组件如何通讯
描述组件渲染和更新的过程
双向数据绑定 v-model 的实现原理
基本使用,组件使用 - 常用,必须会 基本使用 指令
插值、表达式
指令、动态属性
v-html 有xss风险,会覆盖子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <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>
computed 和 watch
computed 有缓存,data不变则不会重新计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <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: { get() { return this.num * 2 }, set(val) { this.num = val/2 } } } } </script>
watch 如何深度监听
watch 监听引用类型,拿不到 oldVal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div> <input v-model="name"/> <input v-model="info.city"/> </div> </template> <script> export default { data() { return { name: 'wsr', info: { city: '北京' } } }, watch: { name(oldVal, val) { // eslint-disable-next-line console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val }, info: { // 引用类型深度监听要写 handler 和 deep handler(oldVal, val) { // eslint-disable-next-line console.log('watch info', oldVal, val) // 引用类型,拿不到oldVal 。因为指针相同,此时已经指向了新的 val }, deep: true // 深度监听 } } } </script>
class 和 style
使用动态属性
驼峰写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <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>
条件渲染
v-if v-else 的用法,可使用变量,也可以使用 === 表达式
v-if 和 v-show 的区别
v-if 只渲染条件成立的 v-show 页面上都渲染,不成立的添加了一个 display:none
v-if 和 v-show 的使用场景
切换频繁时使用 v-show,不频繁使用v-if
循环(列表)渲染
如何遍历对象?也可以用v-for
key 的重要性。key 不能乱写 (random 或 index),要与业务结合
v-for 和 v-if 不能一起使用,因为 v-for 比 v-if 优先执行,一起用的话 v-if 执行多次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <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, listArr: [ { id: 'a', title: '标题1' }, // 数据结构中,最好有 id ,方便使用 key { id: 'b', title: '标题2' }, { id: 'c', title: '标题3' } ], listObj: { a: { title: '标题1' }, b: { title: '标题2' }, c: { title: '标题3' }, } } } } </script>
事件
event 参数,自定义参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <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 对象 // 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>
事件修饰符,按键修饰符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <a v-on:click.stop ="doThis" > </a > <form v-on:submit.prevent ="onSubmit" > </form > <a v-on:click.stop.prevent ="doThat" > </a > <form v-on:submit.prevent > </form > 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 <div v-on:click.capture ="doThis" > ...</div > <div v-on:click.self ="doThat" > ...</div >
1 2 3 4 5 6 <button @click.ctrl ="onClick" > A</button > <button @click.ctrl.exact ="onCtrlClick" > A</button > <button @click.exact ="onClick" > A</button >
观察事件被绑定到哪里
表单
v-model
常见表单项 textarea checkbox radio select
修饰符 lazy number trim
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <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: 'wsr', age: 18, desc: '爱撸猫的程序员', checked: true, checkedNames: [], gender: 'female', selected: '', selectedList: [] } } } </script>
组件使用 props 和 $emit (父子组件间通讯)
props 父组件传递一个值到子组件
$emit 子组件调用父组件的一个函数
组件间的通讯 - 自定义事件 (兄弟组件间) 参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <!-- 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 { 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) } }, created() { // eslint-disable-next-line console.log('index created') }, mounted() { // eslint-disable-next-line console.log('index mounted') }, beforeUpdate() { // eslint-disable-next-line console.log('index before update') }, updated() { // eslint-disable-next-line console.log('index updated') }, } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!-- input.vue 子组件 --> <template> <div> <input type="text" v-model="title"/> <button @click="addTitle">add</button> </div> </template> <script> import event from './event' export default { data() { return { title: '' } }, methods: { addTitle() { // 调用父组件的事件 this.$emit('add', this.title) // 调用自定义事件 event.$emit('onAddTitle', this.title) this.title = '' } } } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <!-- 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> import event from './event' export default { // props: ['list'] props: { // prop 类型和默认值 list: { type: Array, default() { return [] } } }, data() { return { } }, methods: { deleteItem(id) { this.$emit('delete', id) }, addTitleHandler(title) { // eslint-disable-next-line console.log('on add title', title) } }, created() { // eslint-disable-next-line console.log('list created') }, mounted() { // eslint-disable-next-line console.log('list mounted') // 绑定自定义事件 event.$on('onAddTitle', this.addTitleHandler) }, beforeUpdate() { // eslint-disable-next-line console.log('list before update') }, updated() { // eslint-disable-next-line console.log('list updated') }, beforeDestroy() { // 及时销毁,否则可能造成内存泄露 event.$off('onAddTitle', this.addTitleHandler) } } </script>
组件生命周期 单个组件
挂载阶段
created 只是初始化完成 vue 实例,并没有开始渲染。 mounted dom 已经绘制完成
更新阶段
销毁阶段
父子组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!-- index父组件,list 子组件 --> 加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted index created list created list mounted index mounted 子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated 父组件更新过程 父beforeUpdate->父updated index before update list before update list updated index updated 销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
高级特性 - 不常用,体现深度 自定义 v-model 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template> <div> <p>vue 高级特性</p> <hr> <!-- 自定义 v-model --> <p>{{name}}</p> <CustomVModel v-model="name"/> </div> </template> <script> import CustomVModel from './CustomVModel' export default { components: { CustomVModel }, data() { return { name: '张三', website: { url: 'http://imooc.com/', title: 'imooc', subTitle: '程序员的梦工厂' }, showFormDemo: false } } } </script>
CustomVModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <!-- 例如:vue 颜色选择 --> <input type="text" :value="text1" @input="$emit('change1', $event.target.value)" > <!-- 1. 上面的 input 使用了 :value 而不是 v-model 2. 上面的 change1 和 model.event 要对应起来 3. text1 属性对应起来 --> </template> <script> export default { model: { prop: 'text1', // 对应 props text1 event: 'change1' }, props: { text1: String, default() { return '' } } } </script>
参考
$nextTick refs
vue 是异步渲染
data 改变之后,dom不会立刻渲染
$nextTick 会在 dom 渲染之后被处罚,以获取最新 dom 节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <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()}`) // 这时候打印的是之前的长度 const ulElem = this.$refs.ul1 console.log( ulElem.childNodes.length ) // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调 // 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次 this.$nextTick(() => { // 获取 DOM 元素 const ulElem = this.$refs.ul1 // eslint-disable-next-line console.log( ulElem.childNodes.length ) }) } } } </script>
slot
基本使用 父组件往子组件中插入一个内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!-- index.vue --> <template> <SlotDemo :url="website.url"> {{website.title}} </SlotDemo> </template> <!-- slotdemo.vue --> <template> <a :href="url"> <slot> 默认内容,即父组件没设置内容时,这里显示 </slot> </a> </template> <script> export default { props: ['url'], data() { return {} } } </script>
作用域插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <!-- index.vue --> <template> <SlotDemo :url="website.url"> {{website.title}} </SlotDemo> <ScopedSlotDemo :url="website.url"> <template v-slot="slotProps"> {{slotProps.slotData.title}} </template> </ScopedSlotDemo> </template> <!-- scopedslotdemo.vue --> <template> <a :href="url"> <slot :slotData="website"> {{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 --> </slot> </a> </template> <script> export default { props: ['url'], data() { return { website: { url: 'http://wangEditor.com/', title: 'wangEditor', subTitle: '轻量级富文本编辑器' } } } } </script>
具名插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> <NamedSlot> <template v-slot:header> <h1>将插入到header中</h1> </template> <p>将插入到main中</p> <template v-slot:footer> <h1>将插入到footer中</h1> </template> </NamedSlot> </template>
动态、异步组件 动态组件
:is=”component-name”用法
需要根据数据,动态渲染的场景。即组件类型不确定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <component :is ="NextTickName" /> <div v-for ="item in newsData" :key ="item.id" > <component :is ="item.type" /> </div > <script > import NextTick from './NextTick' import text from './text' import image from './image' import video from './video' export default { components: { NextTick, text, image, video }, data() { return { NextTickName: "NextTick" , text: "text" , image: "image" , video: "video" newsData: [ { type: 'text' , id: 1 }, { type: 'image' , id: 2 }, { type: 'video' , id: 3 } ] } } } </script >
异步组件
import() 函数
按需加载,异步加载大组件
vue 常见性能优化之一
1 2 3 4 5 6 7 8 9 10 11 <template> <FormDemo v-if="showFormDemo"/> <button @click="showFormDemo = true">show form demo</button> </template> <script> export default { components: { FormDemo: () => import('../BaseUse/FormDemo') } } </script>
keep-alive
缓存组件
频繁切换,不需要重新渲染
vue 常见性能优化之一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template> <div> <button @click="changeState('A')">A</button> <button @click="changeState('B')">B</button> <button @click="changeState('C')">C</button> <!-- 使用 keep-alive 后组件不会 destroyed --> <keep-alive> <!-- tab 切换 --> <KeepAliveStageA v-if="state === 'A'"/> <!-- v-show --> <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>
mixin
多个组件有相同的逻辑,抽离出来
mixin 并不是完美的解决方案,会有一些问题
变量来源不明确,不利于阅读
多mixin可能造成命名冲突
mixin 和组件可能出现多对多的关系,复杂度较高
vue3 提出的 composition api 旨在解决这些问题
demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <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>
mixin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export default { data() { return { city: '北京' } }, methods: { showName() { console .log(this .name) } }, mounted() { console .log('mixin mounted' , this .name) } }
vuex 和 vue-router 使用 vuex
面试考点并不多(熟悉vue之后,vuex没有难度)
基本概念,基本使用和api需要掌握
可能会考察 state 的数据结构设计
state
getters
action 只能在这做异步操作,整合多个mutation
mutation 原子操作
dispatch
commit
mapState
mapGetters
mapActions
mapMutations
vue-router
面试考点并不多(前提是熟悉vue)
路由模式(hash、 h5 history)
hash 模式http://abc.com/#/user/login
h5 history模式http://abc.com/user/login
后者需要 server 端支持,无特殊要求选择第一种
路由配置(动态路由、懒加载)
1 2 3 4 5 6 7 8 9 10 11 const user = { template: '<div>user {{ $router.params.id }}</div>' } const router = new VueRouter({ routes: [ { path : '/user/:id' , component : user } ] })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export default new VueRouter({ routes: [ { path: '/' , component: () => import ( './../components/Navigator' ) }, { path: '/feedback' , component: () => import ( './../components/FeedBack' ) }, ] })
一些工具 svg字体图标制作 iconmoon iconmoon 使用说明 jsonview 谷歌json查看工具 css reset
api接口mock数据 vue.config.js添加devServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const appData = require ('./data.json' )const seller = appData.sellerconst goods = appData.goodsmodule .exports = { devServer:{ before(app){ app.get('./api/seller' , function (req,res ) { res.json({ errno:0 , data:seller }) }) app.get('./api/goods' , function (req,res ) { res.json({ errno:0 , data:goods }) }) } } }