react

277 阅读32分钟

一、fiber架构

fiber是一种可用于描述组件树的数据结构,他代表一种可中断,可恢复的异步渲染任务,fiber架构主要是为了解决 react在渲染大型项目时的性能问题,并支持异步渲染,优先级调度的高级特性

  1. Fiber节点的含义
  • Fiber表示React中的一个组件或DOM节点。
  • 每个Fiber节点对应一个React元素(如组件、DOM节点等),并包含了该元素的相关信息(如类型、状态、子节点等)。
  1. Fiber架构的核心特性
  • 异步渲染:将渲染任务分解为多个小任务,支持任务的暂停、中断和恢复。
  • 优先级调度:根据任务的优先级动态调整渲染顺序,确保高优先级的任务优先执行。
  • 增量渲染:将渲染任务分成多个小片段,逐步完成。
  • 错误处理:支持错误边界(Error Boundaries),更好地捕获和处理组件树中的错误。
  1. Fiber的双缓存机制
  • React使用双缓存机制来管理Fiber树,包括当前树(Current Tree)和工作树(WorkInProgress Tree)。
  • 当前树表示当前显示的UI对应的Fiber树,而工作树则是正在构建的新Fiber树。
  • 当工作树构建完成后,React会将其切换为当前树,从而实现UI的更新。
  1. Fiber架构的工作流程
  • React根据JSX生成虚拟DOM树,并对每个虚拟DOM节点生成对应的Fiber节点,构建起Fiber树的层级结构。
  • 执行初次渲染时,React从根节点开始递归遍历Fiber树,执行组件的渲染,将组件渲染到DOM上。
  • 在更新阶段,React会根据新的React节点与当前Fiber树生成新的工作树,并通过深度优先遍历和Diff算法比较新旧节点,生成更新标记(删除、移动、插入等)。
  • 构建完成后,工作树替换当前树完成DOM更新。

二、Fiber解决的问题

  1. JS引擎与页面渲染引擎的互斥问题
    由于JS引擎和页面渲染引擎两个线程是互相排斥的,当JS线程长时间占用主线程时,渲染层面的更新就会被迫等待,导致用户可能感受到卡顿。
  2. 解决方案
  • 任务拆分与优先级调度
    Fiber架构将渲染更新过程拆分成多个子任务,每次只做一小部分。通过给每个任务赋予不同的优先级,React能够确保高优先级的任务能够优先执行,并在必要时中断低优先级的任务。这种机制使得React能够更加灵活地应对复杂的更新场景。

  • 合作式调度与requestIdleCallback
    React利用window.requestIdleCallback()方法,在浏览器的空闲时间段内执行低优先级的更新任务。这种方法允许React在不影响用户交互和页面渲染的情况下,完成后台和低优先级的更新工作。

    • 合作式调度
      React在完成一部分任务后,会主动将控制权交回给浏览器,让浏览器有时间进行页面的渲染和其他高优先级任务的执行。当浏览器有空闲时间时,React再继续执行之前未完成的任务。这种机制使得React和浏览器能够更加和谐地共存,共同为用户提供流畅的体验。

一、setState是同步还是异步

  1. 在React的时间处理函数和生命周期方法中,setState是异步
  • 为什么:在 React 的事件处理函数或生命周期方法中可能会频繁的调用setState,如果每次都立即触发就会造成大量不必要的渲染;所以会在 “事件处理完成” 或者 “声明周期方法执行完毕后” 统一更新渲染
  • 解决异步问题 : 利用setState 的回调函数
  1. 在React的合成事件或生命周期方法之外(如:setTimeout、Promise),setState是同步
  • 为什么:因为在这些上下文中,通常不会有大量setState调用需要合并,所以没必要设计为异步

