Skip to content

面试回顾

1. 说一下vue和react两个框架的区别

Vue 和 React 是两个流行的前端框架,它们有许多相同点和不同点,让我们来一一分析:

相同点:

  1. 组件化

    • VueReact 都支持组件化开发思想,将界面拆分为独立可复用的组件,使得代码结构更清晰、可维护性更强。
  2. 虚拟DOM

    • 两者都使用虚拟DOM(Virtual DOM)来提高渲染效率,通过比较虚拟DOM树的差异,最小化实际DOM操作,从而提升性能。
  3. 响应式数据绑定

    • Vue 和 React 都支持响应式数据绑定,当数据发生变化时,自动更新视图,简化了状态管理和视图更新的复杂性。
  4. 单文件组件

    • Vue 和 React 都支持单文件组件(Single File Components),将组件的结构、样式和逻辑封装在一个文件中,提高了开发效率和组件复用性。
  5. 社区和生态系统

    • 两者都拥有活跃的社区和丰富的生态系统,提供了大量的第三方库、插件和工具,支持开发者快速构建复杂的单页面应用(SPA)和大型应用。

不同点:

  1. 学习曲线

    • Vue 的学习曲线相对较低,因其设计理念更贴近传统的HTML、CSS和JavaScript开发方式,易于上手。
    • React 则需要更深入理解其虚拟DOM、JSX语法等概念,对于初学者可能有一定的学习门槛。
  2. 灵活性

    • React 更加灵活,更多的决策留给开发者,例如状态管理可以选择使用 Context API、Redux 等,路由可以选择 React Router 等。
    • Vue 在设计上提供了更多的约定和内置功能,降低了开发者在做技术选型上的负担,有利于快速开发。
  3. 生态系统和工具链

    • React 生态系统更加庞大和成熟,拥有大量的第三方库和工具支持,适合构建大型应用和复杂的组件库。
    • Vue 生态系统虽然也很活跃,并且有自己的一些优秀工具(如Vue Router、Vuex等),但整体上规模较React稍小。
  4. 模板语法 vs JSX

    • Vue 使用基于HTML的模板语法,可以更快速地编写和理解模板,尤其适合传统前端开发者。
    • React 使用JSX(JavaScript XML),允许在JavaScript中直接编写类HTML代码,提供了更高的灵活性和表达能力。
  5. 状态管理

    • React 中常用的状态管理工具包括 Context API 和 Redux,可以选择更适合项目规模的状态管理方案。
    • Vue 原生支持响应式数据和Vuex状态管理库,使得状态管理在大多数场景下更加简洁和直观。

总体而言,选择 Vue 还是 React 取决于项目需求、团队技术栈和开发者个人偏好。Vue 更加直观和容易上手,适合中小型项目和快速原型开发;React 则更灵活和适合构建复杂、大规模应用。

2. 如何使用 useRef 和 useEffect 来模拟 componentWillUpdate

tsx
import React, { useState, useEffect, useRef } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  // 使用 useRef 来存储之前的 count 值
  const prevCountRef = useRef<number | undefined>();

  // useEffect 在组件渲染后执行,这里用来更新 prevCountRef 的值
  useEffect(() => {
    prevCountRef.current = count;
  });

  // 获取之前的 count 值
  const prevCount = prevCountRef.current;

  useEffect(() => {
    if (prevCount !== undefined) {
      console.log('Component will update');
      console.log('Previous count:', prevCount);
      console.log('Current count:', count);
    }
  }, [count]); // 依赖数组包含 count,表示 count 变化时调用此 useEffect

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

export default MyComponent;

3. HTML 渲染是一个复杂的过程,涉及多个阶段,从浏览器接收到 HTML 文件到最终在屏幕上显示内容。下面是详细的渲染全过程:

1.加载阶段

浏览器首先加载HTML文件,并开始解析它。

  1. 浏览器发起HTTP请求,获取HTML文件。
  2. 浏览器将HTML文件解析为DOM树。

2.构建DOM树

解析阶段完成后,浏览器将DOM树构建为一棵完整的树结构。

  1. 浏览器根据HTML文件中的标签、属性和内容,创建相应的DOM节点。
  2. 构建完成后,DOM树的结构如下:
html
<html>
  <head>
    <title>My Website</title>
    <link href="xxx.css">
    <style>
     .box1 {
        color: red;
      }
    </style>
  </head>
  <body>
  <div id='box' class='box1'>我是一个盒子</div>
  </body>
  <script>
    // 执行一些JavaScript代码

  </script>
