Skip to content

1. React 中的 ErrorBoundary 吗,它有那些使用场景

嵌套的比较深的组件存在出错的风险,组件自身没有容错机制,会逐层交给外层组件处理。这个过程会导致整个组件树销毁。页面结果就是白屏。而且生产环境不会报出有效的错误信息,不好定位问题。 使用 ErrorBoundary 就是在可能出错的组件上套一层组件,在这个新的组件中去容错

2. react hooks,它带来了那些便利

  • HOC 嵌套地狱(高阶组件嵌套地狱)
  • 逻辑复用
  • 不用操心讨厌的 this 的问题

React HOC

HOC 是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。 具体来说,HOC 是一个函数,接受一个组件作为参数,并返回一个新的组件。

jsx
function withLogging(Component) {
  return class WithLogging extends React.Component {};
}

问题

  1. 尽量不要随意修改下级组件需要的 props

是因为修改父级传给下级的 props 是有一定风险的,可能会造成下级组件发生错误。比如,原本需要一个 name 的 props,但是在 HOC 中给删掉了,那么下级组件或许就无法正常渲染,甚至报错。

  1. Ref 无法获取你想要的 ref

3

为什么 react 需要 fiber 架构,而 vue 不需要?

其他问题:

  • Fiber 为什么是 React 性能的一个飞跃?

对于这个问题,首先需要知道在 web 中卡顿的原因,主要有以下几点:

  1. CPU 计算量不大,但 DOM 操作非常复杂(比如说你向页面中插入了十万个节点)。这种场景下不管你做不做时间分片,页面都会很卡。

  2. CPU 计算量非常大。理论上时间分片在这种场景里会有较大收益,但是人机交互研究表明,除了动画场景外,大部分用户不会觉得 10 毫秒和 100 毫秒有很大区别。也就是说,时间分片只在 CPU 需要连续计算 100 毫秒以上的情况下才有较大收益

一、什么是 React Fiber 架构? React Fiber 是 React 从 v16 开始引入的新架构,它是一种重新设计的 协调(Reconciliation)机制,主要目标是:

  • 支持异步渲染(interruptible rendering)
  • 更好地控制渲染优先级
  • 实现时间分片(Time Slicing)
  • 提升动画和交互的流畅度

二、为什么 React 需要 Fiber?

  1. 函数式编程的局限性

React 是声明式的 UI 框架,核心理念是函数式编程。这意味着组件是纯函数,更新时会重新执行整个函数生成新的 UI 树。

原来的 React 协调过程是递归调用的,同步、不可中断。 一旦开始更新,就必须一口气完成,哪怕更新任务很重。 这会导致主线程长时间阻塞,掉帧,影响用户体验。

所以 React 团队引入 Fiber,将原本的递归更新改成了一个可中断的“链表式遍历”过程。

  1. 需要实现时间切片 / 并发模式(Concurrent Mode)

React 团队希望让渲染变成“增量可中断”的,这样可以:

  • 把大任务分割成小任务,在帧空闲时间执行(requestIdleCallback/MessageChannel)
  • 提高用户交互流畅性
  • 支持更复杂的异步特性(如 Suspense、Concurrent Rendering)

Fiber 正是这个异步可中断架构的核心。

三、Vue 为什么不需要 Fiber?

Vue(尤其是 Vue 3)虽然也有响应式和虚拟 DOM,但它的设计哲学和目标不同,所以暂时不需要 Fiber 这样的架构。

  1. 响应式系统设计不同 Vue 使用的是响应式系统(基于 Proxy),通过依赖追踪来精准追踪组件依赖的数据:
  • 哪个数据变了,只更新依赖它的组件或 DOM 片段。
  • 精准更新,避免了全树 diff 的问题。