二、React类组件生命周期

  • -----挂载------
  1. constructor(props)
  • 构造函数,用于初始化组件的state和绑定事件处理器。
  • 传入props作为参数。
  1. render()
  • 返回组件的JSX结构。
  • 无参数。
  1. componentDidMount()
  • 组件被插入到DOM中后立即调用。

  • 无参数。

  • 通常用于执行网络请求或启动定时器

  • ------更新------

  1. shouldComponentUpdate(nextProps, nextState)
  • 返回一个布尔值,决定组件是否应该因为props或state的改变而重新渲染。
  • 传入nextProps和nextState作为参数。
  • 默认返回true,但通常用于性能优化。
  1. getSnapshotBeforeUpdate(prevProps, prevState)
  • 在DOM更新之前调用,用于在可能即将发生的DOM变化前捕获一些信息(例如滚动位置)。
  • 传入prevProps和prevState作为参数。
  • 返回一个值,该值将作为第三个参数传递给componentDidUpdate。
  1. componentDidUpdate(prevProps, prevState, snapshot)
  • DOM更新后立即调用。

  • 传入prevProps、prevState和snapshot作为参数。

  • 用于在更新后执行DOM操作。

  • ------卸载------

  1. componentWillUnmount()
  • 无参数。
  • 通常用于执行清理操作,如取消网络请求、清除定时器或解绑全局事件监听器。

三、函数组件模拟生命周期

useEffect:类似于类组件的componentDidMount、componentDidUpdate和componentWillUnmount生命周期方法的组合。

四、React常用的Hooks

  1. useState 包含两个元素 第一个为内部当前状态值,第二个是更新的函数
  2. useMemo 返回一个新的 “值” 第一个参数是一个函数,第二个是一个依赖项;当依赖项发生变化的时候才会重新调用并返回一个新的 “值” 缓存起来;(避免重复计算)
  3. useCallback 返回一个新的 “函数” 第一个参数是一个函数,第二个是一个依赖项;当依赖项发生变化的时候才会重新调用并返回一个新的 “函数” 缓存起来;(避免重复渲染)
  4. useReducer 接受一个reducer函数和初始状态,并返回一个包含当前状态和dispatch函数的数组。通过dispatch函数,可以触发状态的更新,然后重新渲染组件
  5. useEffect 第二个参数必须是一个数组
  • 空数组 :只在组件挂载和卸载时运行一次;
  • 什么都不传:每次组件渲染后都运行;
  • 对象:要确保对象的引用在生命周期内保持不变,否则每次渲染后都运行;
  • 空对象:每次渲染后都运行
  • 字符串和数字:他们的值实际变化时才重新运行

useLayoutEffectuseLayoutEffect 会在所有的 DOM 变更之后同步调用,即在浏览器绘制之前执行。这意味着你可以使用它来读取 DOM 布局并同步触发重渲染。然而,由于它会阻塞浏览器的绘制,因此应谨慎使用以避免性能问题。 useLayoutEffect和useEffect的区别

一、 执行时机

  1. useEffect
    • 在组件渲染到屏幕之后异步执行。
    • 不会阻塞浏览器的绘制和更新。
  2. useLayoutEffect
    • 在浏览器进行布局和绘制之前同步执行。
    • 会在 DOM 更新后立即执行,但在浏览器绘制之前完成。

二、 性能影响

  1. useEffect
    • 由于是异步执行,不会阻塞页面的渲染,对用户交互的响应性影响较小。
    • 如果副作用操作耗时较长,可能会在用户操作后有短暂的延迟才看到效果。
  2. useLayoutEffect
    • 由于是同步执行,如果操作耗时较长,会阻塞页面的渲染,可能导致页面卡顿,影响用户体验。
    • 适用于需要在 DOM 更新后立即同步执行的操作,如读取 DOM 布局或同步更新样式等。

三、使用场景

  1. useEffect

    • 适用于大多数常见的副作用处理场景,如数据获取、订阅事件、手动修改 DOM(这些操作不会直接影响页面布局和视觉呈现)。
    • 也用于清理定时器、事件监听器等资源。
  2. useLayoutEffect

    • 适用于需要在 DOM 更新后立即同步更新布局或样式的场景,如动画、测量元素尺寸等。
    • 由于它在浏览器绘制之前执行,可以避免由于异步的 useEffect 可能导致的闪烁现象。
  3. useRef useRef保存的数据变化不会触发UI的渲染,如果想在React绑定或者解绑的时候运行代码就需要 使用ref回调实现

  4. create.ref 和 useRef的区别


  1. createRef
  • createRef 是一个 React API,通常在类组件中使用来创建引用(refs)。
  • 每次组件重新渲染时,createRef 都会返回一个新的引用对象。
  • 主要用来获取当前组件挂载到 DOM 树中的实际 DOM 节点。
  • 使用 createRef,会在组件的构造函数中创建 ref,并将其附加到组件的实例上。
  1. useRef
  • useRef是一个 React Hook,只能在函数组件中使用。
  • useRef在整个组件的生命周期中返回一个稳定的引用对象,即使组件重新渲染也不会改变。
  • 可以用来获取 DOM 节点,还可以用来存储任意值,这个值在组件的整个生命周期中保持不变,除非你手动改变它。
  • 使用 useRef,你会在组件内部直接声明 ref。
  1. React.memo 和 useMemo的区别
  • React.memo: 是一种高阶组件,避免在 props 没有改变的情况下重新渲染组件。如果组件的props没有变化,React.memo 就会复用上一次渲染的结果
  • 区别useMemo 是用来“记住”计算结果的,以避免在不必要的时候重新计算;而 React.memo 是用来“记住”组件渲染结果的,以避免不必要的组件渲染

