Skip to content

为什么选择使用框架而不是原生 JS?

查看详情
  • 组件化:提高代码的复用性,降低维护的成本
  • 天然分层:原生代码涉及到页面 DOM 的更新大多耦合严重,现在的框架让代码分层和解耦,以 React 为例,如果需要改变 DOM 只需要更新数据即可
  • 生态:以 React 为例不管是数据管理,还是 UI 组件库都有比较成熟的解决方案
  • 开发效率:现代的框架默认自动更新 DOM,而非原生的手动操作,并对一些常见的问题进行了处理,比如 XSS

单页面应用和多页面应用的优缺点是什么

查看详情
  • 单页面
    • 优点
      • 局部刷新页面,动态加载内容,使用流畅,用户体验好
      • 前后端分离,通过API与后端交互,服务器的压力较小
      • 通常提供组件化和模块化的能力,代码复用率高
    • 缺点
      • 首次加载白屏
      • 不利于SEO
      • URL路由的管理较为复杂,可能借助第三方库
  • 多页面
    • 优点
      • SEO友好
      • 首屏加载更快
      • 浏览器兼容性更好
    • 缺点
      • 涉及到DOM操作的时候往往耦合严重
      • 状态管理困难
      • 开发效率较低

React 和其它框架的对比

查看详情
  • React 和 angularJs 的对比
    • angularJs 提供了非常全面而强大的功能,React 只是一个 UI 层的框架,如果开发较复杂的应用需要配合 react-router-dom、redux 等使用
    • angularJs 采用双向数据流,React 采用单向数据流
    • 在编码方式上 React 使用 JSX 而 angularJS 使用 template HTML + JS 的方式
    • angularJs 采用脏检查的方式,跟踪数据的改变,动态改变用户页面。但随着绑定数量的增加,性能就会越来越低。而 React 采用的是数据驱动视图变化的方式,并通过React fiber 的 diff 算法来提升性能
  • React 和 Vue 的对比
    • React 和 Vue 有许多的相似之处,例如:都使用虚拟 DOM,提供响应式和组件化的视图组件
    • Vue 不用像 React 一样需要关注当某个组件的 state 发生变化时,子组件如果不做特殊处理会重新渲染的问题
    • 在编码方式上 React 使用 JSX 而 Vue 默认使用 template HTML + JS 的方式
    • Vue 提供了一个 CLI 脚手架,可以非常容易的构建项目和快速开发组件,但 React 的 create-react-app 只能构建一个功能单一的应用,像路由、数据管理等都需要自己集成
    • React 具有强大的生态系统,就像它的特点中叙述的,一次学习,随处编写,它可以使用 Node 进行服务端渲染或者使用 React Native 开发原生应用

React 的特点

查看详情
  1. 组件化
  • 可重用性:React 鼓励将用户界面分解成可重用的组件,每个组件封装了自己的结构、样式和行为。
  • 独立性:组件是独立的,便于维护和测试。
  • 嵌套和组合:组件可以嵌套和组合,构建复杂的用户界面。
  1. 声明式
  • 简化 UI 开发:通过声明式的编程方式,开发者只需描述 UI 应该呈现的状态,而不需要直接操作 DOM。
  • 可预测性:由于声明式编程,React 可以更容易地管理和调试 UI 状态变化。
  1. 单向数据流
  • 数据绑定:React 实现单向数据绑定,数据在组件树中自上而下流动。这种方式使得数据的流动和变化更加可预测和易于调试。
  • 状态管理:组件通过 props 接收数据,通过 state 管理内部状态。这种单向数据流确保了数据的变化是有规律和可跟踪的。
  1. JSX 语法
  • 增强可读性:JSX 是一种 JavaScript 的语法扩展,可以让你在 JavaScript 代码中直接编写类似 HTML 的模板。它提高了代码的可读性和开发效率。
  • 灵活性:JSX 最终会被编译成 React.createElement 调用,具有灵活的表达能力,可以轻松与 JavaScript 逻辑混合使用。
  1. Hooks
  • 函数组件中的状态和副作用管理:Hooks(如 useState、useEffect)允许在函数组件中管理状态和副作用,避免了使用类组件的复杂性。
  • 自定义 Hook:可以创建自定义 Hook 以重用逻辑代码,使代码更加简洁和可维护。

React 中循环渲染时 key 的作用

查看详情

key 帮助 React 识别哪些元素改变了,比如被添加或删除,提高 diff 的效率,达到性能优化的目的。