而 React 的状态变更是触发函数重渲,必须手动优化(如 memo, useCallback, useMemo)避免性能浪费。 2. 更新过程可控且较轻量 Vue 在更新过程中,会批量收集变更(nextTick),进行合并更新。

  • 它是同步递归的,但通常递归深度较浅。
  • 加上响应式系统限制了更新范围,性能足够好,不需要复杂的调度器。
  1. 没有 Concurrent Mode 的需求

Vue 当前没有对标 React Concurrent Mode 的完整并发渲染模式。

  • Vue 的设计更偏向于“快速更新 + 精准响应”
  • React 更注重可预测性 + 并发渲染控制

请准确说出下面这段代码在 react@18 中各个 log 语句的打印顺序

jsx
import { useEffect } from "react";
import { createRoot } from "react-dom/client";

export const App = ({ name }) => {
  console.log("log:", 5);

  Promise.resolve().then(() => {
    console.log("log:", 6);
  });

  useEffect(() => {
    console.log("log:", 7);
  });

  return (
    <div>
      <h1>Hello {name}!</h1>
      <p>Start editing to see some magic happen :)</p>
    </div>
  );
};

const root = createRoot(document.getElementById("root"));

console.log("log:", 2);
root.render(<App name="StackBlitz" />);
console.log("log:", 3);

Promise.resolve().then(() => {
  console.log("log:", 4);
});

setTimeout(() => {
  console.log("log:", 1);
}, 0);

1. 我们应该在什么场景下使用 useMemo 和 useCallback?

这两个方法都是用户记忆的,useMemo 是用来缓存计算结果的,useCallback 是用来缓存函数的。

  • useMemo : 用于避免昂贵的计算的,如我们对于数据的转换,数组的排序等,从而避免每次渲染都执行一次。
  • useCallback : 用于避免在每次渲染时都创建新的函数实例。 特别是如果我们将函数作为 props 传递给子组件时,useCallback 会非常有用。

1. React Portals 有什么用?

React Portals 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的方式。通常,组件的渲染输出会被插入到其在组件树中的父组件下,但是 Portals 提供了一种穿透组件层次结构直接渲染到任意 DOM 节点的方法。

React Portals 的作用: 父子结构逃逸:React Portals 允许你将子组件渲染到其父组件 DOM 结构之外的地方,这在视觉和位置上「逃逸」了它们的父组件。

样式继承独立:使用 Portal 的组件通常可以避免父组件样式的影响,易于控制和自定义样式。

事件冒泡正常:尽管 Portal 可以渲染到 DOM 树中的任何位置,但是事件冒泡会按照 React 组件树而不是 DOM 树来进行。所以,尽管组件可能被渲染到 DOM 树的不同部分,它的行为仍然像常规的 React 子组件一样。

React Portals 的使用场景: 模态框:最常见的场景之一就是模态对话框,这时候对话框需要覆盖应用程序的其余部分(包括可能存在的其他元素如遮罩层),而且往往模态框的样式不应该受到其它 DOM 元素的影响。

浮动菜单:对于那些需要覆盖其它元素的浮动菜单或下拉式组件,React Portal 可以使这些组件渲染在最外层,避免被其他 DOM 元素的样式或结构干扰。

提示/通知:用于在界面上创建提示信息,如 Toasts 或 Snackbars,这些通常会浮动在内容之上并在固定位置显示。

全屏组件:对于需要全屏显示而不受现有 DOM 层级影响的组件(如图片库的全屏视图、视频播放或者游戏界面)。

第三方库的集成:有时候需要将 React 组件嵌入由非 React 库管理的 DOM 结构中,此时 Portal 可以非常有用。

总之,Portals 提供了一种灵活的方式来逃离父组件的限制,帮助开发者更加自由和方便地进行 UI 布局,同时也有助于维护组件结构的整洁和一致性。

代码使用举例 假设我们想创建一个模态框(Modal)组件,我们会希望这个模态框在 DOM 中是在最顶层的,但在 React 组件树中它应该在逻辑上保持在其父组件下。使用 React Portals 可以很容易地实现这一点。