五、自定义hooks

其实就是一个函数,但是要遵守 React强制的约定和原则

  • 必须以 “use” 开头;
  • 只能在函数最外层调用Hooks:不要在循环、条件或嵌套函数中调用;
  • 只能在 React函数组件 或者 其他自定义Hooks中调用

六、state 和 props 区别是啥

  1. state是组件自己管理数据,可变;props是外部传入的参数,不可变;
  2. 没有state的是 “无状态组件”,有state的是 “有状态组件”;
  3. 多用props,少用 state(多写无状态组件:增加复用性,可读性和易维护性,提高性能)

七、为什么React中的props是只读的

  1. 预测性:可以确信在任何时候,组件接收到的 props 都是一致的
  2. 组件隔离:相同的输入(props)应该产生相同的输出,避免了一个组件意外更改了另一个组件的状态
  3. 单项数据流:确保从父组件到子组件的数据是不可逆的 不是双向绑定;
  4. 易于测试:不需要担心外部状态的更改;
  5. 性能优化:当一个组件的父组件重新渲染时,React 可以快速地检查 props 是否更改,以确定是否需要重新渲染此组件。

八、react虚拟DOM原理 、diff算法

  • 模拟DOM树:react会用节点对象来模拟真实的DOM树结构,这个模拟的DOM就叫虚拟DOM;
  • 比较新旧DOM原理 如下:
  1.  分层比较 diff 算法会分别递归比较两个虚拟 DOM 树的根节点,以及它们的子节点。会逐层比较每一对节点。
  2.  节点类型比较 当比较两个节点时,首先会检查它们的类型(例如,都是 div 元素,或者都是自定义组件)。如果节点类型不同,React 会销毁旧节点及其所有子节点,并创建新节点及其子节点。
  3.  同层节点比较 对于同层级的节点,它会根据 key 属性(如果有的话)来识别哪些节点是“稳定”的,即它们在新旧两个树中都是存在的,只是位置可能发生了变化。对于没有 key 的节点,React 会自己进行最佳猜测,但结果可能不是最优的。
  4. 列表组件优化 对于列表组件(例如,使用 map 函数渲染的多个元素),React会给每个列表项一个唯一的 key 属性。这样,当列表发生变化时(例如,添加、删除或重新排序项),React 可以利用这些 key 就可以快速识别哪些项是稳定的,哪些项是新增的,哪些项是需要删除的。
  5. 属性更新 当比较两个相同类型的节点时,React 会比较它们的属性。对于改变了的属性,React 会更新相应的 DOM 属性(只会处理实际发生改变的属性不会更新全部)
  6. 子节点比较 如果子节点有 keys,React 会根据 keys 来识别应该更新、删除还是重新排序的元素。如果没有 keys,React 则按顺序进行比较(可能会造成性能损失)

九、react 函数组件 和 类组件的区别

  1. 定义方式: 函数组件是使用JavaScript函数定义的组件,它接收props作为参数并返回一个React元素。类组件则是使用ES6类定义的组件,它继承自React.Component,并在类中定义render方法以返回React元素。
  2. 性能:函数组件由于没有实例化和复杂的生命周期方法,因此通常比类组件具有更好的性能
  3. 状态管理:在函数组件中,状态管理通常通过使用React Hooks来实现。类组件中,状态是通过在类中定义this.state并在构造函数中初始化来管理的。
  4. 生命周期::类组件具有完整的生命周期方法,而函数组件则使用useEffect来模拟生命周期行为。
  5. 逻辑复用:函数组件更易于实现逻辑复用,因为可以将一些常用的逻辑封装成自定义Hook,类组件的逻辑复用通常需要通过高阶组件(HOC)或render props等模式来实现。
  6. 使用场景:对于简单的组件,函数组件通常更加简洁和易于理解。然而,对于需要处理复杂状态、生命周期或需要与其他库或框架集成的组件,类组件可能更加适合。
  7. 语法简洁性:函数组件的语法更为简洁,因为它们只是纯函数,在代码量上通常更为轻量。
  8. this关键字的使用:在类组件中,this关键字用于访问组件的状态和属性。函数组件中,没有实例化的概念,因此不能使用this关键字。如果需要访问状态或属性,通常是通过React Hooks(如useState和useContext)来实现的。