</html>

3.解析CSS

在解析HTML的过程中,浏览器还会解析CSS、

  1. 下载css
    • 当浏览器遇到<link>标签或<style>标签时,会下载外部CSS文件或解析内部CSS
  2. 构建CSSOM树
    • CSS被解析成CSSOM(CSS Object Model)树,表示样式信息

4.解析JavaScript

javaScript可以改变HTML和CSS

  1. 下载JavaScript
    • 当浏览器遇到<script>标签时,会下载外部JavaScript文件或内部脚本
  2. 阻塞渲染
    • 默认情况下,JavaScript的执行会阻塞HTML的解析和DOM树的构建,因为脚本可能会修改DOM树
  3. 执行脚本
    • 执行JavaScript代码,可能会修改DOM和CSSOM

5.构建渲染树

解析阶段完成后,浏览器会构建渲染树,将DOM树和CSSOM树结合起来,形成一棵渲染树。

  1. 生成渲染树:
    • 渲染树由可见的DOM树节点和对应的CSSOM样式节点组成
    • 不可见的节点(如:headdisplay: none的元素)不会出现在渲染树中

6. 布局(Layout)

浏览器计算每个节点的大小和位置

  1. 计算几何属性
    • 布局阶段: 浏览器会根据渲染树计算每个元素的几何属性(位置、大小、边距等)
  2. 流式布局:
    • 大多数情况下,浏览器采用流式布局模型,自上而下,从左到右地计算布局

7. 绘制(Paint)

浏览器将渲染树的内容绘制到屏幕上

  1. 绘制阶段
    • 浏览器将各个节点的几何属性和样式转换成像素,绘制到屏幕上的多个层(layers)

8. 合成

合成阶段是在绘制阶段之后,浏览器将多个层合成成一个层,以提高性能和用户体验

  1. 合成阶段
    • 浏览器将多个层合并成一个层,以提高性能和用户体验
    • 这一步通常由GPU完成,特别是对于复杂的动画和变换

9. 显示

显示阶段是在合成阶段之后,浏览器将最终的图像显示在屏幕上

10. 总结

具体的渲染过程总结:

  1. 加载HTML文件:浏览器发起HTTP请求,获取HTML文件。
  2. 构建DOM树:浏览器根据HTML文件中的标签、属性和内容,创建相应的DOM节点。
  3. 解析CSS:浏览器下载外部CSS文件或解析内部CSS,构建CSSOM树。
  4. 解析JavaScript:下载并执行JavaScript代码,可能会修改DOM和CSSOM。
  5. 构建渲染树:结合DOM和CSSOM,生成渲染树。
  6. 布局:计算渲染树中的每个节点的几何属性。
  7. 绘制:将节点转换成像素,绘制到屏幕上。
  8. 合成:将多个层合并成一个层,形成最终的屏幕图像。

渲染过程中需要注意的点

  • 渲染阻塞:JavaScript和CSS的下载和解析可能会阻塞HTML的解析,影响页面渲染速度。
  • 异步加载:可以使用asyncdefer属性异步加载JavaScript,优化渲染性能。
  • 优化布局:减少重排和重绘,使用有效的CSS选择器,优化DOM操作,避免频繁的样式修改。

4. 渲染阻塞:JavaScript 和 CSS 的下载和解析如何阻塞 HTML 的解析

JavaScript 的阻塞行为

JavaScript 的下载和解析会阻塞 HTML 的解析,因为 JavaScript 可以通过 DOM API 操作和修改 HTML 和 CSS。因此,浏览器在遇到 <script> 标签时,必须暂停解析 HTML,直到 JavaScript 被下载、解析并执行完毕。

  1. 默认情况下

    • 当浏览器遇到一个 <script> 标签时,会停止 HTML 的解析,并发起 HTTP 请求下载 JavaScript 文件。
    • 下载完成后,浏览器会解析并执行 JavaScript 代码。
    • 只有当 JavaScript 执行完毕后,浏览器才会继续解析剩余的 HTML。
  2. 原因

    • JavaScript 可能会修改 DOM 结构(例如,添加或删除元素)。
    • JavaScript 可能会修改 CSS 样式(例如,通过更改类名或内联样式)。
    • 因此,为了确保页面的正确渲染,浏览器必须在执行 JavaScript 之前完成这些操作。