首先,我们在 public/index.html 中,添加一个新的 DOM 节点,作为 Portal 的容器:

html
<!-- index.html -->
<div id="app-root"></div>
<!-- React App 将会挂载在这里 -->
<div id="modal-root"></div>
<!-- Modal 元素将会挂载在这里 -->

接着,我们创建一个 Modal 组件,它会使用 ReactDOM.createPortal 来渲染其子元素到 #modal-root:

js
// Modal.js
import React from "react";
import ReactDOM from "react-dom";

class Modal extends React.Component {
  render() {
    // 使用 ReactDOM.createPortal 将子元素渲染到 modal-root 中
    return ReactDOM.createPortal(
      // 任何有效的 React 孩子元素
      this.props.children,
      // 一个 DOM 元素
      document.getElementById("modal-root")
    );
  }
}

export default Modal;

现在,我们可以在应用程序的任何其他组件中使用这个 Modal 组件了,不论它们在 DOM 树中的位置如何:

js
// App.js
import React from "react";
import Modal from "./Modal";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { showModal: false };
  }

  handleShow = () => {
    this.setState({ showModal: true });
  };

  handleClose = () => {
    this.setState({ showModal: false });
  };

  render() {
    return (
      <div className="App">
        <button onClick={this.handleShow}>显示模态框</button>

        {this.state.showModal ? (
          <Modal>
            <div className="modal">
              <div className="modal-content">
                <h2>我是一个模态框!</h2>
                <button onClick={this.handleClose}>关闭</button>
              </div>
            </div>
          </Modal>
        ) : null}
      </div>
    );
  }
}

export default App; 在以上代码中,无论 Modal 组件在 App 组件中的位置如何,模态框的渲染位置总是在 #modal-root 中,这是一个典型的使用 React Portals 的例子。上述代码中的模态框在视觉上会覆盖整个应用程序的位置,但在组件层次结构中它仍然是 App 组件的子组件。

3.react 和 react-dom 是什么关系?

react 和 react-dom 是两个与 React 生态系统密切相关的 npm 包,它们在使用 React 构建用户界面时扮演不同的角色:

react

  • react 包含了构建 React 组件所必需的核心功能,例如创建组件类(如 React.Component),创建元素(如使用 React.createElement),还有新的 React 16+ 特性中的 Hooks(如 useState 和 useEffect)。
  • 它提供了组件生命周期管理、组件状态管理以及 React 元素(用于描述 UI 长相的对象)的创建。
  • react 实现了 React 的核心算法,包括对组件状态的更新以及虚拟 DOM 的概念。 简而言之,react 包对于任何使用 React 的应用程序都是一个必需的依赖,无论该应用程序是运行在浏览器还是其他环境中。

react-dom

  • react-dom 提供了一些让 React 能够与 DOM 互动的方法。在浏览器中,它把 React 组件渲染到真实的 DOM 节点上,并且处理用户的交互(如点击、输入等事件)。
  • 主要的方法是 ReactDOM.render(),它将 React 组件或者元素渲染到指定的 DOM 容器中。在 React 18+ 中,这个角色由 ReactDOM.createRoot().render() 接手。
  • 如果你在使用服务端渲染(Server-Side Rendering, SSR),那么你会使用 react-dom/server 中的方法,如 ReactDOMServer.renderToString() 或 ReactDOMServer.renderToStaticMarkup()。这些方法允许你把 React 组件渲染成初始的 HTML 字符串。
  • 当 React 组件需要被集成到现有的非 React 应用中,或者需要执行如测试和服务端渲染等操作时,通常需要使用 react-dom 包。 它们之间的关系

React 使用了所谓的“适配器模式”(Adapter Pattern),react 包提供平台独立的解决方案,而像 react-dom 这样的包则提供针对特定平台的方法。这允许 React 的核心能够被跨平台使用,例如在浏览器(通过 react-dom)、移动设备(通过 React Native 的 react-native)、VR 设备(通过 react-vr)等。