什么是真实DOM和虚拟 DOM ?它们的优点和缺点是什么?

查看详情

真实DOM:DOM即为文档对象模型,页面最终渲染出的每一个节点都是一个真实的DOM。

  • 优点
    • 易用,无需借助任何框架
  • 缺点
    • 频繁操作DOM时,每次操作都会造成页面的重排和重绘
    • 代码可维护性差

虚拟 DOM: 本质上是一个 JavaScript 对象,包含 tag, props, children 三个属性。

  • 优点
    • 保证性能下限:虚拟 DOM 可以经过 diff 找出最小的差异,然后最终转化为真实的 DOM 展现在页面上,保证每次更新尽量小
    • 跨平台开发:虚拟 DOM 本质上是一个 JavaScript 对象,因此具有跨平台的能力
  • 缺点
    • 首次渲染大量的 DOM 时性能不如真实DOM,因为多了一个虚拟DOM的过程

React 16 的改进是什么?

查看详情
  • 全新的协调器 (Fiber) 架构
  • 错误处理 (Error Boundaries)
  • Fragments
  • hooks(16.8)

React 17 的改进是什么?

查看详情
  • 事件委托的变更:在 React 17 中,React 将不再在document底层附加事件处理程序。相反,它会将它们附加到渲染 React 树的根 DOM 容器中。这是为了解决多个版本的react共存的问题。

React 18 的改进是什么?

查看详情
  • Concurrent模式:React 18 通过并发渲染来提升应用的性能和响应能力。这种特性允许 React 可以在不阻塞主线程的情况下准备新的 UI 更新,并在用户与应用交互时保持流畅。在 React 18 中,使用 createRoot 来创建根节点,启用并发特性。
  • 自动批处理 (Automatic Batching):自动批处理能够将多个状态更新自动批处理成一次渲染,从而减少不必要的重新渲染次数,提高性能。在 React 18 之前,默认情况下 promise、setTimeout、原生应用的事件处理程序以及任何其他事件中的更新都不会被批量处理;但现在,这些更新内容都会被自动批处理。
  • 新的 Suspense 特性:如果组件树的某个部分尚未准备好显示,Suspense 可以让你以声明的方式指定它的加载状态
  • Transitions:用于区分紧急和非紧急更新

React 19 的改进是什么?

查看详情
  • useTransition:不阻塞 UI 的情况下更新状态
  • useOptimistic:乐观地更新 UI
  • useActionState:根据表单操作的结果更新状态
  • useFormStatus:提供上次表单提交的状态信息

如何快速定位哪个组件出现了性能问题?

查看详情
  • react Profiler组件
  • 使用 chrome 的 performance,查看每个函数的调用情况,根据函数调用定位组件 performance

react 的生命周期函数

react 的生命周期函数

React 的请求应该放在哪个声明周期中?

查看详情

官方推荐放在 componentDidMount 中

React 如何进行组件或逻辑的复用

查看详情
  • 高阶组件
  • 渲染属性
  • hooks

使用 Hooks 的优势是什么?

查看详情
  • 类组件的不足
    • 状态逻辑难以复用:在组件间复用状态逻辑很难,可能用到 render props(渲染属性)或 HOC(高阶组件),但都需要在原来的组件外层嵌套一层父容器,导致层级冗余
    • 趋向复杂难以维护:
      • 在生命周期中混杂不相干的逻辑
      • 类组件中到处都是对状态的访问和处理,导致组件难以拆分成更小的组件(更小的组件可以让组件的更新影响更小的范围,提高性能)
    • this 指向问题:父组件给子组件传递函数时必须绑定 this
  • Hooks 的优势
    • 解决了类组件的三大问题
    • 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks)
    • 能将组件中相互关联的部分拆分成更小的函数(比如 ajax 请求)
    • 副作用的关注点分离:以前的副作用(ajax 请求,设置定时器等等)都写在生命周期函数中,而现在可以将所有的副作用拆分到各个 useEffect 函数中,一个 useEffect 只干一件事

react 中的 refs 是什么?

查看详情
  • refs 是一个特殊的、可以附加到任何组件上的属性。这能够让我们直接访问 DOM 元素或组件实例。
  • 创建方式:
    • React.createRef()
    • 回调 refs <input ref={element => this.inputRef = element}>
  • 几种适合适合使用 refs 的情况:
    • 管理焦点,文本选择或媒体播放
    • 触发强制动画
    • 集成第三方库
  • 使用 state, 避免滥用 refs