CSS 的阻塞行为

CSS 的下载和解析会阻塞页面的渲染,但不会阻塞 HTML 的解析。浏览器在遇到 <link> 标签或 <style> 标签时,会立即下载并解析 CSS 文件,但 HTML 的解析会继续进行。尽管如此,浏览器在完成 CSS 解析之前不会渲染任何内容。

  1. 原因
    • CSS 影响页面的呈现和布局,浏览器需要确保所有的样式信息都已经解析完毕后再进行渲染。
    • 如果没有完全解析 CSS 就开始渲染,可能会导致页面在渲染过程中出现明显的样式变化,影响用户体验。

异步加载 JavaScript:async 和 defer 属性

为了避免 JavaScript 阻塞 HTML 的解析,可以使用 asyncdefer 属性,这两个属性允许 JavaScript 文件异步加载。

async 属性

  • 用法<script src="script.js" async></script>
  • 行为
    • 浏览器在解析 HTML 时,遇到带有 async 属性的 <script> 标签时,会立即开始下载 JavaScript 文件,同时继续解析 HTML。
    • JavaScript 文件下载完成后会立即执行,可能会打断 HTML 的解析。
  • 适用场景:适用于不依赖于其他脚本或 DOM 内容的独立脚本。

defer 属性

  • 用法<script src="script.js" defer></script>
  • 行为
    • 浏览器在解析 HTML 时,遇到带有 defer 属性的 <script> 标签时,会立即开始下载 JavaScript 文件,同时继续解析 HTML。
    • 所有的 defer 脚本会按照它们在 HTML 中出现的顺序,在 HTML 解析完毕后,DOMContentLoaded 事件之前执行。
  • 适用场景:适用于需要在 DOM 准备就绪后执行的脚本,尤其是依赖于 DOM 内容或其他脚本的脚本。

preloadlazy 属性的具体情况

preload 属性

preload 是一个 <link> 标签的属性,用于提示浏览器在需要之前下载资源,以提高页面加载性能。

  • 用法<link rel="preload" href="script.js" as="script">
  • 行为
    • 浏览器会在解析 HTML 时,预先下载 preload 指定的资源,但不会立即执行。
    • 当需要时,这些资源会立即可用,减少加载时间。
  • 适用场景:适用于希望提前下载但稍后执行的资源,如关键的 JavaScript 文件或字体文件。

lazy 属性

lazy 是用于懒加载资源的属性,主要用于图像和 iframe。

  • 用法<img src="image.jpg" loading="lazy">
  • 行为
    • 浏览器会延迟加载 lazy 属性指定的资源,直到用户即将滚动到它们。
    • 这可以显著减少初始页面加载时间和流量消耗。
  • 适用场景:适用于延迟加载的图像、iframe 等,直到用户即将查看它们。

总结

  • JavaScript 和 CSS 阻塞:JavaScript 的默认行为会阻塞 HTML 的解析,因为它可能修改 DOM 和 CSS。CSS 的解析会阻塞页面的渲染,但不会阻塞 HTML 的解析。
  • 异步加载:使用 asyncdefer 属性可以避免 JavaScript 阻塞 HTML 的解析,async 适用于独立脚本,defer 适用于需要 DOM 准备就绪后执行的脚本。
  • 预加载和懒加载:使用 preload 可以预先下载关键资源,提高页面加载性能;使用 lazy 可以延迟加载非关键资源,减少初始页面加载时间。

参考资料

MDN script标签script标签属性 MDN link标签link标签属性

5. React 通常不被认为是双向数据绑定的框架

主要是因为它的数据流和状态管理模式与传统的双向绑定框架(如 AngularJS)不同。以下是详细原因和解释:

1. 单向数据流

React 采用单向数据流(单向绑定),这意味着数据从父组件流向子组件,通过 props 传递。任何状态的变化都会触发重新渲染,但数据总是从顶层流向底层,而不是双向绑定。

  • 单向数据流的好处
    • 易于调试和跟踪:数据流动方向单一,易于追踪状态变化和调试。
    • 状态管理清晰:组件之间的数据依赖关系简单,易于管理和维护。

2. 状态管理

在 React 中,状态(state)通常保存在组件内部或通过状态管理库(如 Redux、MobX、Recoil 等)集中管理。组件的状态变化通常通过事件处理函数(如 onClick、onChange)来触发,而不是通过双向绑定的方式。

  • 状态更新机制
    • 组件内部状态通过 this.setState 更新。
    • 全局状态通过状态管理库中的动作(actions)和 reducers 更新。

