前端面试题——React题

418 阅读11分钟

1、react中的fiber架构

1.Fiber架构背景

react的fiber架构是react 16引入,重新设计了React的协调和渲染机制,目的提高渲染性能

fiber架构:允许将渲染任务分成多个小块并分阶段完成,避免一次性阻塞主线程

16之前使用的是同步渲染,架构是堆栈协调器

在这种机制下组件的渲染是同步的,所有任务一旦开始,就必须执行到完成。

小组件树,这样做法是高效,大型组件树,渲染不发被中断,可能导致卡顿甚至阻塞用户交互。

2.Fiber架构核心理念

Fiber主要创新点是将渲染过程变为可中断,可恢复的过程,通过将渲染任务切分成多个小任务,每个任务成为一个Fieber,Fiber可以被优先级调度器打断,然后在空闲时间继续执行,

  • 可中断更新:Fiber允许将渲染任务分成多个小片段,可以在中途暂停并让出控制权给主线程,以便处理更高优先级的任务,如用户输入或动画。
  • 优先级调度:不同的更新任务会被赋予不同的优先级,React会优先处理那些高优先级任务如(用户交互),从而提高用户体验。
  • 增量渲染:Fiber允许组件的渲染过程逐步进行。

3.Fiber结构

每个Fiber节点表示组件树中的一个单元,包含一下关键信息:

  • type:当前节点类型,如函数组件、类组件或者原生DOM元素
  • key:用于节点的唯一标识符
  • child、sibling、return:构成Fiber树的父子或兄弟节点,形成链表结构,便于便利
  • props:组件的传递参数
  • stateNode:与Fiber节点的相关联的实例(如DOM节点和组件实例)
  • alternate:指向同一节点的上一次渲染的FIber节点,用于比较新旧状态的差异

4.Fiber的工作过程

Fiber架构将渲染过程分为两个阶段:

1.协调阶段:-计算并构造Fiber树

协调阶段的任务是对比当前Fiber树和树的更新,找到需要更新的部分。这个过程可以被打断。异步

  • 遍历更新:react会遍历当前组件树并创建Fiber树,通过Diff算法找到更新的节点
  • 任务切片:如有更高优先级的任务(如用户交互),React可以暂停当前任务处理高优先级的任务

2.提交阶段:-真是DOM更新

提交阶段是同步指向,不能被打断,主要将协调阶段计算出的更新应用到实际的DOM上,三个步骤

  • Before mutation:在DOM发生变化之前调用生命周期钩子(如getSnapshotBeforeUpdate)
  • Mutation:执行DOM更新、插入、删除等操作
  • Layout:更新后的回调(如componentDidMount、componentDidUpdate)会在此时处罚

5.Fiber的优先级调度

React在Fiber架构中引入优先级调用,他会根据任务优先级分配。常见的优先级包括

  • 同步优先级:需要立即处理的关键更新。如输入框中用户输入,最高优先!!!
  • 离散事件优先级:如点击按钮等用户操作。属于较高优先级,较高!!
  • 低优先级:非关键性渲染任务(动画更新),被分配到低优先级,低!

这种调度机制保证了在大型应用中,React不会因为长时间渲染任务导致界面卡顿,可以优先处理用户交互等高优先级任务。提升整体流畅性。

6.Fiber架构的优势

  • 流畅的用户体验:通过将渲染任务拆分为多个小任务,并根据优先级调度,用户在更新渲染期间扔能够进行交互
  • 可中断和恢复的渲染:渲染任务可以被打断并在稍后恢复处理,避免长时间任务阻塞主线程
  • 任务优先级:能够合理分配高优先级和低优先级的任务,确保更流畅的动画和响应性
  • 细粒度控制:Fiber提供了对更新过程的更细粒度控制,使得React在应对负载更新时性能更加出色

7.总结

