2024React前端面试题

1.Diff算法

Diff算法比较过程

第一步:

patch函数中对新老节点进行比较 如果新节点不存在就销毁老节点 如果老节点不存在,直接创建新的节点 当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部

第二步:

patchVnode函数比较两个虚拟节点内部 如果两个虚拟节点完全相同,返回 当前vnode 的children 不是textNode,再分成三种情况 - 有新children,没有旧children,创建新的 - 没有新children,有旧children,删除旧的 - 新children、旧children都有,执行`updateChildren`比较children的差异,这里就是diff算法的核心 当前vnode 的children 是textNode,直接更新text

第三步:

updateChildren函数子节点进行比较

  • 第一步 头头比较。若相似,旧头新头指针后移(即 `oldStartIdx++` && `newStartIdx++`),真实dom不变,进入下一次循环;不相似,进入第二步。
  • 第二步 尾尾比较。若相似,旧尾新尾指针前移(即 `oldEndIdx--` && `newEndIdx--`),真实dom不变,进入下一次循环;不相似,进入第三步。
  • 第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即 `oldStartIdx++` && `newEndIdx--`),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。
  • 第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即 `oldEndIdx--` && `newStartIdx++`),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。
  • 第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即 `newStartIdx++`);否则,vnode对应的dom(`vnode[newStartIdx].elm`)插入当前真实dom序列的头部,新头指针后移(即 `newStartIdx++`)。

但结束循环后,有两种情况需要考虑:

新的字节点数组(newCh)被遍历完(`newStartIdx > newEndIdx`)。那就需要把多余的旧dom(`oldStartIdx -> oldEndIdx`)都删除,上述例子中就是`c,d`; - 新的字节点数组(oldCh)被遍历完(`oldStartIdx > oldEndIdx`)。那就需要把多余的新dom(`newStartIdx -> newEndIdx`)都添加。

React中key的作用

React组件在更新的时候,react就会生成新的完整的虚拟DOM树与之前的虚拟dom进行比对,然后再对有更新的节点相关的位置进行更新。

对比之前往往需要进行匹配和比对,为了匹配的更精准,react希望在列表循环等位置去手动为Element添加key属性,这样对比的时候就可以通过查找key来进行精准匹配。

2.React Hook

React Hook定义:

从具象上来说就为函数组件(纯函数)提供副作用能力的 React API,从抽象意义上来说是确定状态源的解决方案。

Hook 是如何运行的:React在处理函数组件时直接将其作为函数调用,Hook 在函数组件「第一次执行」时的表现和「重渲染」时的表现是不一样的。React 为了保证以同样的姿势调用 Hook,开发者却能在「第一次执行」函数组件和无数次的「重渲染」中得到想要的结果,函数组件「第一次执行」和「重渲染」所调用的 Hook 实际上指向的是不同的 Hook。函数组件在「第一次执行」时所使用的 Hook (useXXX) 指向的是对应的 mountXXX,而在更新时,Hook 指向的是对应的 updateXXX。

Hook 是如何存储的:

React 处理 class 组件的方式是将 class 实例化,所以 class 组件将方法、状态、钩子作为成员属性直接使用就可以了。而 React 在处理函数组件时是将其当作函数调用的,没有实例可言,自然也不能将「状态」或是「副作用」挂在原型上。所以只能将 Hook 放在一个和函数组件有关联的地方,即函数组件在渲染过程中对应的 Fiber Node。函数组件在渲染时将 Hook 按照调用顺序以链表的形式挂在 Fiber Node 上面。其中 Fiber Node 的属性 memoizedState 用来指向Hook 链表的第一个节点,Hook 的属性 memoizedState 用来存放 state 值或 effect回调函数。React 在「第一次执行」函数组件时,组件内部每调用一个 Hook,React 就会在组件的 Hook 链表末尾挂载一个 Hook。在「重渲染」,每调用一次 Hook 会先将其从原链表中取出,进行相应「更新」操作后,再将其挂到新的链表上。如果在嵌套的逻辑中(比如 if 或者 switch )调用了 Hook,就会发生错位的问题。

Hook 是如何实现的:

作为函数而被调用的函数组件,每一次渲染都会生成新的作用域。如果想在函数组件中持久化管理、存储状态,还不想造成全局污染等问题,只能使用闭包;在函数组件初始化时,useReducer 做的事情:1、把自己挂到链表上;2、记录「初始值」;3、返回「初始值」和触发更新的 dispatch 函数。函数组件更新时 useReducer 的逻辑:1、从 Hook 链表中读取当前指向的 Hook;2、 顺序执行当前 Hook 的更新动作队列;3、记录下更新后的状态;4、返回更新后的状态和 dispatch 函数。

如果我们将 setState 放在了一个不受限制(比如顶层作用域、没有依赖参数的 useEffect )的地方会导致「无限」重渲染。 原因就在于每一次 setState 都会触发重渲染(如果是同一setState 在同一次渲染被多次调用只会触发一次),而每次重渲染又会执行setState,这就是个死循环;

每次函数组件「重渲染」时所获取的 state,都是上一次渲染时被塞进更新队列中的更新动作执行后的结果,且每次返回的 state 都是一个全新的 state,而不是旧 state 的引用,state 在当前渲染阶段是不可变的。这样就类似于对当前整个组件中的状态形成了一个快照,而不是实时的。

Effect Hook 的实现其实很简单,函数组件「第一次执行」时,Effect Hook 会把开发者传入的回调函数放到当前 Fiber Node 的更新队列 fiber.updateQueue 内,并在执行 DOM 更新之后逐一调用。

在函数组件「重渲染」时 Effect Hook 会将前后的依赖列表进行比对,如果列表为空或者前后依赖列表值的比对一致,此 Effect Hook 会被标记为不需要执行,但它依然存在于 Hook 链表当中,这样做是为了保证不会发生 Hook 错位的问题。如果依赖列表值的比对前后不一致或是没有依赖列表,此 Effect 会被标记为需要执行,回调函数会在执行 DOM 更新之后被调用。 Effect 回调函数的返回值会在组件 unmount 时执行。

react中useEffect使用async await报错

useEffect 钩子函数的一个特性是清理功能,即return函数。如果你从 useEffect 钩子函数返回任何东西,它必须是一个清理函数,此函数将在组件卸载时运行。相当于类组件中的 componentWillUnmount 生命周期方法。

在 JavaScript 中, async...await 会让程序在等待异步任务完成后才会继续执行。 异步函数也总是返回一个 Promise;如果函数还没有返回,则返回值会自动包装在 Promise 中。

useCallback

允许在多次渲染中缓存函数的 React Hook。fn:想要缓存的函数。此函数可以接受任何参数并且返回任何值。dependencies:有关是否更新 fn 的所有响应式值的一个列表。

useContext

可以读取和订阅组件中的 context。向组件树深层传递数据。

useMemo

是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。在组件的顶层调用 useMemo 来缓存每次重新渲染都需要计算的结果。calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。

3.React生命周期

React的生命周期中常用的有:

constructor,负责数据初始化。

render,将jsx转换成真实的dom节点。

componentDidMount,组件第一次渲染完成时触发。

componentDidUpdate,组件更新完成时触发。

componentWillMount 函数的触发时机是在组件将要装载,在组件render之前调用

componentWillUnmount,组件销毁和卸载时触发。

不常用的有:

getDerivedStateFromProps,

你可能感兴趣的:(前端面试题,前端)