十、react 组件通信

父子 :父组件通过 props 将数据传递给子组件。这是 React 中最常用的组件通信方式,遵循单向数据流的原则,即数据从父组件流向子组件。

image.png 子父 : 父组件向子组件传递一个回调函数作为 props,子组件调用这个函数,并将数据作为参数传递。父组件就能接收到子组件传来的数据。

image.png 兄弟组件通信:兄弟组件之间无法直接通信,它们需要通过共同的父组件来传递数据 跨级 :创建一个 Context 对象并使用 Provider 组件来包裹子组件,就可以将数据传递给这些子组件。子组件可以使用 useContext 钩子来访问这些数据

image.png 其他 : redux

十一、受控组件 和 非受控组件

  1. 受控组件 可以随时从 React 的 state 中读取表单的值,数据由React组件的状态(state)管理,输入表单,React 的 state 就会立即更新。

优势

-   **数据集中管理**:所有的表单数据都存储在 React 的状态中,使得状态管理更为集中且可预测。
-   **易于维护**:由于数据由组件状态管理,因此可以更方便地进行验证、格式化和其他处理。
-   **更好的用户体验**:可以实时响应用户输入,提供即时的反馈。
  1. 适用场景:适用于大多数表单场景,特别是当表单交互复杂、需要实时验证或格式化输入时
  2. 非受控组件 不需要每次输入都记录下来,数据由DOM本身管理,需要表单的数据时,你可以通过 ref去直接询问表单元素的值

特点

  • 简化代码:在某些简单表单场景中,使用非受控组件可以减少代码量。
  • 直接操作 DOM:通过 ref 属性获取 DOM 节点,并直接操作其值。
  1. 适用场景:适用于非常简单的表单场景,例如只需要在提交时验证数据的表单,或者没有复杂用户交互的表单。此外,在某些特定情况下,如文件上传输入框,非受控组件也是必要的选择。

十二、react合成事件、react为什么要有自己的合成事件,如何阻止不同时期的事件

  1. react合成事件 首先呢 React 的 合成事件 是基于浏览器的事件机制 自身实现了一套事件机制, 包括了事件注册,事件的合成、事件冒泡、事件派发等;合成事件是 React 模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器;
  • 当真实的 dom 触发事件的时候,会构造 React 合成事件对象,按照冒泡 或 捕获的路径去收集真正的事件处理函数,这个过程中会处理原生事件,然后当冒泡到 document 对象后,在处理 React 事件;
  • React 是在 document 上监听所有支持的事件,当时事件发生 并 冒泡到 document 的时候,React 会将事件的内容封装并交给真正的处理函数运行;
  • 合成事件采用了事件池,合成事件会被统一放进事件池中管理,这样可以减少对内存的开销。
  • React 通过合成事件,模拟捕获 和 冒泡阶段的,从而达到不同浏览器的兼容。
  1. 为什么要有自己的合成事件
  • 浏览器兼容性:将不同的平台事件抹平成统一的合成事件,确保react在不同的浏览器上有一致的行为
  • 性能优化:react事件不会被立即释放,而是会存储在一个数组中,事件触发的时候直接从数组中取避免了频繁的垃圾回收
  • 事件统一管理和事件机制:实现了事件的统一管理,所有的事件都挂载在 document上(17版本后事件委托挂载在容器上),然后通过事件代理机制处理
  1. 阻止不同时期的事件
  • 合成事件的冒泡:e.stopPropagation()
  • 合成事件和最外层document上的冒泡:e.nativeEvent.stopImmediatePropagation()
  • 合成事件与除了最外层document之外原生事件的冒泡:通过e.target判断是否等于该元素