React Fiber架构通过引入可中断的更新机制和优先级调度,极大的提升了复杂应用的渲染性能。Fiber架构的主要目标时使React更够在大型应用中更高效地处理渲染任务,保证UI交互的流畅性,并减少不必要的重新渲染。

  • react的diff算法

    首先 diff算法是同级别比较不跨级别 时间复杂度O(n)

    tree层 只有创建和删除操作

    component层 会判断是否是同一个类的组件

    element层 对应key的比较

2、组件之间的通讯

react是单项数据流

1、父子组件props,子组件调用回调函数可以改变父组件传的值 2、兄弟组件之间 可以借用父组件进行传值 3、跨组件之间的通讯 借用createContext方法,解构出Provide,Consumer组件provide提供数据 Consumer 接收传递的值函数组件的用法:

<Context.Provide value={需要传递的数据}>
    //接收数据先从页面解构出Context  
    import {Context} from './index'
    const 公共数据 = useContext(Context)

4、使用es6新特性完成传值 class

export default class ClearTimer {
  private static instance = new ClearTimer();
  public static get Instance() {
    return this.instance;
  }
  private themenum: string = "";
  public setThemenum(xx: string) {
    this.themenum = xx;
  }
  public getThemenum() {
    return this.themenum;
  }
}
重点 !!!使用useContext遇到的问题

1.跨组件更新问题

问题:当Context之值变化时,所有使用useContext的组件都会重新渲染,不管组件是否依赖值是否变化?

解决方法

  • 使用React.memo:使用React.memo包裹那些依赖useContext但不需要频繁更新的组件,防止不必要的渲染
  • 将上下文值拆分:可以把上下文拆分多个小的context,减少全局状态的更新。如:如果一个组件只需要使用上下文中的某个字段,可以单独为这个字段创建一个context。

2.context值更改引发的无线渲染

如果通过useConetxt使用了一个会动态变化的函数或对象,每次渲染都创建新的对象,会导致无限重新渲染

问题:由于上下文的值是引用类型,当传递给Provide的值是一个新的对象,即便对象的内容相同,React也会认为是一个新值,导致重新渲染。

解决方法

  • 使用usememo或者useCallback:在传递给Provider的值是函数或者对象时,确保使用useMemo或者useCallback来缓存,避免每次渲染生成新的引用
const value = useMemo(() => ({someState}),[someState]);
//或者
const value = useCallback(() => someFuntion(), [dependcies]);

<SomeContext.Provider value={value}>
{xxx}
<SomeContext.Provider/>

3.Context值丢失或者不更新

问题:当上下文状态更新后,某些组件依然没有获取到最新值,可能因为Provider被条件渲染,或组件更新时没有重新订阅到上下文

解决方法

  • 确保Provider始终渲染:不要将Provider放在条件语句中,这样会导致组件的订阅上下文失效。
  • 检查组件的更新逻辑:使用的shouldComonentUpate或者React.memo来优化性能时,可能会导致子组件没有更新,因此没有获取到最新的上下文值。

4.嵌套过深带来的复杂性 问题:Context层次过多,会导致代码复杂,且难以在某些大型应用中维护上下文传递顺序。

解决方法

  • 合理拆分Context:尽量避免将所有全局状态放在一个上下文中,而是将不同的逻辑拆分成多个Context,比如将用户信息、主题、语言等状态分开管理
  • 组合使用多个Provider:React支持组合多个context.Provider,可以在根组件中将多个Provider嵌套在一起,并通过Context Selector只获取需要的上下文值。
<ThemeContext.Provider value={theme}>
    <AuthContext.Provider value={auth}>
        <App/>
    <AuthContext.Provider/>
</ThemeContext.Provider>

5.与Redux或其他章台管理工具的冲突 问题:React中useContext和全局状态管理工具可能同时管理同一状态或部分相同的状态,导致状态的流向难以追踪,更新不一致。

解决方法

  • 明确使用场景useContext适合管理局部的、轻量级的状态,而全局性、复杂性应该交给专门的管理(redux)处理,避免全局和局部都处理相同的状态。
  • 大型项目合理架构:如果状态较为复杂,建议采用单一的状态管理工具。