简述 React 的 diff 算法

查看详情
  • 当对比两颗树时,React 首先比较两棵树的根节点
  • 当根节点元素类型不同时,React 会拆卸原有的树并且建立起新的树
  • 当根节点元素的类型相同时,React 仅对比及更新有改变的属性
  • 然后依次按照此规则递归遍历所有的子节点

setState 什么时候是同步的,什么时候是异步的?

查看详情
  • 在 React 中,如果是由 React 引发的事件处理(比如通过 onClick 引发的事件处理,在生命周期中调用 setState),调用 setState 不会同步更新 this.state,除此之外的 setState 调用会同步更新 this.state。所谓“除此之外”,指的是绕过 React 通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval 产生的异步调用。
  • 这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。这里的异步指的是多个 setState 会合成到一起进行批量更新。在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。

在 React 类组件中,为什么修改状态要使用 setState 而不是用 this.state.xxx = xxx?

查看详情

setState 方法做了很多关键的工作来确保组件能够正确地更新和重新渲染。直接修改 this.state 不会触发这些机制,从而导致应用行为不正确。

  • 触发重新渲染
  • 进行状态合并
  • 在setState后会进行声明周期方法的调用

React 组件间如何通信

查看详情
  • 父组件以 props 向子组件通信
  • 子组件利用自定义事件触发回调向父组件通信
  • 利用 context 实现嵌套组件的跨层级通信

JSX 的本质是什么

查看详情

JSX 本质是一个 JavaScript 的语法扩展(语法糖),最终会被 React.createElement()转换为 JS

context 是什么?有什么用途?

查看详情
  • Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
  • 组件的跨层级通信(例如:地区偏好,UI 主题)

shouldComponentUpdate 的用途

查看详情

通过 shouldComponentUpdate 的返回值控制组件是否渲染,从而达到性能优化的目的

React 里的事件机制

查看详情

React 创建了一套自己的事件系统,这些事件被称为合成事件。合成事件是对浏览器原生事件的包装,它们在所有浏览器中表现一致。 React 使用事件委托模式将事件处理器附加到组件的根元素上,而不是直接附加到具体的 DOM 节点上,然后通过事件冒泡机制处理具体的事件。

react 事件和原生事件有什么区别?

查看详情
  • React 事件使用小驼峰命名而原生事件采用全小写
  • React 事件阻止默认行为需要使用 event.preventDefault,不能使用 return false 来实现。

父子类组件在渲染到页面上的时候调用声明周期方法的顺序是什么?

查看详情
  • 父子组件初始化

    • 父组件 constructor
    • 父组件 getDerivedStateFromProps
    • 父组件 render
    • 子组件 constructor
    • 子组件 getDerivedStateFromProps
    • 子组件 render
    • 子组件 componentDidMount
    • 父组件 componentDidMount
  • 子组件修改自身state

    • 子组件 getDerivedStateFromProps
    • 子组件 shouldComponentUpdate
    • 子组件 render
    • 子组件 getSnapShotBeforeUpdate
    • 子组件 componentDidUpdate
  • 父组件修改props

    • 父组件 getDerivedStateFromProps
    • 父组件 shouldComponentUpdate
    • 父组件 render
    • 子组件 getDerivedStateFromProps
    • 子组件 shouldComponentUpdate
    • 子组件 render
    • 子组件 getSnapShotBeforeUpdate
    • 父组件 getSnapShotBeforeUpdate
    • 子组件 componentDidUpdate
    • 父组件 componentDidUpdate
  • 卸载子组件

    • 父组件 getDerivedStateFromProps
    • 父组件 shouldComponentUpdate
    • 父组件 render
    • 父组件 getSnapShotBeforeUpdate
    • 子组件 componentWillUnmount
    • 父组件 componentDidUpdate

下列代码的执行结果

查看详情
javascript
class Demo extends React.Component {
  state = {
    count: 0,
  };

  componentDidMount() {
    this.setState({ count: this.state.count + 1 });
    console.log('1: ', this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log('2: ', this.state.count);
    setTimeout(() => {
      this.setState({ count: this.state.count + 1 });
      console.log('3: ', this.state.count);
    }, 0);
    setTimeout(() => {
      this.setState({ count: this.state.count + 1 });
      console.log('4: ', this.state.count);
    }, 0);
  }

  render() {
    return <div>{this.state.count}</div>;
  }
}

// 0 0 2 3

下列代码的执行结果

查看详情
javascript
class Counter extends React.Component<any, any> {
  constructor(props) {
    super(props);
    this.state = {
      num1: 0,
      num2: 0,
      num3: 0,
    };
  }