十三、优化Render项目的手段

  1. 按功能拆分组件:将大组件拆分为多个小组件,每个组件负责单一功能,然后在父组件中整和这些组件,提高代码的可读性和可维护性。
  2. 创建自定义Hooks:利用React 16.8中的Hooks特性,创建可复用的自定义Hooks,简化组件逻辑,提高代码复用率。
  3. 虚拟滚动:对于长列表或大数据量的渲染,可以使用虚拟滚动技术,只渲染当前可见的部分,提高渲染性能。
  4. 优化动画和过渡效果:避免使用复杂的动画和过渡效果,减少CPU和GPU的消耗。如果必须使用,可以优化动画的帧率和持续时间。
  5. 合并请求:将多个网络请求合并为一个,减少HTTP请求的数量,降低网络延迟。
  6. 使用缓存:对于不经常变化的数据,可以使用缓存策略,减少重复请求的次数。React 16.8中的Hooks可以配合自定义Hooks来实现数据缓存的功能。
  7. 代码分割
  • 配置Webpack:在Webpack配置文件中使用splitChunks插件来配置代码分割。
  • 动态加载:使用动态import()语法来按需加载代码块。
  • 处理路由:在React Router中配置路由时,使用lazySuspense来实现代码分割和懒加载。
  • 压缩资源:对图片、CSS和JavaScript文件进行压缩,减少文件大小,提高加载速度。
  1. 高阶组件:高阶组件是一个函数,这个函数接收一个组件作为参数,并返回一个新的组件
  2. React.memo
  3. shouldComponentUpdate 和 PureComponent
  • shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 就行,shouldComponentUpdate(nextProps, nextState),其中 nextProps 是即将传入的新的 props,nextState 是即将传入的新的 state。
  1. 引入 why did you render 包来捕获不必要的更新 image.png image.png

十四、React中 state 和 props 有什么区别

  • props是父组件传递过来的数据,具有不可变性;
  • state在组件中创建的,在constructor中初始化,可以修改;
  1. props为什么是只读的
  • 单项数据流:如果能够修改props会打破单项数据流,让数据流动变得难以预测;
  • 维护组件封装状态:让组件的状态管理封装起来并且可以控制;
  • 解决副作用和BUG:规定props为只读可以帮助开发者编写出更清晰且易于维护的代码。

十四、React类组件为什么修改状态用 setState 而不是 this.state.xxx

  • React 需要知道何时更新和重新渲染界面。
  • 我们改变的状态可能会影响其他界面或组件,React 要确保所有东西保持同步。
  • setState 由 React 安排和批处理,这可以提高应用性能,因为它不会每次改变都重新渲染界面,而是集中处理多个改变然后一次性更新界面。

十五、React为什么要自定义合成事件

  • 是对于跨浏览器兼容性和对性能优化的考虑;
  1. 跨浏览器兼容性:react会处理不同浏览器之间的差异,让你的代码能一样的工作;
  2. 性能优化:react通过合成事件系统来处理事件而不是直接绑定原生事件到DOM上;
  3. 合成事件池:所有事件在回调之后都会回收、重用,而不是每次事件触发都重新创建一个;
  4. 更容易控制:提供reactAPI更容易控制这些事件行为,如:event.stopPropagation()阻止冒泡
  5. 与react更新机制协调:UI渲染和事件处理更好的同步

十六、commpoentWillUnMount 生命周期对应的hooks怎么写

image.png

十七、闭包问题

  • 闭包陷阱是由于回调函数捕获了其定义时作用域内的变量,并且这些变量在后续渲染中发生了变化,但回调函数仍然使用最初捕获的值
  • 函数组件利用hooks去捕获某个变量的状态,并且在异步操作(setTimeout、Promise),或者在回调函数中引用了这个状态
  • 例如: image.png

image.png App组件初次渲染执行useEffect中的定时器每次执行都会将count加1,但是运行后 发现只会执行一次而且 打印的都是0

  1. 原因:组件每次渲染的时候都会重新执行函数中的代码,但是重新执行的时候count变量在闭包中,所以只会在首次渲染的时候会捕获到函数里面count的值,之后渲染即使count发生了变化,但是之前捕获到的变量仍旧会保持原来的值,就会形成闭包陷阱
  2. 解决
  • 利用useRef:用useRef保存数据,每次使用最新数据的时候从ref里面去取就行了
  • 利用useEffect第二个参数去更新