当你在浏览器中构建 React 应用程序时,你通常会同时安装并使用这两个包。在引导你的应用程序时,你将使用 react 包来定义你的组件,然后用 react-dom 包将你的顶层组件渲染到页面中的 DOM 元素上。这样的分离也为服务器端渲染或在其他渲染目标上使用 React 打下了基础。

4. React 中为什么不直接使用 requestldlecallback?

React 在性能优化方面的一个关键组件是调度器(Scheduler),它负责在渲染的过程中合理安排工作,以减少用户的等待时间以及避免单个任务占用过多的主线程时间,从而提高渲染性能。React 在 18.0 版本后引入了新的调度器机制,提供了更好的性能体验。

那么,为什么 React 不直接使用 requestIdleCallback 而要自己实现调度器呢?

  • 控制精细度: React 需要比 requestIdleCallback 更高的控制精细度。requestIdleCallback 是基于浏览器的空闲时间进行调度的,而 React 调度器可以根据组件优先级、更新的紧急程度等信息,更精确地安排渲染的工作。

  • 跨浏览器兼容性: requestIdleCallback 直到 2018 年才是浏览器中较普遍支持的 API。React 需要一个能够跨各个版本或框架的解决方案,以实现一致的性能体验。

  • 时间切片: React 使用一种称为“时间切片”(time slicing)的技术,允许组件分布在多个帧中渲染以维持流畅的 UI。这依赖于 React 自己对任务和帧的精确控制,而不是依赖浏览器的 requestIdleCallback。

  • 更丰富的特性: React 调度器提供了比 requestIdleCallback 更丰富的特性和更加详细的调度策略,这包括:

    • Immediate 模式,用于同步渲染,当它是必需的时候。
    • User-blocking 模式,用于任务需要尽快完成,但能够容忍一定延迟,比如交互动画。
    • Normal 和 Low 模式,用于不同优先级的更新。
  • 复杂功能的实现: React 使用调度器实现某些特定的特性,比如:

    • Fiber 架构,允许 React 在类组件上实现 Concurrent 特性。
    • 在客户端渲染和服务器端渲染之间实现一致性。
  • 优化生态工具: 对于 React 生态中的其他工具和实现(如 react-native、fast-refresh 等),它们可能需要特定或不同的调度策略。

  • 未来兼容性: React 团队可以更好地在自己控制的调度器中实现未来的优化和特性,而不受浏览器 API 变更的影响。

最后,调度器是 React 架构中的一个重要部分,它让 React 能够实现更丰富和灵活的用户界面渲染逻辑。尽管 requestIdleCallback 可以被用来实现一些调度器的特性,但是完全使用它将限制 React 进一步优化的可能性,并迫使 React 依赖于浏览器的调度行为,这可能不符合 React 的长期发展和技术策略。

6. 子组件是一个 Portal,发生点击事件能冒泡到父组件吗?

这其实需要看你怎么进行事件的绑定的,

  • React 事件

对于 React 内部的合成事件,其冒泡规则不是按照 DOM 树的结构来决定的,而是由 FiberNode 树结构来决定的,所以对于 Portal 组件来说,虽然其 DOM 结构不在当前父节点下,但是其 FiberNode 结构还是在父 FiberNode 下,所以其事件依然可以冒泡到父组件。

  • 原生事件

对于原生事件,其冒泡规则是按照 DOM 树的结构来决定的,所以对于 Portal 组件来说,其事件是无法冒泡到父组件的。

7.说说你对 ReactHook 的闭包陷阱的理解,有哪些解决方案?

js
function App() {
  const [count, setCount] = useState(1);
  useEffect(() => {
    setInterval(() => {
      console.log(count);
    }, 1000);
  }, []);
}

无论怎么点击 setCount 按钮,控制台打印的 count 都是 1。 原因是 useEffect 中使用的 count 是闭包中的 count,而不是 useState 中的 count,所以每次渲染时,useEffect 中的 count 都是 1。