3. 双向绑定的定义

双向绑定指的是模型和视图之间的自动同步机制,即视图的变化会自动更新模型,模型的变化也会自动更新视图。AngularJS 是一个典型的双向绑定框架,在数据绑定上提供了更复杂的机制。

  • 双向绑定的实现
    • 视图(View)和模型(Model)之间存在直接的绑定关系。
    • 视图的变化会自动更新模型,反之亦然。

4. React 中的数据绑定

React 中的数据绑定是单向的,即使在表单输入处理中,也不是传统的双向绑定。

  • 表单处理
    • React 中表单输入的处理通常通过受控组件(controlled components)实现。受控组件的值由 React 组件的状态管理,输入变化通过事件处理函数(如 onChange)来更新状态。
    • 非受控组件(uncontrolled components)则通过 ref 直接访问 DOM 元素的值,但这也不是双向绑定。

示例:React 中的受控组件

jsx
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }

  handleChange = (event) => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <input type="text" value={this.state.value} onChange={this.handleChange} />
    );
  }
}

在上述示例中,输入框的值(value)由组件状态(state)控制。用户在输入框中的操作触发 handleChange 事件处理函数,进而更新组件状态。

结论

React 不被认为是双向数据绑定框架,主要原因在于其采用了单向数据流和组件状态管理的模式。React 的设计理念强调数据自上而下的单向流动,这使得状态管理更加清晰、易于调试和维护。尽管可以通过受控组件实现类似双向绑定的效果,但这并不是 React 的核心设计模式。

6. Vue 和 React 都有 ref 属性,用于访问 DOM 元素或组件实例。尽管它们在某些方面具有相似性,但在用法和实现细节上也有显著的不同。下面是它们的相同点和不同点的详细比较:

相同点

  1. 直接访问 DOM 元素

    • 都允许开发者直接访问 DOM 元素,以便进行一些直接的 DOM 操作或与第三方库集成。
  2. 引用组件实例

    • 都可以用来获取组件实例的引用,允许访问组件实例的方法或属性。

不同点

Vue 中的 ref

  1. 定义和使用

    • 在 Vue 模板中,使用 ref 属性定义引用。
    • 在 Vue 3 中,通过 this.$refs 访问模板中的引用,或者在组合 API 中通过 ref 函数创建响应式引用。
  2. 访问方式

    • 在 Vue 2 中,通过 this.$refs 访问。
    • 在 Vue 3 中,结合组合 API,可以使用 refonMounted 等钩子访问。
  3. 响应式

    • Vue 3 中的 ref 可以创建响应式数据,并在组合 API 中使用。
  4. 示例

    vue
    <!-- Vue 2 组件 -->
    <template>
      <div>
        <input ref="myInput">
      </div>
    </template>
    <script>
    export default {
      mounted() {
        this.$refs.myInput.focus();
      }
    }
    </script>
    
    <!-- Vue 3 组件 -->
    <template>
      <div>
        <input ref="myInput">
      </div>
    </template>
    <script>
    import { ref, onMounted } from 'vue';
    export default {
      setup() {
        const myInput = ref(null);
        onMounted(() => {
          myInput.value.focus();
        });
        return { myInput };
      }
    }
    </script>

React 中的 ref

  1. 定义和使用

    • 在 React 中,使用 React.createRefuseRef(在函数组件中)来创建引用。
    • ref 属性添加到 JSX 元素或组件中。
  2. 访问方式

    • 在类组件中,通过 this.myRef.current 访问。
    • 在函数组件中,通过 myRef.current 访问。
  3. 非响应式

    • React 的 ref 本身并不是响应式的,不会触发重新渲染。
  4. 示例

    jsx
    // 类组件
    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.myInput = React.createRef();
      }
    
      componentDidMount() {
        this.myInput.current.focus();
      }
    
      render() {
        return <input ref={this.myInput} />;
      }
    }
    
    // 函数组件
    import React, { useRef, useEffect } from 'react';
    
    function MyComponent() {
      const myInput = useRef(null);
    
      useEffect(() => {
        myInput.current.focus();
      }, []);
    
      return <input ref={myInput} />;
    }