十八、redux

redux如何做持久化的存储:用 redux-persist 这个库

  1. 配置 redux-persist
    接下来,需要设置 redux-persist 与 Redux Store 一起工作。首先,配置你的 root reducer 以支持持久化。

    javascript复制代码
    	import { persistReducer } from 'redux-persist';
    
    	import storage from 'redux-persist/lib/storage'; // 使用 localStorage 作为默认存储
    
    	import { combineReducers } from 'redux';
    
    	import yourReducer from './yourReducer';
    
    	 
    
    	const persistConfig = {
    
    	  key: 'root',
    
    	  storage,
    
    	};
    
    	 
    
    	const rootReducer = combineReducers({
    
    	  yourReducer: yourReducer,
    
    	  // 任何其他的 reducer
    
    	});
    
    	 
    
    	const persistedReducer = persistReducer(persistConfig, rootReducer);
    
  2. 创建 Redux Store 与 Persistor
    使用持久化后的 reducer 创建 Redux Store,并创建持久化器(Persistor)。

    javascript复制代码
    	import { createStore } from 'redux';
    
    	import { persistStore } from 'redux-persist';
    
    	import persistedReducer from './reducers'; // 使用持久化的 reducer
    
    	 
    
    	const store = createStore(persistedReducer);
    
    	const persistor = persistStore(store);
    
  3. 在应用中集成 PersistGate
    在你的 React 应用中,使用 PersistGate 组件包裹应用的根组件,确保在恢复状态后再渲染 UI。一般来说,PersistGate 放置在 Provider 之后。

    javascript复制代码
    	import React from 'react';
    
    	import ReactDOM from 'react-dom';
    
    	import { Provider } from 'react-redux';
    
    	import { PersistGate } from 'redux-persist/integration/react';
    
    	import App from './App';
    
    	import store, { persistor } from './store';
    
    	 
    
    	ReactDOM.render(
    
    	  <Provider store={store}>
    
    	    <PersistGate loading={null} persistor={persistor}>
    
    	      <App />
    
    	    </PersistGate>
    
    	  </Provider>,
    
    	  document.getElementById('root')
    
    	);
    
  4. 处理异步加载(可选)
    PersistGate 的 loading 属性允许你在恢复状态之前显示一个加载组件。可以是任何 React 组件,比如一个简单的加载指示器。 一、核心三要素是什么之间的关系呢

1. 核心三要素

  • 单一数据源

  • state只读

  • 使用纯函数来执行修改

  • 单向数据流:view-->action-->reducer-->store-->view循环

    1. 为什么redux要求 reducer 是纯函数

  • 保证状态变更可预测、可回溯

  • 需要纯函数的特性(无副作用、相同的数据输出)与状态管理的关系

二、原理与工作流程 创建action然后通过dispatch通知到redux的store,store接收到后会触发响应的函数,然后会根据action的type做出响应的改变并返给store,最后store保存新的状态并通知组件触发UI的更新 三、redux如何实现状态共享

  • 通过单一的store和react-redux的Provider注入上下文,以及context api实现的状态共享 context api的作用: 状态共享、避免手动传递props、动态更新

四、中间件

1. redux中间件的原理是什么,如何实现一个logger中间件 它允许在action被发送到reducer之前拦截并处理这些action,或者执行一些副作用(effects),本质是通过包装 store的dispatch方法,在action和reducer之间插入一些自定义的逻辑 实现一个 logger中间件

image.png redux-Thunk和recux-saga的区别:

1. thunk:

  • 异步处理方式:允许编写一个返回函数的 action creator,这个函数可以接收 dispatch 和 getState作为参数,然后就允许在函数内部进行异步操作,并在适当时候调用 dispatch 来分发同步action
  • 适用场景:适合简单的场景; 如:只需要简单的网络请求并且请求完成后更新redux-store

2. saga:

  • 异步处理方式:用Es6的Generator函数实现异步的管理,它允许你暂停、恢复函数的执行,使异步代码看起来像同步代码一样,saga提供了一系列的"effects",如:take、put、call等用于描述异步操作和控制流程。
  • 适用场景:处理复杂的异步操作场景,比如需要管理多个并发请求、取消操作等