解决方案:

  1. 使用 useRef
  2. 将 count 作为 useEffect 的依赖项

8.说说 React render 方法的原理?在什么时候会被触发?

9. 说说 React 事件和原生事件的执行顺序

在 React 中,合成事件和原生事件的触发顺序是先合成事件,然后是原生事件。

React 使用了一种称为"合成事件"的机制来处理事件。当你在组件中使用事件属性(例如 onClick)时,React 会在底层创建合成事件,并将其附加到相应的 DOM 元素上。合成事件是 React 自己实现的一套事件系统,它通过事件委托和其他技术来提供更好的性能和一致的事件处理方式。

当触发一个合成事件时,React 会首先执行事件的处理函数,然后会调用合成事件的 stopPropagation()方法来阻止事件冒泡。如果处理函数调用了 stopPropagation(),则合成事件会终止,不再触发原生事件。

如果合成事件没有被终止,并且对应的 DOM 元素上还有原生事件监听器,React 会触发相应的原生事件。原生事件是由浏览器提供的,React 并没有对其进行改变或拦截。

因此,合成事件和原生事件的触发顺序是先合成事件,然后是原生事件。这意味着在事件处理函数中,你可以放心地使用合成事件对象,而不需要担心原生事件的影响。

为何有一些文章是说, 原生事件先执行?

原生事件先执行的说法是因为在 React 早期的版本中,React 使用事件委托的方式来处理事件。事件委托是指将事件处理函数绑定在父元素上,然后利用事件冒泡机制,通过父元素捕获并处理子元素的事件。这种方式会导致在事件冒泡阶段,父元素的事件处理函数会先于子元素的事件处理函数执行。

在这种情况下,如果一个组件有一个合成事件和一个原生事件绑定在同一个元素上,原生事件的处理函数会在合成事件的处理函数之前执行。这就造成了一些文章中提到的原生事件先执行的观察结果。

然而,从 React v16 开始,React 改变了事件处理的方式,不再使用事件委托,而是直接将事件处理函数绑定在目标元素上。这样做的好处是提高了性能,并且保证了事件处理函数的执行顺序与绑定顺序一致。

因此,根据 React 的最新版本,合成事件会先于原生事件执行。如果你发现有一些旧的文章提到原生事件先执行,那可能是因为这些文章对 React 的早期版本进行了描述,不适用于目前的 React 版本。

10, 说说对受控组件和非受控组件的理解,以及应用场景?

  • 受控组件

受我们控制的组件,组件的状态全程响应外部数据

如简单的 UI 展示组件

  • 非受控组件

一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态

如 Input 组件

14.说说你对 immutable 的理解?如何应用在 react 项目中?

一、Immutable

Immutable,不可改变的,在计算机中,即指一旦创建,就不能再被更改的数据

对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象

Immutable 实现的原理是 Persistent Data Structure(持久化数据结构):

用一种数据结构来保存数据 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费 也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享)

如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享

如下图所示:

图示

二、如何使用 使用 Immutable 对象最主要的库是 immutable.js

immutable.js 是一个完全独立的库,无论基于什么框架都可以用它

其出现场景在于弥补 Javascript 没有不可变数据结构的问题,通过 structural sharing 来解决的性能问题

内部提供了一套完整的 Persistent Data Structure,还有很多易用的数据类型,如 Collection、List、Map、Set、Record、Seq,其中:

  • List: 有序索引集,类似 JavaScript 中的 Array

  • Map: 无序索引集,类似 JavaScript 中的 Object

  • Set: 没有重复值的集合

主要的方法如下:

  • fromJS():将一个 js 数据转换为 Immutable 类型的数据
js
const obj = Immutable.fromJS({ a: "123", b: "234" });
  • toJS():将一个 Immutable 数据转换为 JS 类型的数据
  • is():对两个对象进行比较