  handleClick1 = () => {
    this.setState({ num1: this.state.num1 + 1 });
    console.log(this.state.num1);
  };

  handleClick2 = () => {
    setTimeout(() => {
      this.setState({ num2: this.state.num2 + 1 });
      console.log(this.state.num2);
    }, 1000);
  };

  handleClick3 = () => {
    for (let i = 0; i < 5; i++) {
      setTimeout(() => {
        this.setState({ num3: this.state.num3 + 1 });
        console.log(this.state.num3);
      }, 1000);
    }
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick1}>num1={this.state.num1}</button>
        <button onClick={this.handleClick2}>num2={this.state.num2}</button>
        <button onClick={this.handleClick3}>num3={this.state.num3}</button>
      </div>
    );
  }
}

// 0
// 1
// 1 2 3 4 5

下列代码的执行结果

查看详情
javascript
function Counter() {
  const [num1, setNum1] = useState(0);
  const [num2, setNum2] = useState(0);
  const [num3, setNum3] = useState(0);

  const handleClick1 = () => {
    setNum1(num1 + 1);
    console.log(num1);
  };

  const handleClick2 = () => {
    setTimeout(() => {
      setNum2(num2 + 1);
      console.log(num2);
    }, 1000);
  };

  const handleClick3 = () => {
    for (let i = 0; i < 5; i++) {
      setTimeout(() => {
        setNum3(num3 + 1);
        console.log(num3);
      }, 1000);
    }
  };

  return (
    <div>
      <button onClick={handleClick1}>num1={num1}</button>
      <button onClick={handleClick2}>num2={num2}</button>
      <button onClick={handleClick3}>num3={num3}</button>
    </div>
  );
}

// 0
// 0
// 0 0 0 0 0

React 中的浅比较是什么?

查看详情
  • React中的浅比较函数(shallowEqual)中并不是使用全等===,而是使用 Object.is
  • 浅比较中,空对象和空数组会被认为相等
  • 浅比较中,一个以索引值作为键的对象和一个在相应各下标处具有相同值的数组相等。如{0:2,1:3}等于[2,3]
  • 浅比较中,+0 和-0 在浅比较中是不相等的, NaN 和 NaN 是相等的。
  • 虽然两个直接创建的对象(或数组)通过浅比较是相等的({}和[]),但嵌套的数组、对象是不相等的。如{someKey:{}}和{someKey:[]}浅比较是不相等的

简述 react 中的渲染和提交阶段

查看详情

在 React 中,渲染(Render)和提交(Commit)是两个关键阶段,负责组件的更新和 UI 的渲染。

  • 渲染阶段是react确定更新内容的过程,在这个阶段react会创建或者更新fiber树,并计算需要改变的内容。这是一个纯计算的阶段,可以被中断。
  • 提交阶段是react将渲染阶段计算出的变化应用到真实DOM的过程,在这个阶段react会操作DOM,因此这个阶段是不可中断的。在此阶段会触发副作用函数,例如 useEffect、componentDidMount、componentDidUpdate 和 componentWillUnmount。

简述react的运行机制

查看详情

我们写的JSX代码经过Babel编译后,最终会转换为react.createElement()代码。当应用启动的时候,react会调用createRoot方法,并用根组件调用render方法递归得出所有的子组件,然后为它们创建DOM节点并使用appendChild方法将其添加到浏览器。

当组件的状态发生变化时,react将计算它们变化的内容,然后将其提交到浏览器。

在函数组件每一次函数上下文执行的时候,react 如何记录 hooks 的状态?

查看详情

react通过fiber数据结构和hooks链表管理组件的hooks状态。React 内部使用了一种叫做 Fiber 的数据结构来表示组件树中的每一个组件节点。每个 Fiber 节点都有一个 memoizedState 属性,用于存储该组件的 hooks 状态链表。链表上hooks上的memoizedState 保存了当前hooks信息。

多个 react hooks 如何记录每一个 hooks 的顺序的?/ 为什么不能条件语句中声明 hooks?

查看详情

每执行一个react hooks都会产生一个hooks对象,在这个对象中有一个next属性它将指向下一个hooks对象。然后每个hooks就以链表形式串联起来。在条件语句中声明hooks,会破坏hooks的链表结构。