五、性能优化

  1. 函数组件中可以用 React.memo对组件进行包裹他会对props和state进行浅比较,避免不必要的渲染
  2. 使用useSelector用于比较当前的state和上次的state,没有变化就不会重新渲染
  3. 避免在selector中进行复杂的计算,selector应该只用于派发数据

六、useSelector和connect的区别

  1. useSelector基于Hooks,更加的简介
  2. connect自动做浅比较,useSelector需要手动处理

十九、react-router

在单页面应用中,一个web项目只有一个html页面,当页面加载完成之后,就不会因为用户的操作 去进行页面的重新加载或者跳转 特性:

  • 改变url后不让浏览器向服务器发送请求
  • 在不刷新页面的前提下动态改变浏览器地址栏中的URL地址

两种模式

  1. hash模式(HashRouter)
  • URL格式:在URL后面加上 #
  • 工作原理:#后面是路由部分,路由部分发生变化的时候浏览器不会向服务器发送请求,而是会触发hashChange事件 然后 SPA可以监听这个事件,然后根据新的 hash 值去渲染对应的组件
  • 优点:可以很好的兼容,可以在不支持 Html5 history Api中使用。
  • 缺点:不美观 路由中带有 #
  1. history模式
  • URL格式:没有 # 与常规网站的相同
  • 工作原理:根据Html5中的 historyApi来实现路由,根据 history.pushState 和 history.replaceState来改变URL,同时不刷新页面,当URL改变的时候 页面会触发 popState事件,但是SPA一般不会依赖这个来进行对路由的处理,而是会主动监听URL的变化并渲染对应的组件
  • 优点:URL看起来比较美观
  • 缺点:需要服务器端支持,如果不支持的话,用户输入URL地址可能会返回 404

二十、react的设计思想是什么

  • 组件化:React的核心思想是组件化,即将UI拆分成小的、可重用的部件,以提高可维护性和可重用性。这些组件可以嵌套和组合,以构建复杂的用户界面。每个组件拥有自己的状态和生命周期。
  • 单项数据流:数据自顶向下流动,父组件通过props向子组件传递数据
  • 声明式编程:只需声明在任何给定时刻UI应该是什么样子,React负责更新DOM以匹配该描述
  • 虚拟DOM:React在内存中维护一个虚拟DOM树,当状态改变时,React创建新的虚拟DOM树并与旧树比较,只将必要的更改应用到实际DOM,从而减少了直接操作DOM的次数,提高了性能。
  • 函数式编程与不可见性:强调纯函数、不可变数据和副作用管理。React鼓励使用不可变数据更新状态,例如通过创建新对象/数组(而非直接修改原数据)来更新状态。不可变性可以使代码更加安全,因为它可以避免在不同组件之间共享状态时出现潜在的问题。

二十一、react类组件中 super 和 super(props)的区别

  • 不传:直接调用super()而不传递props在React组件中可能会导致问题,因为React的基类(即React.Component)的构造函数期望能够接收到props参数,以便正确地初始化组件的状态和生命周期
  • 传递 :当你使用super(props)时,你实际上是在调用React.Component的构造函数,并将props作为参数传递给它。这样,React的内部机制就能够正确地处理props,并将其存储在组件实例上,以便在组件的方法中访问

二十二、hooks为什么要写在顶层

一、确保调用顺序一致:算是他设计的约束,确保调用在每次渲染时顺序保持一致; 二、 遵循 React 的设计原则:就是确保组件状态的清晰可预测,提高了代码的可读性和可维护性; 三、技术实现上的限制:react用的是Fiber架构来控制管理组件的渲染更新,每个组件都会生成一个节点(fiberNode),组件内使用的hook会以链表的形式挂载在节点(FiberNode)的 memoizedState上面,然后他每次都会以固定的顺序来遍历fiber树,并根据之前遍历的生成节点(FiberNode)来复制和更新hooks链表,所以如果Hooks的调用顺序发生变化可能导致找不到对应的hooks; 四、避免潜在的问题:还可以避免一些潜在的问题,如果在条件语句或循环中使用 Hooks,可能会导致在闭包中访问到的状态不是最新的,或者函数引用在多次渲染中不一致;