js
import { Map, is } from "immutable";
const map1 = Map({ a: 1, b: 1, c: 1 });
const map2 = Map({ a: 1, b: 1, c: 1 });
map1 === map2; //false
Object.is(map1, map2); // false

is(map1, map2) // true

  • get(key):对数据或对象取值

  • getIn([]) :对嵌套对象或数组取值,传参为数组,表示位置

js
let abs = Immutable.fromJS({ a: { b: 2 } });
abs.getIn(["a", "b"]); // 2
abs.getIn(["a", "c"]); // 子级没有值

let arr = Immutable.fromJS([1, 2, 3, { a: 5 }]);
arr.getIn([3, "a"]); // 5
arr.getIn([3, "c"]); // 子级没有值

如下例子:使用方法如下:

js
import Immutable from "immutable";
foo = Immutable.fromJS({ a: { b: 1 } });
bar = foo.setIn(["a", "b"], 2); // 使用 setIn 赋值
console.log(foo.getIn(["a", "b"])); // 使用 getIn 取值,打印 1
console.log(foo === bar); //  打印 false

如果换到原生的 js,则对应如下:

js
let foo = { a: { b: 1 } };
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b); // 打印 2
console.log(foo === bar); //  打印 true

三、在 React 中应用 使用 Immutable 可以给 React 应用带来性能的优化,主要体现在减少渲染的次数

在做 react 性能优化的时候,为了避免重复渲染,我们会在 shouldComponentUpdate()中做对比,当返回 true 执行 render 方法

Immutable 通过 is 方法则可以完成对比,而无需像一样通过深度比较的方式比较

在使用 redux 过程中也可以结合 Immutable,不使用 Immutable 前修改一个数据需要做一个深拷贝

jsx
import '_' from 'lodash';