6.SSR(服务器端渲染)中的同步问题 问题:使用SSR场景下,二如果没有正确的状态同步,客户端和服务器渲染都可能导致结构不一致

解决方法

  • 确保初始状态同步:确保在服务端渲染时提供了完整的上下文状态,避免渲染时产生意外
  • 使用合适的状态管理工具:在SSR项目中,结合Context和其他工具如(Next.js的getinitialProps)确保状态一致。

3、React的hooks

在hooks出现之前 函数组件是无状态的

16.8之后 出现了hooks 函数组件变得有状态

useCallback 缓存回调函数,适用于需要传递给子组件的事件处理函数,避免不必要的重新渲染

React.memo 缓存组件

useMemo 相当与vue的计算属性,适用于需要缓存值的场景

useState 存放数据

useRef用于获取dom元素或组件对象

useEffect模拟生命周期

两个参数 第一个参数是回调函数,模拟componentDidUpdate ,componentDidMount ,

第二个参数是数组,为空时模拟,componentDidMount

第二个为空,模拟componentDidMount ,模拟vue中的watch

第一个参数 里return 一个新的函数 模拟compontentWillUnMount

4、 useState是同步还是异步

useState是异步

setState同步异步

在合成事件和生命周期钩子中,setState是异步

在原生和定时器只同步

setState

合成事件生命周期钩子(除componentDidUpdate) 中,setState是"异步"的;

原生事件setTimeout 中,setState是同步的,可以马上获取更新后的值;

批量更新:多个顺序的setState不是同步地一个一个执行滴,会一个一个加入队列,然后最后一起执行。在 合成事件 和 生命周期钩子 中,setState更新队列时,存储的是 合并状态(Object.assign)。因此前面设置的 key 值会被后面所覆盖,最终只会执行一次更新。

函数式: setState第一个参数为函数形式时,在这个函数中可以回调拿到最新的state对象,然后函数return出的对象讲被设置成newState。this.setState((state, props) => newState)

5、类组件和函数组件的区别

2.1状态

类组件一直有状态,函数组件在16.8之前没有状态,之后才有状态

2.2写法

类组件是es6写法,需要继承React.Component并且创建render函数返回react元素

函数组件就是纯函数式 编写

2.3 函数组件 :return jsx 类组件:render有函数

2.4 调用方式

类组件需要new 实例化

函数组件 需要重新渲染

2.5生命周期

类组件有生命周期

函数组件没有生命周期

2.6this

类组件有this

函数组件没有this

6、 react中的key

react利用key 识别组件,它相当于一个身份认证

它可以提高我们diff算法的优化效率

key是react用来追踪哪些列表的元素被修改,被添加或者是被删除的辅助标示。在开发过程中我们需要保证某个元素的key在其同级元素中具有唯一性。

新旧虚拟dom比较,

7、# setState是同步还是异步

React18版本为分水岭

react18之后都是异步的

react的合成事件,setState是异步 在setTimeout、setInterval 原生js中它也是异步

react18版本之前

在react的合成事件 它是异步 在setTimeout、setInterval、原生js他是同步

8、父组件获取子组件方法

ref或者forwardRef

9、react state如何获取上一次值

使用箭头函数

10、为什么要使用reduce?

使用reduce 可以提供

11、react合成事件

react的合成事件,屏蔽了浏览器自带的事件, 解决了浏览器的不兼容,

将事件绑定在document上,按照冒泡或者捕获的路径去收集真正的事件处理函数,在此过程中会先处理原生事件,然后当冒泡到document对象后 普通事件 是绑定在真实dom上

12、为什么浏览器无法读取JSX?

浏览器只能处理JavaScript对象,而不能读取常规JavaScript对象中的JSX。所以为了使浏览器能够读取JSX,首先需要像Babel这样的JSX转化器将JSX文件转化为JavaScript对象,然后将其传给浏览器。