函数组件中的 useState 和 class 类组件 setState 有什么区别?

查看详情
  • useState 是一个 Hook,用于在函数组件中管理状态。
    • 需要手动合并状态对象。
    • 多次更新需要用函数式更新以避免状态覆盖问题。
  • setState 是类组件中用于管理状态的方法。
    • 自动合并状态对象。
    • 多次更新会自动批量处理。

react 是怎么捕获到 hooks 的执行上下文,是在函数组件内部吗?

查看详情

React 通过 Fiber 数据结构、全局变量和 hooks 调度器的结合,捕获并管理函数组件内部的 hooks 执行上下文。

  1. Fiber 数据结构: React 使用 Fiber 数据结构来表示组件树中的每一个组件节点。每个 Fiber 节点都有一个 memoizedState 属性,用于存储该组件的 hooks 状态链表。

  2. 全局变量: React 内部使用一些全局变量来跟踪当前正在渲染的组件和 hooks 调用顺序:

  • currentFiber: 指向当前正在渲染的 Fiber 节点。
  • workInProgressHook: 指向当前正在处理的 hook。
  1. hook dispatcher: 在函数组件执行期间,React 会设置一个特殊的 hooks 调度器(hook dispatcher),这个调度器负责处理所有 hooks 的调用。这个调度器会根据当前的渲染阶段(初次渲染或更新)来选择不同的处理函数。

为什么 useRef 不需要依赖注入就能访问到最新的改变值?

查看详情

useRef始终返回hooks链表节点上的memoizedState

useMemo 是怎么对值做缓存的?如何应用它优化性能?

查看详情

判断两次 deps是否相等,如果不相等,证明依赖项发生改变,那么执行 useMemo的第一个函数,得到新的值,然后重新赋值给hook.memoizedState,如果相等证明没有依赖项改变,那么直接获取缓存的值。

为什么两次传入 useState 的值相同,函数组件不更新?

查看详情

浅比较的结果相同

react组件封装的原则是什么?如何设计一个组件?

查看详情

组件封装的基本原则:

  • 单一职责:每个组件应该只负责一个特定的功能或者UI
  • 高内聚低耦合:组件的内部应该高度内聚,完成相关的功能或UI。同时,组件之间的耦合应该尽量降低,只通过props和回调函数进行通信
  • 可复用
  • 关注点分离:将样式、逻辑和渲染分开。

设计一个组件的步骤:

  • 确定组件的功能
  • 确定组件的props
  • 设计/编写组件的内部代码
  • 编写组件的测试用例
  • 组件文档化

在开发组件的时候如何降低组件的耦合度?

查看详情
  • 只通过props和回调函数进行通信
  • 不依赖外部导入

react中如何捕获错误?

查看详情
  • 错误边界是: React 16 引入的一个特性,用于捕获其子组件树中的 JavaScript 错误,并显示回退 UI,而不会导致整个应用崩溃。错误边界是使用类组件创建的,需实现 componentDidCatch 生命周期方法和 getDerivedStateFromError 静态方法。但它无法捕获以下错误:
    • 异步代码中的错误
    • 它自身抛出的错误
    • 服务端渲染时的错误
  • 使用 try-catch 捕获异步错误

react函数组件中如何保存上一次的state

查看详情
javascript
import React, { useState, useEffect, useRef } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  useEffect(() => {
    // 每次渲染完成后,更新 prevCountRef 的值
    prevCountRef.current = count;
  });

  const prevCount = prevCountRef.current;

  return (
    <div>
      <p>Current Count: {count}</p>
      <p>Previous Count: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

useEffect、useLayoutEffect和useInsertionEffect的区别是什么?

查看详情
  • useEffect 在组件渲染到屏幕之后异步执行。这意味着它不会阻塞浏览器的绘制过程,可以避免阻塞用户界面的渲染。
    • 执行时机:在组件渲染后,浏览器完成布局和绘制后异步执行。
    • 常见用途:数据获取、订阅、事件监听器、清理操作等。
  • useLayoutEffect 在所有 DOM 变更后同步执行。在浏览器完成布局和绘制之前执行,因此会阻塞页面的绘制。
    • 执行时机:在组件渲染并更新 DOM 后,浏览器绘制之前同步执行。
    • 常见用途:需要读取 DOM 布局并同步修改 DOM 的操作,以确保页面绘制前状态一致。
  • useInsertionEffect 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect 或者 useLayoutEffect。