二十三、Vite为什么比Webpack快

  1. 利用现代浏览器对ES Modules(ESM)的原生支持

    • Vite直接利用现代浏览器对ESM的原生支持,在开发环境下可以直接运行源代码,
    • Webpack得先进行打包。
  2. 按需编译策略

    • Vite采用了按需编译的策略,即只有当请求某个模块时,才会对该模块进行编译
    • Webpack在构建时会对整个项目进行扫描和分析,无论模块是否被使用,都会被打包进最终的输出文件中
  3. 使用Esbuild预构建依赖

    • Vite使用Esbuild预构建依赖。Esbuild是一个使用Go语言编写的打包工具,其构建速度比以Node.js编写的打包器要快得多。
    • Go语言在编译和执行速度上具有优势,使得Esbuild能够更快速地完成依赖的预构建。
  4. 更简洁的构建流程

    • 在生产环境下,Vite会使用Rollup进行打包。Rollup在处理ES模块方面有很好的性能表现,使得Vite生成的生产包通常比较简洁,构建速度也相对较快。

二十三、Es Module 和CommonJs

  1. 模块化的目的与意义
  • 目的:解决全局污染和依赖管理混乱的问题
  • 意义:可以提高代码的可读性、可复用性、可维护性

ES Modules与CommonJs的区别

  1. 语法与用法
  • ES Modules使用exportimport关键字,而CommonJs使用requiremodule.exports/exports
  • ES Modules支持静态分析和优化,而CommonJs主要在运行时确定依赖关系。
  1. 加载与执行
  • ES Modules是异步加载的,而CommonJs是同步加载的。
  • ES Modules在执行阶段执行模块,但依赖关系在预处理阶段确定;而CommonJs在执行阶段分析模块依赖并执行。
  1. 应用场景
  • ES Modules主要用于浏览器环境和现代JavaScript开发环境,支持树摇(tree shaking)等优化。
  • CommonJs主要用于Node.js环境,是Node.js的默认模块化规范(尽管Node.js也支持ES Modules)。

ES Modules(ES6 Modules)

  1. 基本特性
  • ES Modules是ECMAScript 2015(ES6)引入的官方模块系统。
  • 使用exportimport关键字进行模块的导出和导入。
  • 支持静态分析和优化,因为模块的依赖关系在编译时就已经确定。
  1. 导入与导出
  • 导入: 使用import关键字导入模块
  • 导出:export导出关键字导出变量、函数、类等
  1. 动态绑定与静态分析
  • 使用动态绑定,导入和导出的变量都指向内存中的同一位置。当导出模块的值发生变化时,导入模块中的值也会相应更新。
  • 由于依赖关系在编译时确定,因此可以进行可靠的静态分析。
  1. 加载与执行
  • ES Modules是异步加载的,不会阻塞页面的渲染。
  • 在浏览器环境中,可以通过<script type="module">标签引入ES Module。
  • ES Modules在执行阶段执行模块,但依赖关系在预处理阶段就已经确定。

CommonJs

  1. 基本特性
  • CommonJs是一种模块化规范,主要用于Node.js环境。
  • 使用require函数导入模块,使用module.exportsexports对象导出模块。
  1. 导入与导出
  • 导入: 使用require函数导入模块。
  • 导出: 将需要导出的变量、函数、类等赋值给module.exports对象或exports对象
  1. 缓存机制与循环引用
  • 使用动态绑定,导入和导出的变量都指向内存中的同一位置。当导出模块的值发生变化时,导入模块中的值也会相应更新。
  • 由于依赖关系在编译时确定,因此可以进行可靠的静态分析。
  1. 加载与执行
  • CommonJs模块是同步加载的,即在执行require函数时会立即加载并执行模块文件。
  • CommonJs模块在执行阶段分析模块依赖,并采用深度优先遍历(depth-first traversal)来执行模块。

默认导出和命名导出的区别

默认导出引入用:import MyComponentAlias from './MyComponent'

  • 使用export default关键字进行导出。
  • 一个模块(文件)中只能有一个默认导出。
  • 导入时,可以使用任意名称来引用导出的内容,因为默认导出是“匿名的”,即它不与特定的名称绑定。

命名导出引入用:import { MyHeader, MyFooter } from './MyComponents';

  • 使用export关键字进行导出,可以在函数、变量或类声明之前,也可以在模块的末尾统一导出。
  • 一个模块可以有多个命名导出。
  • 导入时,必须使用与导出时相同的名称(或大括号中的别名)来引用导出的内容。