导学
课程链接
https://coding.imooc.com/class/419.html
前端面试常见流程
思维导图和课程资料
https://blog.cdn.ionluo.cn/files/frame-project-interview-master.zip
注意事项
- 关于Vue的基础使用部分,推荐官方文档,这里只是罗列了一些基础的部分,想要学好还是看官方文档。https://cn.vuejs.org/v2/guide/
- 关于React的基础,官方文档虽然没有Vue那么清晰易懂,但是也是很完善的,也是推荐用官网文档来进行学习。https://reactjs.bootcss.com/
Vue使用
说明
1 | // package.json |
Computed
1 | <template> |
Watch
1 | <template> |
扩展:computed 和 watch 的区别和运用的场景?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
- 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
- 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
关于这个问题,也可以看看这篇文章:https://blog.csdn.net/weixin_39015132/article/details/83310726
Class和Style
1 | <template> |
v-if和v-show
1 | <template> |
v-for
1 | <template> |
v-for的优先级比v-if高,但是不建议两者一起使用, 可以利用计算属性的方式生成需要的列表。
v-on
1 | <template> |
上面漏了2个:
1
2
3
4 <!-- 点击事件只会触发一次 -->
<a @click.once="doThis"></a>
<!-- 给组件绑定点击事件 -->
<my-component @click.native="doThis"></my-component>
通用法:
1
2
3 <!-- 只有在keyCode 是 13 时调用 vm.submit() -->
<!-- 等于写法: <input @keyup.enter="submit"> -->
<input @keyup.13="submit">记住所有的keyCode比较困难,所以Vue为最常用的按键提供了别名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 .enter
.tab
.delete
.esc
.space
.up
.down
.left
.right
.ctrl
.alt
.shift
.meta
<!-- 以下为鼠标按键修饰符 -->
.left
.right
.midddle可以通过全局config.keyCodes对象自定义键值修饰符别名:
1
2 // 可以通过 @keyup.f1 使用
Vue.config.keyCodes.f1 = 11we2
v-model
1 | <template> |
扩展:直接给一个数组项赋值,Vue 能检测到变化吗?
由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
1
2
3
4
5
6 // Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)为了解决第二个问题,Vue 提供了以下操作方法:
1
2 // Array.prototype.splice
vm.items.splice(newLength)扩展:对象属性添加和删除,Vue 能检测到变化吗?
还是由于JavaScript的限制,Vue不能检测对象属性的添加和删除:
1
2
3
4
5
6
7
8 // a的变化可以检测到
var vm = new Vue({
data: {
a: 1
}
})
// b的变化检测不到
vm.b = 2扩展:对于已经创建的实例,Vue不能动态添加根级别的响应式属性。
但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:
1
2
3
4
5
6
7 var vm = new Vue({
data: {
message: {
text1: 'hello'
}
}
})你可以添加一个新的text2属性到message对象:
1 Vue.set(vm.message, 'text2', 'world')你还可以使用vm.$set实例方法,它只是全局Vue.set的别名:
1 this.$set(this.message, 'text2', 'world')有时你可能需要为已有对象赋予多个新属性,比如使用Object.assign()或_.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,可以这么做:
1
2
3
4 this.message = Object.assign({}, this.message, {
text2: 'wold',
text3: '!'
})PS: 也可以通过 this.$forceUpdate() 直接强制更新视图
组件通信
有父子组件通信,子父组件通信,平行组件通信
Index.vue
1 | <template> |
Input.vue
1 | <template> |
List.vue
1 | <template> |
event.js
1 | import Vue from 'vue' |
组件扩展:
给组件绑定原生事件(.native)
1 <my-component @click.native="doTheThing"></my-component>自定更新父组件属性(.sync)
1
2
3
4
5 <!-- 有时候,我们希望实现Props的“双向绑定”,可以使用.sync修饰符,它是一个编译时的语法糖 -->
<my-component :foo.sync="bar"></my-component>
<!-- 编译成: <my-component :foo="bar" @update:foo="value => bar = value"></my-component> -->
<!-- 子组件需要更新foo时,显示触发一个更新事件 -->
this.$emit('update:foo', newValue)组件命名约定
1
2
3
4
5
6
7
8
9
10
11
12 component: {
// 使用 kebab-case 形式注册
'kebab-cased-component': {},
// register using camelCase
'camelCasedComponent': {},
// register using PascalCase
'PascalCaseComponent': {},
}
// 在HTML模板中,请使用kebab-case形式
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<pascal-case-component></pascal-case-component>递归组件
组件生命周期
谈谈你对 Vue 生命周期的理解?
(1)生命周期是什么?
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
(2)各个生命周期的作用
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activited | keep-alive 专属,组件被激活时调用 |
deactivated | keep-alive 专属,组件被销毁时调用 |
beforeDestroy | 组件销毁前调用 |
destroyed | 组件销毁后调用 |
(3)生命周期示意图
Vue 的父组件和子组件生命周期钩子函数执行顺序?
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
在哪个生命周期内调用异步请求?
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面 loading 时间;
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
在什么阶段才能访问操作DOM?
在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。
父组件可以监听到子组件的生命周期吗?
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
1 | // Parent.vue |
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
1 | // Parent.vue |
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
自定义组件的v-model
Index.vue
1 | <template> |
CustomVModel.vue
1 | <template> |
$nextTick
1 | <template> |
slot
基本使用
Index.vue
1 | <template> |
SlotDemo.vue
1 | <template> |
作用域插槽
Index.vue
1 | <template> |
ScopedSlotDemo.vue
1 | <template> |
具名插槽
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
1 | <template> |
动态组件的缓存需要借助keep-alive: https://cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%9C%A8%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6%E4%B8%8A%E4%BD%BF%E7%94%A8-keep-alive
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
1 | Vue.component('async-example', function (resolve, reject) { |
下面展示Webpack 和 ES2015 语法加在一起(vue-cli),我们可以这样使用动态导入:
1 | <template> |
mixin
Index.vue
1 | <template> |
mixin.js
1 | export default { |
mixin的说明
- 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
- 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
- 值为对象的选项,例如
methods
、components
和directives
,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。 - 请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。
- 可自定义选项合并策略
mixin的问题
- 变量来源不明确,不利于阅读(合理的形式应该像es6的import)
- 多个mixin可能会造成命名冲突(官方推荐使用一定的规范来避免)
- mixin和组件可能出现多对多的关系,复杂度高(要极力避免)
周边组件库
Vuex
面试考点并不多,但是基本概念,基本使用和API必须掌握,可能会考察state的数据结构设计。
代码demo见:https://gitee.com/cheerfulion/my_public_demos/tree/master/vuex_demo
demo这里在小型项目就够用了,大项目的话可以看下module:
Vue-router
这里的考点我觉得文档上面已经列的很好了,内容也不多,推荐直接点击上面标题阅读源文档。这里就主要讲解几个点吧!
路由模式
hash模式(默认)
history模式(需要后台支持,文档中给出了各个后端服务器的配置方法,注意该方式需要前端指定404页面)
动态路由
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
/user/ion* | /user/ionluo | { pathMatch: 'luo' } g |
除了 $route.params
外,$route
对象还提供了其它有用的信息,例如,$route.query
(如果 URL 中有查询参数)、$route.hash
等等。你可以查看 API 文档 的详细说明。
详见官方文档。
路由懒加载
其实就是上面的异步组件的webpack实现。
Axios
UI组件库
Element
开发团队:饿了么
核心关键词:PC端UI组件库,不支持Vue1.x
Mint UI
开发团队:饿了么
核心关键词:移动端独立组件库,Vue1.x和Vue2.x都支持,体积小(30kb),但是组件个数偏少
iView
核心关键词:PC,中后台产品,Vue1.x和Vue2.x都支持。开源了一个Iview Admin,做后台非常方便。
Vux
核心关键词:移动端,WeUI,组件多,不支持Vue1.x。Vux主要服务于微信页面,WeUI是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计。
其他
Bootstrap-Vue
Bootstrap-VUE提供了基于vue2的Bootstrap V4组件和网格系统的实现,完成了广泛和自动化的WAI ARA可访问性标记。想当初刚流行响应式网站的时候,Bootstrap是世界上最受欢迎的建立移动优先网站的框架,Bootstrap可以说风靡全球。就算放在现在很多企业网站都是采用Bootstrap做的响应式。Bootstrap-Vue可以让你在Vue中也实现Bootstrap的效果。
Ant Design Vue
Ant Design Vue是 Ant Design 3.X 的 Vue 实现,开发和服务于企业级后台产品。Ant Design Vue共享Ant Design of React设计工具体系,实现了所有Ant Design of React的组件,支持现代浏览器和 IE9 及以上(需要 polyfills)。可以让熟悉Ant Design的在使用Vue时,很容易的上手。
Vant
Vant是一个轻量、可靠的移动端 Vue 组件库。Vant是有赞团队开源的,主要维护也是有赞团队。Vant Weapp 是有赞移动端组件库 Vant 的小程序版本,两者基于相同的视觉规范,提供一致的 API 接口,助力开发者快速搭建小程序应用。
推荐阅读
Vue原理
组件化和MVVM
响应式原理
vdom和diff算法
模板编译
组件渲染过程
前端路由
关于MVC、MVP和MVVM的理解
MVC作为经典的框架模式,视图层,数据层以及业务逻辑层都有关联,缺点是数据和视图耦合性高,逻辑层臃肿(Jquery)。
MVP视图和数据都通过P这个中间层交互,导致P层特别臃肿。但是去除了数据和视图的耦合性,维护起来更方便(Django)。
MVVM把VM代替上面的C和P层,直接通过数据驱动渲染视图(Angular, React, Vue)。
响应式原理
核心API - Object.defineProperty
Vue3.0使用Proxy
实现响应式,因为Object.defineProperty
具有一些缺点。
注意:
Proxy
兼容性不如Object.defineProperty
,且无法polyfill
Object.defineProperty缺点
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(所以Vue提供了Vue.set和 Vue.delete方法)
- 无法原生监听数组,需要特殊处理
下面实现对象和数组的响应式:
视频讲解见:https://www.bilibili.com/video/BV1VA411x76D
1 | // 触发更新视图 |
虚拟DOM和diff算法(难点)
视频讲解见:https://www.bilibili.com/video/BV1dV411a7mT
- vdom是实现vue和React的重要基石
- diff算法是vdom中最核心、最关键的部分
- vdom是一个热门话题,也是面试中热门话题
vdom
- DOM操作非常耗费性能
- 以前用JQuery,可以自行控制DOM操作的时机,手动调整
- vdom用JS模拟DOM结构,计算出最小的变更(diff算法)来操作DOM
- vdom是React提出的,vue2.0后也开始使用。
这里的JS模拟在不同的框架中可能不一样的表示,即没有既定的标准, 如
tag
可能有的称为element
,style
可能不放在props
中。
diff算法
模板编译
- 前置知识:JS的with语法
- vue template complier 将模板编译为 render 函数
- 执行 render 函数生成 vnode
with语法
with 要慎用,它打破了作用域规则,易读性变差
1 | const obj = {a:100, b:200} |
编译模板(vue template complier 将模板编译为 render 函数)
1 | // "vue-template-compiler": "^2.6.10" |
总结
模板编译为 render 函数,执行 render 函数返回 vnode
基于 vnode 再执行 patch 和 diff
使用 webpack vue-loader , 会在开发环境编译模板
了解过上面的 render 函数,则在一些特殊情况下,也可以定义组件的时候不用 template 而是直接使用render
1 | Vue.component('heading', { |
组件渲染和更新过程
初次渲染过程
解析模板为 render 函数(或在开发环境已完成,vue-loader)
触发响应式,监听 data 属性 getter setter
执行 render 函数, 生成 vnode, patch(elem, vnode)
更新过程
修改 data,触发 setter(此前在getter中已被监听)
重新执行 render 函数,生成 newVnode
patch(vnode, newVnode)
前端路由原理
hash路由
hash 变化会触发网页跳转,即浏览器的前进、后退
hash 变化不会刷新页面,SPA必须的特点
hash 永远不会提交到 server 端
核心API:window.onhashchange
1 |
|
history路由
- 用 url 规范的路由,但跳转时不刷新页面
- 正式环境需要 server 端配合,可参考 https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
- 核心API:history.pushState、window.onpopstate
- https://developer.mozilla.org/zh-CN/docs/Web/API/History
1 |
|
Vue面试真题演练
为什么要在v-for中用key
- 必须用 key,且不能是 index 和 random
- diff算法中通过 tagName 和 key 来判断,是否是 sameNode
- 减少渲染次数,提高渲染性能
描述 Vue 组件生命周期(父子组件)
- 单组件生命周期图
- 父子组件生命周期关系
- 详见
Vue使用
下的组件生命周期
一节
Vue组件如何通讯
父 –> 子: props
子 –> 父: this.$emit –> this.$on
EventBus (平行组件)
vuex (中大型项目组件通讯,可以任意组件互相通讯(未设置命名空间情况下))
详见
Vue使用
下的组件通信
一节
描述组件渲染和更新过程
详见
Vue原理
下的组件渲染和更新过程
一节
双向数据绑定 v-model 的实现原理(注意不是问响应式数据的原理)
input 元素的 value = this.name
绑定 input 事件 this.name = $event.target.value
data 更新触发 re-render
详见
Vue原理
下的模板编译
一节
对MVVM的理解
详见
Vue原理
下的关于MVC、MVP和MVVM的理解
一节
- computed有何特点
- 使用方式相当于data中的数据(属性),声明方式相当于method(函数)
- 计算结果会缓存,只有相关数据改变才会重新计算
为何组件 data 必须是一个函数
组件在编译后是一个类(构造器),组件的使用就是类的实例化。如果data不是函数的话,复用组件就会导致数据共享而造成组件混乱。而不用复用的Vue实例(
new Vue({ data: {} })
)的data就可以是对象就是这个原因。
ajax请求应该放在哪个生命周期
这个问题视频中说放mounted,网上感觉各有争议,我这里自己归纳下
- 放哪个生命周期看业务需求。极端点情况,不需要使用返回结果的时候,你甚至可以在beforeCreate和destroyed生命周期里面发起ajax请求。
- 当使用服务器渲染(ssr)的时候并且页面结构由ajax请求数据渲染得出,这时候需要在
created
生命周期发起请求,因为ssr没有mounted生命周期 - 当页面结构由ajax请求数据渲染得出且请求结束需要进行DOM操作的话,放在mounted生命周期。
- 当在keep-alive动态组件中需要激活时请求刷新数据,在activated发起ajax请求
- 当数据更新时,……等等
总结:ajax在哪个生命周期发起请求需要看具体的业务需求,考虑正常的ajax拿去数据初始化页面,则可以放在created生命周期,虽然和放mounted生命周期性能上的差异很小,但是如此可以兼容服务端的渲染,而不需要到时候调整代码位置。
如何将组件所有props传递给子组件
1
2<User v-bind = '$props' />
<!-- 细节知识点,了解即可 -->更多可移步此处:https://blog.csdn.net/xueyue616/article/details/105379009
如何自己实现 v-model
这里视频介绍的是组件的v-model,实现如下图,但是下图也仅仅展示了子组件的写法,父组件需要加上 v-model=”text”。详见
Vue使用
下的自定义组件的v-model
一节。如果只是普通的v-model就是:1
<input type='text' :value="text" @input="text = $event.target.value">
多个组件有相同的逻辑,如何抽离?
mixin以及mixin的一些缺点,详见
Vue使用
下的mixin
一节何时要使用异步组件
- 加载大组件
- 路由异步加载
何时用使用beforeDestroy
- 解绑自定义事件 event.$off
- 解绑自定义DOM事件,如 window.onsroll 等
- 清除定时器
- Vuex中 action 和 mutation 有何区别
- action 中处理异步, mutation不可以
- mutation做原子操作(不可分割的操作,要不全部完成,要不全部不完成)
- action 可以整合多个 mutation
请用 vnode 描述一个 DOM 结构
Vue监听 data 变化的核心 API 是上面
Object.defineProperty
深度监听,监听数组
有何缺点
详见
Vue原理
的响应式原理
一节
Vue如何监听数组变化
- Object.definePropery 不能监听数组变化
- 重新定义原型,重写 数组方法(如 push, pop 等),实现监听
- Vue3 的 Proxy 则可以原生支持监听数组的变化
diff算法的时间复杂度
- O(n)
- 在 O(3) 做了些调整使得时间复杂度降为 O(n) (同级比较,tag不同销毁重建,tag和key相同认为是相同的结点)
简述 diff 算法过程
- patch(elem, vnode) 和 patch(vnode, newVnode)
- patchVnode 和 addVnodes 和 removeVnodes
- updateChildren ( key的重要性 )
Vue为何是异步渲染,$nextTick何用
- 异步渲染(以及合并 data 修改)都是用以提高渲染性能的
- $nextTick 在 DOM 更新完之后触发回调
Vue常见的性能优化方式
- 合理使用 v-show 和 v-if
- 合理使用computed
- v-for 加 key, 以及避免v-for和v-if同时使用(如果要同时使用的情况也请用计算属性)
- 自定义事件、DOM事件、定时器及时销毁(防止内存泄漏)
- 合理使用异步组件
- 合理使用keep-alive
- data 层级不要太深 (响应式是要递归的,所以data层级尽量扁平些)
- 使用 vue-loader 在开发环境做模板编译
- webpack层面优化
- 前端通用的性能优化,如图片懒加载
- 使用ssr
Vue3学习
该节讲的有点不太好,没有太多的干货,建议根据官方文档学习,该节主要看下 响应式的实现原理 即可。
针对该情况对章节内容进行了一定的打乱调整以方便后期自己查阅
关于相关面试题感觉可以看看这篇文章:https://juejin.cn/post/7139921537896808479
Vue3 升级内容
- 全部用 ts 重写(响应式,vdom,模板编译等)
- 性能提升,代码量减少
- 会调整部分API
Vue2.x马上要过时了吗
- Vue3从正式发布到推广开来,还需要一段时间
- Vue2.x应用范围非常广,有大量项目需要维护升级
- Proxy存在浏览器兼容性问题,且不能polyfill
个人学习笔记
关于 Composition API 和 Options API 混用时,Options API 可以通过 this 获取到 setup 返回的数据,但是 Composition API 没有办法获取到 Options API 的数据
setup语法糖
1
ref 可以定义基本类型、对象类型的响应式数据
reactive 只能定义对象类型的响应式数据
课程内容
1、vue3 比 vue2 有什么优势
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织
- 更好的逻辑抽离
- 更多的性能
Composition API
2、描述 vue3 生命周期
Options API 生命周期
beforeDestory 改为 beforeUnmount
destoryed 改为 unmouted
其他沿用Vue2生命周期
Composition API 生命周期
1 | import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' |
如何看待 Composition API 和 Options API
详见官方介绍,这里只是简单说明:https://cn.vuejs.org/guide/extras/composition-api-faq.html#%E4%BB%A3%E7%A0%81%E7%BB%84%E7%BB%87
Composition API 带来了什么?
更好的代码组织 - 相同逻辑关注点的代码组织在一起,不会像Options API一样被强制拆分
更好的逻辑复用 - 通过 组合函数 实现更高效的逻辑复用
更好的类型推导 - 搭配 TS 使用,组合式 API 主要利用基本的变量和函数,它们本身就是类型友好的
Composition API 和 Options API 如何选择?
- 不建议共用,会引起混乱
- 小型项目、业务逻辑简单,用 Options API
- 中大型项目、逻辑复杂,用 Composition API
别误解 Composition API
- Composition API属于高阶技巧,不是基础必会
- Composition API是为解决复杂业务逻辑而设计
- Composition API就像 Hooks 在 React 中的地位
4、如何理解ref、toRef、toRefs
是什么
ref
- 生成值类型的响应式数据
- 可用于模板和 reactive
- 通过.value修改值
- ref sugar提案
toRef
- 针对一个响应式对象(reactive封装)的prop
- 创建一个ref,具有响应式
- 两者保持引用关系
toRefs
- 将响应式对象(reactive封装)转换为普通对象
- 对象的每个prop都是对应的ref
- 两者保持引用关系
合成函数返回响应式对象
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
26import { reactive, toRefs } from 'vue';
function useFeatureX() {
const state = reactive({
x: 1,
y: 2
})
// 逻辑运行状态,省略N行
// 返回时转换为ref
return toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下破坏结构
const { x, y } = useFeatureX()
return {
x,
y
}
}
}最佳使用方式
用reactive 做对象的响应式,用ref做值的响应式
ref 的变量命名都用 xxxRef
setup 中 返回 toRefs(state), 或者 toRef(state, ‘xxx’)
合成函数返回响应式对象时,使用toRefs
后面两点都是为了template使用响应式对象的时候可以直接引用而不是需要用 state.xxx,也就是toRef是把reactive对象的一个属性转换成ref对象,toRefs是把整个reactive对象转换成ref对象构成的普通对象
进阶,深入理解
为何需要ref
返回值类型会丢失响应式
如在 setup、computed、合成函数都有可能返回值类型
Vue如不定义ref,用户将自造ref,反而混乱
个人理解应该是 vue3的响应式是通过 proxy 实现的,proxy只能挟持对象而不能是简单数据类型,因此对于简单值类型,使用ref去创建响应式
为何需要.value
- ref是一个对象(不丢失响应式),value存储值
- 通过 value 属性的get和set实现响应式
- 用于模板、reactive时,不需要.value,其他情况都需要
为何需要 toRef toRefs
- 初衷:不丢失响应式的情况下,把对象数据 分解/扩散
- 前提:针对的是响应式对象(reactive封装的)非普通对象
- 注意:不创造响应式,而是延续响应式(老师自己创造的话)
5、vue3 升级了哪些重要内容
vue2项目需要升级到vue3版本,可以参考:https://v3-migration.vuejs.org/zh/
上面代码示例用的是vue3.0.2,在3.0.4之后需要把所有的
sayHello
改成onSayHello
- 多事件
这个我记得vue2也是支持的
- Fragment
- 移除 .sync
一个语法糖的更改,其他其实也是一样的,要通过
emit('update:title', 'xxx')
来触发.sync 语法糖更改为了 v-model
1
2
3
4
5
6
7
8
9 <!-- Vue3中下面两者等价 -->
<!-- 触发法方法 $emit('update:title', 'I am a new title!') -->
<ChildComponent v-model:title="pageTitle" />
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event">
<!-- Vue2中下面两者等价 -->
<!-- 触发法方法 this.$emit('update:title', 'I am a new title!') -->
<ChildComponent :title.sync="pageTitle" />
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event">
- 移除filter
- Teleport
- Suspense
- Composition API
6、Composition API 如何实现代码逻辑复用
- 抽离逻辑代码到一个函数
- 函数命名约定为 useXxxx 格式(React Hooks也是)
- 在 setup 中引用 useXxxx 函数
1 | import { defineComponent, ref, onMounted, onUnmounted } from 'vue'; |
7、watch 和 watchEffect 的区别是什么
- 两者都可以监听 data 属性变化
- watch 需要明确监听哪个属性
- watchEffect 会根据其中的属性,自动监听其变化
1 | import { defineComponent, reactive, ref, toRefs, watch, watchEffect } from 'vue'; |
8、setup 种如何获取组件实例
在 setup 和 其他 Composition API 中没有this
可通过 getCurrentInstance 获取当前实例
若使用 Options API可照常使用 this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import { defineComponent, getCurrentInstance, onMounted } from 'vue';
export default defineComponent({
name: 'GetInstanceView',
data() {
return {
x: 1
}
},
setup() {
console.log('this1', this) // undefined
const instance = getCurrentInstance() as any
console.log('instance', instance) // Object
onMounted(() => {
console.log('this in onMounted', this) // undefined
console.log('x', instance.data.x) // 1
})
},
mounted() {
console.log('this2', this) // Proxy(Object)
console.log('x', this.x) // 1
}
});
Proxy实现响应式
9、Vue3 如何实现响应式
- 回顾 vue2.x 的 Object.defineProperty
- 学习 Proxy 语法
- Vue3 如何用 Proxy 实现响应式
- Proxy 和 Reflect 是一对最佳拍档
- Proxy 的 set 与 Object.defineProperty 的 set 区别是Proxy的set可以监听到属性的添加
1 | const data = { |
Reflect 作用
- 和Proxy能力一一对应
- 规范化、标准化、函数式
- 替换掉 Object 上的工具函数(Object作为一个构造函数不应该集成太多的方法)
1 | const obj = { a:10, b: 20 } |
proxy实现响应式
- 深度监听,性能更好
- 可监听 新增/删除 属性
- 可监听数组变化
1 | // 测试数据 |
编译优化
10、Vue3 为何比 Vue2 快
Proxy响应式
PatchFlag
编译模板时,动态节点做标记
标记,分为不同的类型,如 TEXT PROPS
diff算法时,可以区分静态节点,以及不同类型的动态节点
https://template-explorer.vuejs.org/
hoistStatic
将静态节点的定义,提升到父作用域,缓存起来
多个相邻的静态节点,会被合并起来
典型的拿空间换时间的优化策略
cacheHandler
SSR优化
静态节点直接输出,绕过了 vdom
动态节点还是需要动态渲染
tree-shaking
- 编译时,根据不同的情况,引入不同的 API
Vite
11、Vite 是什么
- 一个前端打包工具,Vue作者发起的项目
- 借助Vue的影响力,发展较快,和webpack竞争
- 优势:开发环境下无需打包,启动快
Vite为何启动快
- 开发环境使用 ES6 Module,无需打包 – 非常快
- 生产环境使用 rollup,并不是快很多
1 | <!-- 普通使用 --> |
12、Composition API 和 React Hooks 的对比
- 前者 setup 只会被调用一次,而后者函数会被多次调用
- 前者无需 useMemo useCallback,因为 setup 只调用一次
- 前者无需顾虑调用顺序,而后者需要保证 hooks 的顺序一致
- 前者 reactive + ref 比后者 useState 要难理解
第一条是本质,第二三条是表象
React使用
React文档:https://react.dev/learn
React版本:16.4
代码地址:https://gitee.com/cheerfulion/my_public_demos/tree/master/react_learning
开胃菜:
- React组件如何通讯
- JSX的本质是什么
- context是什么,有何用途?
- shouldComponentUpdate 用途
- 描述 redux 单向数据流
- setState 是同步还是异步?
创建项目
create-react-app: https://create-react-app.bootcss.com/docs/getting-started
1 | npx create-react-app my-app |
JSX基本使用
JSX语法,条件,列表渲染,事件,组件和props(类型的检查),state和setState,组件生命周期
JSX语法,条件,列表渲染
1 | import React from 'react' |
事件
1 | import React from 'react' |
表单
1 | import React from 'react' |
父子组件通信
1 | import React from 'react' |
setState
不可变值,可能异步更新,可能会被合并
在 React 的 18.2.0 版本,没有发现同步 setState
1 | import React from 'react' |
组件生命周期
可以再了解下:shouldComponentUpdate(Render阶段,在Render之前)、getSnapshotBeforeUpdate(Pre-commit阶段,可以读取DOM,在Commit阶段前,Render阶段后)
高级使用
函数组件,受控和非受控组件,refs,Protals,context,异步组件(懒加载),性能优化,shouldComponentUpdate,纯组件,不可变值immutablejs,高阶组件,render prop
函数组件
适合仅返回JSX的情况
- 纯函数,输入props,输出JSX
- 没有实例,没有生命周期,没有state
- 不能拓展其他方法
非受控组件
相对state控制的受控组件,通过ref控制的就是非受控(个人理解)
使用场景: 操作DOM元素
1 | import React from 'react' |
Protals
组件默认会按照既定层次嵌套渲染,那么如何让组件渲染到父组件之外呢?那么就可以使用Protals让子组件逃离父组展示了。比如BFC,父组件z-index过小,fixed的场景。
1 | import React from 'react' |
context
公共信息(如语言,主题)如何传递给每个组件?用props太繁琐,用redux似乎又有点小题大做。
异步组件(懒加载)
import、React.lazy、React.Suspense
1 | import React from 'react' |
性能优化
shouldComponentUpdate(简称SCU)
默认返回true,必须配合不可变值使用(数组、对象),需要性能优化才有必要使用。
1
2
3
4
5
6
7
8// 由于默认React父组件有更新都会触发子组件更新(componentDidUpdate),因此可以通过shouldComponentUpdate判断下是否更新子组件
shouldComponentUpdate(nextProps, nextState) {
// if (nextState.count !== this.state.count) {
if (nextProps.count !== this.props.count || nextProps.length !== this.props.length) {
return true // 重新渲染
}
return false // 不重新渲染
}PureComponent 和 React.memo
不可变值 immutable.js
周边工具
redux
store,reducer,action,dispatch,单向数据流模型,中间件redux-thunkredux-saga
react-redux
provider,connect,mapStateToProps,mapDispatchToProps
react-router
React原理
函数式编程,vdom和diff算法,JSX本质,合成事件,setState和batchUpdate,组件渲染过程,前端路由
React面试真题演练
React Hooks
Webpack和babel
项目设计
项目流程
其他Vue课程
其他React课程
其他Vue面试题
其他React面试题
本文链接: http://www.ionluo.cn/blog/posts/6d298630.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!