总结

  • 相同点

    • Vue 和 React 都使用 ref 来直接访问 DOM 元素或组件实例。
    • 都可以在组件的生命周期钩子(Vue 的 mounted,React 的 componentDidMountuseEffect)中使用 ref
  • 不同点

    • Vue 的 ref 在 Vue 3 中结合组合 API 可以创建响应式数据,而 React 的 ref 不具备响应式特性。
    • Vue 使用模板语法定义 ref,通过 this.$refs 或响应式引用访问;React 使用 React.createRefuseRef 创建引用,通过 .current 访问。
    • Vue 的 ref 属性更多地与模板绑定,React 的 ref 属性则与 JSX 元素和组件直接绑定。

通过理解这些相同点和不同点,可以更好地在项目中选择和使用 ref,以实现需要的功能。

7. Promise原理

Promise 是一种用于处理异步操作的 JavaScript 对象,它提供了一种更优雅和强大的方式来处理异步代码。Promise 的原理涉及到其内部的状态管理和链式调用机制。

原理概述

  1. 状态

    • Promise 对象有三种状态:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。
    • 初始状态为 Pending,可以转变为 Fulfilled 或 Rejected。
  2. 状态转换

    • 当异步操作成功完成时,Promise 从 Pending 转变为 Fulfilled,表示操作成功,并携带返回的值。
    • 当异步操作失败时,Promise 从 Pending 转变为 Rejected,表示操作失败,并携带错误信息。
  3. 链式调用

    • Promise 可以通过 .then() 方法进行链式调用,每个 .then() 方法返回一个新的 Promise。
    • 如果前一个 Promise 状态为 Fulfilled,则执行当前 .then() 的成功处理函数;如果状态为 Rejected,则执行当前 .then() 的失败处理函数。
    • .then() 方法可以被多次调用,形成链式操作。
  4. 异步执行

    • Promise 内部的任务是异步执行的,即使是同步代码也会被放入事件循环队列中执行。

Promise 的实现(简化版)

以下是一个简化版的 Promise 实现,用于说明其基本原理:

javascript
class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };

    const reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  let called = false;

  if (x instanceof MyPromise) {
    if (x.state === 'pending') {
      x.then(y => {
        resolvePromise(promise2, y, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, e => {
          if (called) return;
          called = true;
          reject(e);
        });
      } else {
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

简要解释

  • 构造函数:Promise 构造函数接收一个执行器函数(executor),它立即执行,并接收两个参数 resolvereject
  • 状态管理:Promise 内部通过 this.statethis.value(成功值)或 this.reason(失败原因)来管理状态和结果。
  • 链式调用:通过 then 方法实现链式调用,每个 .then() 方法返回一个新的 Promise,可以通过 resolvereject 控制状态转换。
  • 异步执行:Promise 内部使用 setTimeout 模拟异步执行,确保每个 .then() 方法都是异步执行的。

总结

Promise 的核心原理在于状态管理、链式调用和异步执行。它通过明确的状态转换和 .then() 方法的链式调用,提供了一种处理异步操作的优雅方式,避免了回调地狱(callback hell),使得异步代码更加清晰和可维护。

8. React.memouseMemo 在 React 中都用于性能优化,但它们的作用和使用场景有所不同。

React.memo

React.memo 是一个高阶组件,用于函数组件的性能优化。它的作用是在组件 props 没有变化时,避免不必要的重新渲染,从而提升性能。

  • 用法

    jsx
    const MemoizedComponent = React.memo(MyComponent);

    或者

    jsx
    const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
      // 返回 true 表示 props 没有变化,组件不需要重新渲染
      return prevProps.id === nextProps.id;
    });
  • 作用

    • 当组件的 props 没有变化时,React.memo 返回的组件会使用之前渲染的结果,避免重新执行组件的函数体和重新渲染。
  • 适用场景

    • 当函数组件的渲染代价较高,但只有部分 props 变化时,可以使用 React.memo 进行优化。

useMemo

useMemo 是一个 Hook,用于在渲染过程中缓存计算结果,避免重复计算。

  • 用法

    jsx
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 作用

    • useMemo 接收一个函数和依赖数组 [a, b],只有当依赖数组中的某个值发生变化时,才会重新计算函数的返回值。
    • 返回值是计算结果的缓存,可以是任何类型的数据,不仅限于组件。
  • 适用场景

    • 当有昂贵的计算或者复杂的数据转换逻辑时,可以使用 useMemo 缓存计算结果,避免在每次渲染时都进行重复计算。

区别总结

  • 对象

    • React.memo 是一个高阶组件,用于函数组件的性能优化,比较组件的 props 是否变化。
    • useMemo 是一个 Hook,用于缓存计算结果,在依赖变化时重新计算。
  • 作用

    • React.memo 优化组件的渲染,避免不必要的重新渲染。
    • useMemo 缓存计算结果,优化性能,避免重复计算。
  • 适用场景

    • 使用 React.memo 当组件渲染开销大且只有部分 props 变化时。
    • 使用 useMemo 当有复杂计算或数据转换逻辑时,需要缓存计算结果。

通过合理使用 React.memouseMemo,可以有效地优化 React 应用的性能,提升用户体验和页面响应速度。

9. React 中常用的 Hooks 的作用和应用场景:

1. useContext

  • 作用:用于在函数组件中访问 React 上下文(Context),获取全局数据。
  • 应用场景
    • 在组件树较深的情况下,避免通过 props 层层传递数据,直接从 Context 中获取全局数据。
    • 全局主题、用户信息等常用数据的传递和访问。

示例

jsx
import React, { useContext } from 'react';

// 创建上下文
const ThemeContext = React.createContext('light');

// 使用 useContext 获取上下文中的值
function ThemeButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme }}>Button</button>;
}