const Component = React.createClass({
  getInitialState() {
    return {
      data: { times: 0 }
    }
  },
  handleAdd() {
    let data = _.cloneDeep(this.state.data);
    data.times = data.times + 1;
    this.setState({ data: data });
  }
}

使用 Immutable 后:

jsx
getInitialState() {
  return {
    data: Map({ times: 0 })
  }
},
  handleAdd() {
    this.setState({ data: this.state.data.update('times', v => v + 1) });
    // 这时的 times 并不会改变
    console.log(this.state.data.get('times'));
  }

同理,在 redux 中也可以将数据进行 fromJS 处理

js
import * as constants from "./constants";
import { fromJS } from "immutable";
const defaultState = fromJS({
  //将数据转化成immutable数据
  home: true,
  focused: false,
  mouseIn: false,
  list: [],
  page: 1,
  totalPage: 1,
});
export default (state = defaultState, action) => {
  switch (action.type) {
    case constants.SEARCH_FOCUS:
      return state.set("focused", true); //更改immutable数据
    case constants.CHANGE_HOME_ACTIVE:
      return state.set("home", action.value);
    case constants.SEARCH_BLUR:
      return state.set("focused", false);
    case constants.CHANGE_LIST:
      // return state.set('list',action.data).set('totalPage',action.totalPage)
      //merge效率更高,执行一次改变多个数据
      return state.merge({
        list: action.data,
        totalPage: action.totalPage,
      });
    case constants.MOUSE_ENTER:
      return state.set("mouseIn", true);
    case constants.MOUSE_LEAVE:
      return state.set("mouseIn", false);
    case constants.CHANGE_PAGE:
      return state.set("page", action.page);
    default:
      return state;
  }
};

15.说说 ReactJsx 转换成真实 DOM 过程?

整个转换过程分为三个阶段:

  • 解析 JSX 语法,将其转换成 React 元素对象。

这一步由 Babel 插件完成。当遇到 JSX 语法时,Babel 会将其转换成 React.createElement 函数调用。其中大写的标签就作为组件类型,将其作为对象类型进行传递。 如果是小写的标签,就作为普通的 DOM 标签进行传递。

js
React.createElement("img", {
  src: "avatar.png",
  className: "profile",
});
React.createElement(Hello, null);
  • 将 React 元素对象转换成 React 内部的 Fiber 节点。

这个是在 React 的 beginWork 阶段完成的,在这个阶段会根据节点对象的 type 属性来判断节点的类型,如

普通元素节点 => 字符串类型 对象类型 => 根据 $$typeof 来判断 Class 类型 => 类组件 Function 类型 => 函数组件 或者 类组件

  • 将 Fiber 节点渲染成真实的 DOM 节点。

这个是在 React 的 completeWork 阶段完成的,将 Fiber 节点渲染成真实的 DOM 节点,并存储到 fiberNode.stateNode 属性中。

16. 说说你在 React 项目是如何捕获错误的?

17.说说 React 服务端渲染怎么做?原理是什么?

18. ReactFiber 是如何实现更新过程可控?

更新过程的可控主要通过将之前的在原生执行栈中去遍历处理整个 vdom,改成将一个个节点转换成对应的 FiberNode 节点 ,体现在下面几个方面:

  • 任务拆分
  • 任务挂起、恢复、终止
  • 任务具备优先级

任务的拆分

React Fiber 之前是基于原生执行栈,每一次更新操作会一直占用主线程,直到更新完成。这可能会导致事件响应延迟,动画卡顿等现象。

在 React Fiber 机制中,它采用"化整为零"的战术,将调和阶段(Reconciler)递归遍历 VDOM 这个大任务分成若干小任务,每个任务只负责一个节点的处理。 再通过时间分片,在一个时间片中执行一个或者多个任务。这里提一下,所有的小任务并不是一次性被切分完成,而是处理当前任务的时候生成下一个任务,如果没有下一个任务生成了,就代表本次渲染的 Diff 操作完成。

20. setstate 是同步,还是异步的?

同步和异步主要取决于它被调用的环境。

  • 如果 setState 在 React 能够控制的范围被调用,它就是异步的。 例如:合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行 DOM 更新。
  • 如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。 例如:原生事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 setState 被调用后会立即更新 DOM 。

21. 简述下 React 的事件代理机制?

React 的事件代理机制是 React 优化事件处理的方式,其在 React 版本的更新中频率还是蛮高的,在 React16 之前,事件处理都是直接绑定在 DOM 节点上的,这样会导致一个问题,就是如果在一个页面中有 1000 个 DOM 节点,那么就需要绑定 1000 个事件处理函数,这样会导致内存泄漏,性能也会受到影响。

而在 React16 及之后其通过事件委托的能力,统一将所有的事件注册到 document 或者 根节点上,这样就可以避免为每一个节点都绑定事件处理函数,这样就可以减少内存的占用,提高性能。

同时他还提供了以下的优化能力:

  1. 合成事件机制

其不是直接使用原生的事件,而是通过插件的方式去管理一批合成事件,这样就可以提供了一致的 API,在底层磨平了浏览器的差异,特别是对于输入有关的事件,如 compositionstart、compositionend、compositionupdate 等事件,对于不支持的时候通过判断是否是否唤起输入法,然后通过 keypress 和 node.value 的差异去模拟原生事件

  1. 事件绑定

一般我们的事件都是绑定在目标对象上的,这样 DOM 很多的时候就导致性能受到影响,所以 React 后面将其统一注册到 到 document 或者 根节点上,通过事件委托的方式去捕获所有的原生事件,然后通过 nativeTarget 去获取目标节点,再根据事件类型去触发对应的事件处理函数。

这样其实很有其他的好处 : 维持了事件触发顺序链的一致性,如 Potral 虽然 dom 在目标节点外,但是其事件链还是按照 FiberNode 的顺序进行处理的

  1. 批量更新

React 会在事件处理函数执行完后,进行批量更新 UI,减少不必要的渲染,提高性能。

22. 简述下 React 的生命周期?每个生命周期都做了什么?

23. 为什么不能在循环、条件或嵌套函数中调用 Hooks?

这就涉及到 React 中函数式组件 hook 状态的存储问题,因为对于函数式组件其从触发更新到插入到浏览器这个流程中不一定只执行一次,所以为了维护 hook 的状态其将组件中所有的 hook 按照一个双向链条的方式存储在 FiberNode 上,这样在函数式组件初次渲染的时候去生成对应的 effect,在重新渲染阶段按照 hook 执行的顺序从链条上不断获取当前 hook 的状态信息,如 state 等。

所以如果在循环、条件、嵌套等条件中调用,那么新旧组件的 hook 数量就可能不一致,从而导致状态混乱了

24. 说说你对 useContext 的理解

25. 说说你对 useMemo 的理解

26. 说说你对自定义 hook 的理解

27. 如何让 useEffect 支持 async/await?

28. 我们应该在什么场景下使用 useMemo 和 useCallback?

29. 说说你对 ReactHook 的闭包陷阱的理解,有哪些解决方案?

30. React18 新特性

31. React 中,怎么实现父组件调用子组件中的方法?

32. 你常用的 React Hooks 有哪些?

33. 说说你对 useReducer 的理解

35, 怎么在代码中判断一个 React 组件是 class component 还是 function component?

  • 对于类组件: React 在 Component 的原型上定义了一个 isReactComponent 属性,并将其值设置为一个空对象 {}。因此,如果一个组件继承自 React.Component 并且原型上存在 isReactComponent 属性,则可以判断它是一个类组件。

  • 对于函数组件:函数组件没有原型链,也没有 isReactComponent 属性。

js
function isClassComponent(component) {
  return (
    typeof component === "function" && !!component.prototype.isReactComponent
  );
}

// 示例用法
const MyComponent = () => <div>Hello, I'm a function component!</div>;
const MyClassComponent = class extends React.Component {
  render() {
    return <div>Hello, I'm a class component!</div>;
  }
};

console.log(isClassComponent(MyComponent)); // false
console.log(isClassComponent(MyClassComponent)); // true

36. useRef/ref/forwardsRef 的区别是什么?

37. useEffect 的第二个参数,传空数组和传依赖数组有什么区别

38. 说说你对 ReactRouter 的理解?常用的 Router 组件有哪些?

39. 讲讲 React.memo 和 JS 的 memorize 函数的区别

40. 怎么判断一个对象是否是 React 元素?

41.说说对 React 中 Element、Component、Node、Instance 四个概念的理解

42.React 和 Vue 在技术层面有哪些区别?

43.实现 useUpdate 方法,调用时强制组件重新渲染

44.taro 的实现原理是怎么样的?

45.taro 2.x 和 taro 3 最大区别是什么?

46.单页应用如何提高加载速度?

50. 说说 ReactRouter 有几种模式,以及实现原理?

51. js 中数组是如何在内存中存储的?

52.实现-个 useTimeout Hook

53.react 中怎么捕获异常?

54.最大子序和

55.说说 https 的握手过程

56.HTTP2 中,多路复用的原理是什么?

57.说说你对”三次握手”、“四次挥手”的理解

58.如何确保你的构造函数只能被 new 调用,而不能被普通调用?

59.为什么推荐将静态资源放到 cdn 上?

60.说说 React 事件和原生事件的执行顺序

61.Vue2.0 为什么不能检查数组的变化,该怎么解决?

62.说说 Vue 页面渲染流程

63.请简述=的机制

64,怎么做移动端的样式适配?

65.说说 sourcemap 的原理?

66.vue 中 computed 和 watch 区别

67.什么是 DNS 劫持?

68.爬楼梯

69.怎么实现图片懒加载?

70.HTTP 报文结构是怎样的?

71,如果使用 Vue3.0 实现一个 Modal,你会怎么进行设计?

72.js 中数组是如何在内存中存储的?

73.setTimeout 为什么不能保证能够及时执行?