Vue
1.computed 和 watch 区别
- computed 根据已有值计算出新值,有缓存
- watch 用于监听现有数据变化,无缓存
watch 能取代 computed 的任何的操作,反过来不行,比如 watch 中可以进行异步操作
2.生命周期
Vue2(3)生命周期:
| 选项式 API | Hook inside setup |
|---|---|
beforeCreate:初始化一个空的 Vue 实例,data methods 等尚未被初始化,无法调用 | Not needed*(setup) |
created:Vue 实例初始化完成,data methods 都已初始化完成,可调用,但尚未开始渲染模板 | Not needed*(setup) |
beforeMount:编译模板,调用 render 函数生成 vdom ,但还没有开始渲染 DOM | onBeforeMount |
mounted:渲染 DOM 完成,页面更新。组件创建完成,开始进入运行阶段,发送网络请求可以在此时 | onMounted |
beforeUpdate:在数据发生改变后,DOM 被更新之前被调用,这里适合在现有 DOM 将要被更新之前访问它 | onBeforeUpdate |
updated:在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用,尽量不要在 updated 中继续修改数据,否则可能会触发死循环 | onUpdated |
beforeUnmount:组件进入销毁阶段,在这个阶段,实例仍然是完全正常的,移除、解绑一些全局事件、自定义事件,可以在此时操作 | onBeforeUnmount |
unmounted:卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载 | onUnmounted |
errorCaptured:捕获错误 | onErrorCaptured |
activated被 keep-alive :缓存的组件激活时调用 | onActivated |
deactivated:被 keep-alive 缓存的组件停用时调用 | onDeactivated |
3.组件通信
- 父子组件:
- 使用
props 和 emits进行通信 - 使用
$attrs(可以通过v-bind="$attrs"向下级传递) 、$parent、$refs
- 使用
- 爷孙组件:
- 使用
两次父子组件间通信来实现 - 使用
provide + inject来通信
- 使用
- 任意组件:
- vue2 使用
eventBus = new Vue()通信 - vue3 使用如
mitt库 通信 - vue2 ==>
VuexVue3 ==>Pinia
- vue2 使用
4.Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理工具
- store 是个大容器,包含以下所有内容
- state 读取状态,带有一个 mapState 辅助函数
- getters 读取派生状态,附有一个 mapGetters 辅助函数
- mutations 同步提交状态变更,附有一个 mapMutations 辅助函数,建议原子操作(每次只修改一个数据)
- actions 异步变更状态,它提交的是 mutation 间接变更状态,附有一个 mapActions 辅助函数
- Module 给 store 划分模块,方便维护代码
- namespaced: 模板开启命名空间,保证不冲突
Vue3 使用 useStore () 获得 store 实例,setup 使用 map 的两个映射方法 mapState 和 mapGetters 需要封装才能使用
注意:Pinia去除了 mutations 和 Module ,所有更改都是通过 action
5.VueRouter
Vue Router 是 Vue.js 的官方路由,用来构建 SPA 单页应用
说出一些概念
- router-link router-view 、嵌套路由、Hash 模式 和 History 模式、导航守卫、懒加载、动态路由、缓存路由
Vue-router v4 升级之后,mode: 'xxx' 替换为 API 的形式,但功能是一样的
mode: 'hash'替换为createWebHashHistory()mode: 'history'替换为createWebHistory()mode: 'abstract'替换为createMemoryHistory()
三种模式区别:
- Hash 模式是基于锚点(#),本质改变
window.location.href改变 URL 但是不刷新页面,而且 hash 值不会包含 HTTP 请求,监听hashchange事件,根据当前路由地址找到对应组件重新渲染 - History 模式是基于 HTML5 中的 History API(/),有六种模式改变 URL 不刷新页面,兼容性和 hash 模式相比略差,需要服务端支持,
History.pushState()方法改变地址栏,监听popstate事件,根据当前路由地址找到对应组件重新渲染- replaceState:替换原来的路径
- pushState:使用新的路径
- popState:路径的回退
- go:向前或向后改变路径
- forward:向前改变路径
- back:向后改变路径
- abstract 模式不修改 url ,路由地址在内存中,但页面刷新会重新回到首页
注意:Vue3 使用 useRouter() 和 useRoute() 获得 router 和 route 实例
react-router 同有三种模式,设计上和 vue-router 一样
6.双向绑定
- 一般使用
v-model / .sync(vue3被费力) 实现,v-model是 v-bind:value 和 v-on:input 的语法糖 v-bind:value实现了 data ⇒ UI 的单向绑定- 通过 Object.defineProperty API 给 data 创建 getter 和 setter,用于监听 data 的改变,data 一变就会改变 UI
v-on:input实现了 UI ⇒ data 的单向绑定- 通过 template compiler 给 DOM 添加事件监听,DOM input 的值变了就 会去修改 data
7.Vue3为什么使用Proxy
弥补 Object.defineProperty 的不足
- 深度监听、性能更好,惰性的数据响应式(需要用到才转换)
- 可监听新增/删除属性(get set deleteProperty)
- 可原生监听数组变化
- 和 Refelct 天生搭档,更加标准化函数化
缺点:
- Proxy无法兼容所有浏览器,无法polyfill
8.Vue3为什么使用Composition API
Composition API 比 mixins、extends、高阶组件 更好的原因:
- 模版中的数据来源
- 命名空间
- 性能
- 更适合 TypeScript
9.Vue3更新了什么
详细的可以看我这篇文章:40张图全面剖析Vue3核心的CompositionAPI(十四) - 掘金 (juejin.cn)
我这里概括下
源码升级:
- 源码TS重写,全新的TS支持
- 使用 Proxy 代替 Object.defineProperty 实现响应式
- 重写虚拟DOM的实现
- Tree-Shaking 和 SSR
新特性:
- Composition API(组合API)
- 新的内置组件
- Fragment
- Teleport
- Suspense
- createApp() 代替了 new Vue() 返回 app 实例
- 移除 过滤器filter、keyCode修饰符、.sync修饰符、off 和 $once
- 组件驱动动态 CSS 值
更好的周边工具:
- Vite、Pinia
10.vdom真的很快吗
- 直接进行 DOM 操作永远都是最快的(但要目标明确,不能有无谓的 DOM 操作 —— 这很难)
- 如果业务复杂,要“数据视图分离,数据驱动视图”,无法直接修改 DOM ,那 vdom 就是一个很好的选择
所以,vdom 并不比 DOM 操作更快(反而更慢,它做了 JS 运算),它只是在某个特定的场景下,无法做到精准 DOM 修改时,一个更优的选择
扩展:Svelte 不使用 vdom ,它将组件修改,编译为精准的 DOM 操作
11.如何正确的操作 DOM
mounted 和 updated 都不会保证所有子组件都挂载完成,如果想等待所有视图都渲染完成,需要使用 $nextTick
mounted() {
this.$nextTick(function () {
// 仅在整个视图都被渲染之后才会运行的代码
})
}
React
1.虚拟DOM
- 虚拟 DOM 就是虚拟节点,React 用 JS 对象来模拟 DOM 节 点,然后将其渲染成真实的 DOM 节点
- 用 JSX 语法写出来的 div 其实就是一个虚拟节点(因为 JSX 语法会被转译为 createElement 函数调用,也叫 h 函 数)
function render(vdom) {
// 如果是字符串或者数字,创建一个文本节点
if(typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom)
}
const { tag, props, children } = vdom
// 创建真实DOM
const element = document.createElement(tag)
// 设置属性
setProps(element, props)
// 遍历子节点,并获取创建真实DOM,插入到当前节点
children.map(render).forEach(element.appendChild.bind(element))
//虚拟DOM 中缓存真实 DOM 节点
vdom.dom = element
// 返回 DOM 节点
return element
}
function setProps // 略
优点:
-
DOM 操作不方便问题。以前各种 DOM API 要记,现在只有 setState
-
让 DOM 操作的整体性能更好,能(通过 diff)减少不必要的 DOM 操作
-
为 React 带来了跨平台能力,因为虚拟节点除了渲染为真实节点,还可以渲 染为其他东西
缺点:
- 性能要求极高的地方,还是得用真实 DOM 操作(太少)
- React 为虚拟 DOM 创造了合成事件,跟原生 DOM 事件不太一样,工作中要额外注意
- 所有 React 事件都绑定到根元素,自动实现事件委托
- 如果混用合成事件和原生 DOM 事件,有可能会出 bug
2.React VS Vue 的 DOM diff 算法
- DOM diff 就是对比两棵虚拟 DOM 树的算法
- 当组件变化时,会 render 出一个新的虚拟 DOM
- diff 算法对比新旧虚拟 DOM 之后,得到一个 patch,然后 React 用 patch 来更新真实 DOM
优化
传统的 tree diff 算法复杂度是 O(n^3) ,算法不可用
Vue React 都是用于网页开发,基于 DOM 结构,对 diff 算法都进行了优化
- 只在同一层级比较 ,
tag不同则直接删掉重建 - 子节点会通过
key区分 - 最终把时间复杂度降低到
O(n)
React diff 特点 - 仅向右移动
比较子节点时,仅向右移动,不向左移动
Vue2 diff 特点 - 双端比较
定义四个指针,分别比较
- oldStartNode 和 newStartNode
- oldStartNode 和 newEndNode
- oldEndNode 和 newStartNode
- oldEndNode 和 newEndNode
然后指针继续向中间移动,直到指针汇合
Vue3 diff 特点 - 最长递增子序列
Vue3 针对 key 做不同操作
- 有key,那么就使用
patchKeyedChildren方法 - 没有key,那么久使用
patchUnkeyedChildren方法
例如数组 [3,5,7,1,2,8] 的最长递增子序列就是 [3,5,7,8 ] 。这是一个专门的算法
算法步骤
- 通过 从头开始进行遍历比较找到开始的不变节点
[A, B] - 通过从尾部开始进行遍历比较找到末尾的不变节点
[G] - 此时如果旧节点多则删除,新节点多则新建
- 剩余的有变化的节点
[F, C, D, E, H]- 通过
newIndexToOldIndexMap拿到 oldChildren 中对应的 index[5, 2, 3, 4, -1](-1表示之前没有,要新增) - 计算最长递增子序列得到
[2, 3, 4],对应的就是[C, D, E],即这些节点可以不变 - 剩余的节点,根据 index 进行新增、删除
- 通过
该方法旨在尽量减少 DOM 的移动,达到最少的 DOM 操作
3.setState
setState 默认异步更新
componentDidMount() {
this.setState({val: this.state.val + 1}, () => {
// 回调函数可以拿到最新值
console.log('callback', this.state.val)
})
console.log(this.state.val) // 拿不到最新值
}
setState 有时同步更新
- 根据
setState的触发时机是否受 React 控制 - 如果触发时机在 React 所控制的范围之内,则异步更新
- 生命周期内触发
- React JSX 事件内触发
- 如果触发时机不在 React 所控制的范围之内,则同步更新
- setTimeout setInterval
- 自定义的 DOM 事件
- Promise then
- ajax 网络请求回调
setState 默认会合并
- 多次执行,最后 render 结果还是 1
componentDidMount() {
this.setState({val: this.state.val + 1})
this.setState({val: this.state.val + 1})
this.setState({val: this.state.val + 1})
}
setState 有时不会合并
- 同步更新,不会合并
- 传入函数,不会合并 (因为对象可以
Object.assign,函数无法合并)
this.setState((prevState, props) => {
return { val: prevState.val + 1 }
})
setState 是 React 最重要的 API,使用要注意:
- 使用不可变数据
- 合并 vs 不合并
- 异步更新 vs 同步更新
setState是同步还是异步
- setState 是同步执行,state 都是同步更新,只不过让 React 做成了异步的样子
- 因为要考虑性能,多次 state 修改,只进行一次 DOM 渲染
- 所以不是微任务也不是宏任务
4.React 如何实现组件间通信
- 父子组件通信:props + 函数
- 爷孙组件通信:两层父子通信或者使用 Context.Provider 和 Context.Consumer
- 任意组件通信:消息订阅-发布、集中式管理(Redux 、Mobx)
5.React生命周期
- 挂载时调用 constructor,更新时不调用
- 更新时调用 shouldComponentUpdate 和 getSnapshotBeforeUpdate,挂载时不调用
- should... 在 render 前调用,getSnapshot... 在 render 后调用
- 请求最好放在 componentDidMount 里
6.高阶组件 HOC?
- 参数是组件,返回值也是组件的函数
例如
- React.forwardRef
- ReactRedux 的 connect
- ReactRouter 的 withRouter
参考阅读:「react进阶」一文吃透React高阶组件(HOC) - 掘金 (juejin.cn)
6.Redux
Redux 是一个状态管理库/状态容器
工作流程
- 组件通过 dispatch 方法触发 Action( type + payload 荷载)
- Store 接受 Action 并将 Action 分发给 Reducer
- Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
- 组件订阅了 Store 中的状态, Store 中的状态更新会同步到组件
React-redux核心概念:
- connect(mapStateToProps,mapDispatchToProps)(Component)
connect方法接受两个参数,返回一个高阶组件
connect方法的第一个参数是mapStateToProps方法,将store中的state传递到组件的props中
onnect方法的第二个参数是mapDispatchToProps方法,将store中的dispatch传递到组件的props中
connect方法可以帮我们订阅store,当store中状态发生变更的时候,帮助我们重新渲染组件
Typescript
1.TS和JS区别
-
语法层面:TypeScript = JavaScript + Type(TS 是 JS 的超集)
-
执行环境层面:浏览器、Node.js 可以直接执行 JS,但不能执行 TS(Deno 可以执行 TS)
-
编译层面:TS 有编译阶段,JS 没有编译阶段(只有转译阶段和 lint 阶段)
-
编写层面:TS 更难写一点,但是类型更安全
-
文档层面:TS 的代码写出来就是文档,IDE 可以完美提示。JS 的提示主要靠 TS
2.any、unknown、never 的区别
any VS unknown
二者都是顶级类型(top type),任何类型的值都可以赋值给顶级类型变量:
let foo: any = 123; // 不报错
let bar: unknown = 123; // 不报错
但是 unknown 比 any 的类型检查更严格,any 什么检查都不做,unknown 要求先收窄类型:
const value: unknown = "Hello World";
const someString: string = value;
// 报错:Type 'unknown' is not assignable to type 'string'.(2322)
const value: unknown = "Hello World";
const someString: string = value as string; // 不报错
如果改成 any,基本在哪都不报错。所以能用 unknown 就优先用 unknown,类型更安全一点
never
never 是底类型,表示不应该出现的类型,这里有一个尤雨溪给出的例子:
interface A {
type: 'a'
}
interface B {
type: 'b'
}
type All = A | B
function handleValue(val: All) {
switch (val.type) {
case 'a':
// 这里 val 被收窄为 A
break
case 'b':
// val 在这里是 B
break
default:
// val 在这里是 never
const exhaustiveCheck: never = val
break
}
}
3.type 和 interface 的区别
官方给出的文档说明:
- 组合方式:interface 使用 extends 来实现继承,type 使用 & 来实现交叉类型
- 扩展方式:interface 可以重复声明扩展(合并),type 一个类型只能声明一次
- 范围不同:type 适用于基本类型,interface 一般不行
- 命名方式:interface 会创建新的类型名,type 只是创建类型别名,并没有新创建类型
4.TS 工具类型
-
将英文翻译为中文。
-
Partial 部分类型
-
Required 必填类型
-
Readonly 只读类型
-
Exclude 排除类型
-
Extract 提取类型
-
Pick / Omit(主要针对对象)排除 key 类型
-
ReturnType 返回值类型
-
-
举例说明每个工具类型的用法