2. useReducer

  • 作用:类似于 Redux 中的 reducer,用于管理组件的复杂状态逻辑。
  • 应用场景
    • 当组件状态具有复杂的逻辑关系或需要多个状态变量联动时,可以使用 useReducer 管理状态。
    • 替代 useState 在多个状态之间切换和管理。

示例

jsx
import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

3. useRef

  • 作用:用于在函数组件中创建持久性的引用,类似于在类组件中使用 this.refs
  • 应用场景
    • 获取 DOM 元素的引用,进行 DOM 操作。
    • 缓存任何可变的值,类似于实例变量的用法。

示例

jsx
import React, { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={() => inputRef.current.focus()}>Focus Input</button>
    </div>
  );
}

4. useLayoutEffect

  • 作用:与 useEffect 类似,但是会在所有 DOM 变更之后同步调用 effect,可以立即读取 DOM 布局。
  • 应用场景
    • 当需要在浏览器 layout(布局)之后立即执行某些操作时,特别是对 DOM 进行测量或窗口大小调整等操作。

示例

jsx
import React, { useState, useLayoutEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState([0, 0]);

  useLayoutEffect(() => {
    function updateSize() {
      setSize([window.innerWidth, window.innerHeight]);
    }
    window.addEventListener('resize', updateSize);
    updateSize();
    return () => window.removeEventListener('resize', updateSize);
  }, []);

  return size;
}

5. useMemo

  • 作用:用于缓存计算结果,避免在每次渲染时重新计算。
  • 应用场景
    • 当有昂贵的计算或者复杂的数据转换逻辑时,可以使用 useMemo 缓存计算结果,提高性能。

示例

jsx
import React, { useMemo } from 'react';

function computeExpensiveValue(a, b) {
  // 模拟昂贵的计算
  return a + b;
}

function MyComponent({ a, b }) {
  const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

  return <div>Result: {memoizedValue}</div>;
}

6. useCallback

  • 作用:缓存回调函数,避免在每次渲染时重新创建回调函数。
  • 应用场景
    • 当将回调函数传递给子组件时,可以使用 useCallback 缓存回调函数,避免不必要的子组件重新渲染。

示例

jsx
import React, { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <p>Clicked {count} times</p>
    </div>
  );
}

function ChildComponent({ onClick }) {
  return <button onClick={onClick}>Click Me</button>;
}

7. useState

  • 作用:用于在函数组件中添加状态。
  • 应用场景
    • 组件内部需要存储和更新的状态数据。

示例

jsx
import React, { useState } from 'react';

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

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

8. useEffect

  • 作用:在组件渲染完成后执行副作用操作。
  • 应用场景
    • 数据获取、DOM 操作、订阅和取消订阅等副作用操作。

示例

jsx
import React, { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(seconds + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, [seconds]);

  return <div>Seconds: {seconds}</div>;
}

这些 React Hooks 在不同的场景下提供了灵活和强大的功能,能够有效地优化和管理 React 组件的状态、副作用和性能,是现代 React 开发中不可或缺的工具。

最后更新时间: