React面试进阶(五)
什么是 React 状态管理 MobX?它的应用场景有哪些?
MobX 是一个用于状态管理的库,它通过运用透明的函数式响应编程(TFRP)使状态管理变得简单和可扩展。在 MobX 中,你可以编写无模板的极简代码来精准描述出你的意图,使用熟悉的 JavaScript 赋值操作。MobX 的响应性系统会侦测到你所有的变更并把它们传送到需要的地方。所有对数据的变更和使用都会在运行时被追踪到,并构成一个截取所有状态和输出之间关系的依赖树。这样保证了那些依赖于状态的计算只有在真正需要时才会运行。
MobX 的应用场景包括但不限于:
- 大型应用:在大型应用中,状态管理变得尤为重要。MobX 提供了可预测且易于调试的状态管理方式,使得开发者能够更好地管理应用的状态。
- 需要高效性能的应用:MobX 通过只更新真正需要更新的部分,避免了不必要的渲染,从而提高了应用的性能。
- 组件化开发:在 React 等组件化框架中,MobX 可以很好地与组件结合,实现状态在组件间的共享和更新。
React 项目中如何引入 SVG 文件?
在 React 项目中引入 SVG 文件有多种方式,具体取决于项目的配置和需求。以下是一些常见的方法:
-
方法一:使用 React 的内置功能
- 将 SVG 文件放置在 React 项目的合适位置,如
src/assets
文件夹中。 - 在 React 组件中通过
import
语句导入 SVG 文件,并使用 React 的内置功能将其转换为一个 React 组件。 - 在组件的 JSX 中使用该 SVG 组件,并可以为其添加属性如
width
和height
来调整大小。
- 将 SVG 文件放置在 React 项目的合适位置,如
-
方法二:使用 svg-url-loader 或 file-loader
- 如果项目使用了 webpack,并且需要更灵活地处理 SVG 文件(例如,将 SVG 作为 URL 引入而不是作为 React 组件),可以配置 webpack 来使用 svg-url-loader 或 file-loader。
- 安装相应的 loader,并在 webpack 配置文件中添加相应的规则。
- 在 React 组件中通过
import
语句导入 SVG 文件的 URL。 - 在组件的 JSX 中使用
<img>
标签,并将导入的 SVG 文件 URL 作为src
属性的值。
-
方法三:使用第三方库
- 有些第三方库(如 react-svg)提供了更高级的功能,例如动态加载、样式处理等。
- 安装相应的库,并在 React 组件中引入并使用该库来处理 SVG 文件。
如何配置 React Router 实现路由切换?
React Router 是 React 社区中最受欢迎的路由库之一,它提供了一组用于处理路由的组件和工具。以下是如何配置 React Router 实现路由切换的基本步骤:
-
安装 React Router:
通过 npm 或 yarn 安装 React Router 及其相关依赖。
-
设置路由:
- 创建一个路由配置文件(如
App.js
),并引入 React Router 的相关组件(如BrowserRouter
、Route
和Switch
)。 - 使用
BrowserRouter
包裹整个应用,以便使用 HTML5 的历史记录 API。 - 在
BrowserRouter
内部使用Route
组件来定义不同的路由路径和对应的组件。 - 使用
Switch
组件来包裹所有的Route
组件,以便只渲染与当前 URL 匹配的路由组件。
- 创建一个路由配置文件(如
-
实现路由切换:
- 在应用的组件中使用 React Router 提供的
<Link>
组件来创建导航链接。 - 当用户点击
<Link>
组件时,React Router 会更新 URL 并渲染与新 URL 匹配的路由组件。 - 还可以使用编程式导航来实现路由切换,即使用
useHistory
钩子(在 React Router v5 中)或useNavigate
钩子(在 React Router v6 中)来在代码中触发路由跳转。
- 在应用的组件中使用 React Router 提供的
React 的执行流程是怎样的?可以从源码的角度深入说明
React 的执行流程可以分为以下几个关键步骤:
-
渲染触发:
通过更新某处的状态来触发渲染。这个状态可以是组件的 props 或 state。
-
渲染阶段:
React 调用组件函数,确定如何更新 DOM。这是内部计算阶段,不会立即对屏幕产生视觉变化。在这个阶段,React 会构建一个新的虚拟 DOM 树,并与之前的虚拟 DOM 树进行比较(diff 算法),以确定需要更新的部分。
- 从源码的角度看,渲染阶段涉及 React 元素的创建、虚拟 DOM 树的构建以及 diff 算法的执行。React 元素是描述 UI 的对象,它们通过 JSX 语法被编译成 JavaScript 对象。虚拟 DOM 树是一个在内存中表示真实 DOM 的轻量级副本。diff 算法用于比较新旧虚拟 DOM 树之间的差异,并计算出最小的更新集。
-
提交阶段:
React 实际写入 DOM,进行元素的更新、插入和删除。这个阶段会对屏幕产生视觉变化。
- 从源码的角度看,提交阶段涉及将计算出的更新集应用到真实 DOM 上。React 会遍历更新集,并根据需要执行 DOM 操作(如插入、更新或删除节点)。
-
浏览器绘制:
浏览器将更新后的内容绘制到屏幕上。
React 的这种执行流程使得它能够在状态变化时高效地更新 UI,而无需重新渲染整个页面。通过虚拟 DOM 和 diff 算法,React 能够计算出最小的更新集,并只更新需要改变的部分,从而提高了性能。
React 是什么?它的主要特点有哪些?
React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发和维护。React 的主要特点包括:
-
声明式设计:
React 使创建交互式 UI 变得轻而易举。开发者只需描述 UI 应该是什么样的,React 会负责在状态变化时更新 UI。这种方式使得代码更容易理解,减少了调试的复杂性。
-
组件化:
React 提倡组件化的开发方式。通过将 UI 拆分为小的、可重用的组件,开发者可以更好地组织代码,提高应用的可维护性和可扩展性。同时,组件的复用性也降低了开发成本。
-
高效:
React 通过对 DOM 的模拟(虚拟 DOM),最大限度地减少与 DOM 的交互。这种高效的更新机制使得 React 在处理高频率的用户交互时表现出色,而不影响性能。
-
灵活:
React 非常灵活,可以轻松地与其他库或框架集成。无论你现在使用什么技术栈,都可以在无需重写现有代码的前提下,通过引入 React 来开发新功能。
在 React 中,如何实现组件间的过渡动画?
在 React 中实现组件间的过渡动画有多种方法,以下是一些常见的方法:
-
使用 CSS 类名:
可以通过添加或移除 CSS 类名来触发过渡和动画效果。在组件进入或离开时,通过动态改变类名来改变样式。
-
使用 React Transition Group:
React Transition Group 是一个常用的第三方库,提供了在组件进入和离开时执行过渡动画的功能。它通过在组件的生命周期方法中添加 CSS 类名来实现过渡效果。
-
使用 React Spring:
React Spring 是另一个流行的动画库,它基于物理动画原理,提供了强大的动画功能。React Spring 使用 Spring 和 Transition 组件来实现组件之间的过渡动画,可以通过配置动画属性和使用插值函数来创建各种复杂的动画效果。
-
使用 CSS-in-JS 库:
CSS-in-JS 库(如 styled-components、Emotion 等)可以在组件中直接编写 CSS 样式,并提供了动态生成类名的功能。通过在组件的状态变化时动态修改样式,可以实现组件之间的过渡效果。
React 的 useEffect 和 useLayoutEffect 有什么区别?
useEffect
和 useLayoutEffect
都是 React 的 Hook,用于在函数组件中执行副作用操作。它们的主要区别在于执行时机和用途:
-
useEffect:
useEffect
会在浏览器绘制之后执行,因此不会阻塞浏览器的绘制过程。它适用于大多数副作用操作,如数据获取、订阅外部数据源以及手动更改 React 组件中的 DOM 等。 -
useLayoutEffect:
useLayoutEffect
会在所有的 DOM 变更之后同步调用,因此在浏览器绘制之前执行。这使得它能够在 DOM 更新后同步读取 DOM 布局并同步触发重绘。然而,由于它会阻塞浏览器的绘制过程,因此应谨慎使用以避免性能问题。useLayoutEffect
适用于需要在 DOM 更新后同步读取或修改 DOM 布局的场景。
如何封装一个 React 的全局公共组件?
封装一个 React 的全局公共组件通常涉及以下几个步骤:
-
确定组件的功能和样式:
首先,明确组件的功能和样式需求。这将有助于确定组件的 props 和 state,以及需要使用的 CSS 样式。
-
创建组件文件:
在项目的合适位置创建一个新的组件文件(如
.jsx
或.tsx
文件)。 -
定义组件:
在组件文件中定义一个函数组件或类组件,并添加必要的 props 和 state。
-
实现组件的逻辑和样式:
根据组件的功能和样式需求,实现组件的逻辑和样式。这包括处理 props 和 state 的变化、调用其他函数或 API、以及应用 CSS 样式等。
-
导出组件:
在组件文件的末尾,使用
export
语句导出组件,以便在其他文件中引用Redux 底层如何实现属性传递?
Redux 通过一种集中的方式来管理应用的状态,并使用“action”和“reducer”来实现状态的更新和属性的传递。具体来说,Redux 使用一个单一的状态树(store)来存储整个应用的状态。当需要更新状态时,应用中的组件会发送一个 action 到 reducer,reducer 根据 action 的类型和内容来更新状态树,并返回一个新的状态对象。这样,所有订阅了状态变化的组件都会收到更新后的状态,从而实现属性的传递。
React 的 useState 和 this.state 有什么区别?
useState 和 this.state 都是 React 中用于管理状态的方式,但它们有一些关键的区别:
- 使用场景:useState 是 React 函数组件中的钩子,用于在函数组件中添加状态;而 this.state 是类组件中用于管理状态的一个属性。
- 声明和更新:在函数组件中,通过 useState 钩子声明状态变量,并返回一个包含当前状态和一个更新状态函数的数组;在类组件中,状态通常通过 this.state 来访问,并通过 this.setState 方法来更新状态。
- 异步性:无论是 useState 还是 this.setState,在更新状态时都是异步的。
如何在 React 中实现双向绑定,并将其抽象成公共组件?
在 React 中,可以通过受控组件的方式来实现双向绑定,并将其封装成一个通用的输入组件。具体步骤如下:
- 创建一个通用的输入组件,如 InputField,它接受 value 和 onChange 两个属性。这些属性用于实现双向绑定。
- 在 InputField 组件内部,定义一个 handleChange 函数,该函数在输入框的值改变时被调用,并传递新的值给 onChange 回调函数。
- 在父组件中使用 InputField 组件,并管理输入框的状态。父组件将状态传递给 InputField 组件,并通过 onChange 回调函数更新状态,从而实现双向绑定。
在 React 项目中,如何进行静态类型检测?
在 React 项目中,可以通过多种方式实现静态类型检测,以提高代码质量和可维护性。常见的方法包括:
- 使用 TypeScript:TypeScript 是 JavaScript 的一个超集,它为 JavaScript 添加了静态类型检查功能。将 React 项目迁移到 TypeScript 可以带来更好的类型安全性和开发体验。
- 使用 Flow:Flow 是 Facebook 开发的一个静态类型检查器,它可以与 React 项目一起使用,提供类型检查功能。
- 使用 ESLint:ESLint 是一个流行的 JavaScript 代码检查工具,通过配置 ESLint 规则,可以捕获潜在的类型错误。
- 使用 React 的 PropTypes:PropTypes 允许在组件中使用类型注解来指定输入属性的类型,从而捕获类型错误。
React 事件绑定的原理是怎样的?
React 中的事件绑定并不是直接绑定在对应的 DOM 上,而是统一绑定在根组件或 document 上(从 React 17 开始,所有事件都绑定在 root 组件上),采用事件冒泡的形式向上传递。这样做的好处是 React 可以统一处理所有的事件绑定,在组件销毁时可以统一移除绑定。React 使用合成事件(SyntheticEvent)来模拟原生 DOM 事件,并提供了一个与原生事件相同的接口。当事件发生时,React 会将事件封装成合成事件,并调用相应的处理函数。
React 的 componentWillReceiveProps 的触发条件是什么?
componentWillReceiveProps 的触发条件是当组件接收到的 props 发生变化时。具体来说,如果父组件的状态发生变化并导致传递给子组件的 props 发生变化,那么子组件的 componentWillReceiveProps 将会被触发。需要注意的是,如果组件内部的状态(state)发生变化,componentWillReceiveProps 并不会被触发。
在 React 中如何进行状态管理?
在 React 中,有多种方法可以进行状态管理,包括:
- 使用 useState 和 useReducer 钩子:useState 适用于简单的状态管理,而 useReducer 则适用于具有复杂状态逻辑的组件。
- 使用 React 的 Context:Context 提供了一种在组件树中共享数据的方式,适用于需要在多个组件之间共享状态的情况。
- 使用第三方的状态管理库:如 Redux 或 MobX,这些库提供了更强大和灵活的状态管理功能,适用于大型和复杂的应用。
React 性能优化的方法有哪些?比如怎么提升组件渲染效率?
React 性能优化的方法包括:
- 避免不必要的渲染:通过 React.memo、shouldComponentUpdate 或 useMemo 等方法来避免不必要的组件渲染。
- 使用纯组件:纯组件在给定相同的 props 和 state 时总是渲染相同的结果,这有助于 React 进行性能优化。
- 拆分组件:将大型组件拆分成小型、独立的组件,可以提高渲染效率。
- 避免在 render 方法中进行复杂的计算:可以将复杂的计算放在 useMemo 或 componentDidMount 等生命周期方法中。
- 使用懒加载和代码拆分:通过 React.lazy 和 Suspense 来实现组件的懒加载,以减少初始加载时间。
React 有哪些优秀的特性?是怎么实现的?
React 的优秀特性包括:
- 组件化:React 通过组件化的方式来构建用户界面,使得代码更加模块化和可重用。
- 虚拟 DOM:React 使用虚拟 DOM 来提高渲染效率,通过比较虚拟 DOM 和真实 DOM 的差异来更新界面。
- 单向数据流:React 采用了单向数据流的设计模式,使得状态管理更加清晰和可预测。
- 丰富的生态系统:React 拥有丰富的生态系统,包括各种状态管理库、路由库、UI 组件库等,方便开发者进行快速开发。
Redux 的 action 是什么?如何在 Redux 中定义 action?
在 Redux 中,action 是一个描述发生了什么的普通 JavaScript 对象。它必须包含一个 type 属性来指示所执行的动作类型,还可以包含一些其他自定义的数据。通过调用 action creator(一个返回 action 对象的函数),可以创建一个 action。在 Redux 中定义 action 的步骤通常包括:
- 定义一个常量来表示 action 的类型。
- 创建一个 action creator 函数,该函数返回一个包含 type 属性和其他自定义数据的 action 对象。
什么是单一数据源?React 中怎么实现单一数据源?
单一数据源是指整个应用的状态被存储在一个集中的地方,这样可以确保状态的可预测性和可维护性。在 React 中,可以通过使用 Redux 或 MobX 等状态管理库来实现单一数据源。这些库提供了一个集中的 store 来存储应用的状态,并通过 action 和 reducer(或类似的机制)来更新状态。
在 React 中,发起网络请求应该放在生命周期的哪个阶段?为什么?
在 React 中,发起网络请求通常放在组件的 componentDidMount
生命周期方法中(对于类组件)或 useEffect 钩子中(对于函数组件)。这样做的原因是这些生命周期方法或钩子在组件挂载到 DOM 后被调用,此时可以确保组件已经渲染并准备好接收和处理网络请求的结果。
在 React 中,修改 props 会引发哪些生命周期函数?
在 React 中,如果父组件的状态发生变化并导致传递给子组件的 props 发生变化,那么子组件将会接收到新的 props。虽然修改 props 不会直接触发子组件的生命周期函数(如 componentDidUpdate),但可以通过在子组件中使用 componentDidUpdate
(对于类组件)或 useEffect(对于函数组件)来检测 props 的变化并做出相应的处理。在 componentDidUpdate
中,可以通过比较新旧 props 来决定是否执行某些操作;在 useEffect 中,可以通过依赖数组来监听 props 的变化。
什么是 React 的 immutable?它有什么作用?如何使用?
什么是 React 的 immutable:
Immutable Data(不可变数据)一旦创建,就不能再被更改。对 Immutable 对象的任何增删改操作都会返回一个新的 Immutable 对象,而不是修改原来的对象。
作用:
- 提高数据安全性:由于不可变数据不能被修改,这可以避免在数据传递和处理过程中出现的意外变更,从而提高应用的数据安全性。
- 优化性能:Immutable 使用了结构共享(Structural Sharing)和持久化数据结构(Persistent Data Structure),在修改数据时,只修改受影响的节点,其他节点则进行共享,从而减少了不必要的内存分配和数据复制,提高了性能。
如何使用:
在 React 中使用 immutable 数据,通常需要借助一些库,如 immutable.js
。以下是一个简单的使用示例:
import React, { Component } from "react";
import { fromJS, Map } from "immutable";
class Test extends Component {
render() {
return (
<>
<div>hello world</div>
</>
);
}
componentDidMount() {
// 创建 immutable 数据
const immutableMapObj = fromJS({ a: 1, b: { bb: 3 } });
// 新增
const addImmutableMapObj = immutableMapObj.setIn(['b', 'bb'], 4);
console.log('新增: ', addImmutableMapObj.toJS());
// 删除
const deleteImmutableMapObj = immutableMapObj.deleteIn(['b', 'bb']);
console.log('删除: ', deleteImmutableMapObj.toJS());
// 修改
const updateImmutableMapObj = immutableMapObj.setIn(['b', 'bb'], 444);
console.log('修改: ', updateImmutableMapObj.toJS());
// 查询
const selectImmutableMapObj = immutableMapObj;
console.log('查值: ', selectImmutableMapObj.getIn(['b', 'bb']));
// 使用 Map 创建 immutable 数据
const map1 = Map({ a: 1, b: 2, c: 3 });
// 修改原始 immutable 对象 map1 为 map2
const map2 = map1.set('b', 50);
// 获取原始 immutable 对象的属性值
console.log(map1.get('b')); // 2
// 获取新 immutable 对象的属性值
console.log(map2.get('b')); // 50
}
}
export default Test;
在 React 的渲染过程中,当兄弟节点的 key 值不同的时候,它们是如何处理的?
在 React 的渲染过程中,当兄弟节点的 key 值不同的时候,React 会认为这两个节点是完全不同的节点。因此,React 会先销毁旧的节点,然后重新创建新的节点。这个过程会导致组件的重新挂载和状态的丢失。所以,在使用列表渲染时,应该尽量保持兄弟节点的 key 值稳定,以避免不必要的组件重新渲染和状态丢失。
在 React 中如何引用 Sass 或 Less?
在 React 中引用 Sass 或 Less,可以通过以下几种方法:
-
使用 CSS 预处理器的内置功能:
- 在创建 React 项目时,可以选择使用 Create React App 工具。
- 安装所需的依赖:
npm install node-sass
(对于 Sass)或npm install less less-loader
(对于 Less)。 - 在项目的根目录下创建一个名为
src
的文件夹,并在其中创建一个名为styles
的文件夹。 - 在
styles
文件夹中创建一个主样式文件(如main.scss
或main.less
)。 - 在 React 组件中引入主样式文件。
-
使用 CSS 模块化:
- 将 Sass 或 Less 文件的模块化功能与 React 组件的模块化功能结合在一起。
- 在 React 组件所在的目录中创建一个名为
styles
的文件夹,并在其中创建一个样式文件(如MyComponent.module.scss
或MyComponent.module.less
)。 - 在 React 组件中引入样式文件并使用它。
-
使用第三方库:
- 安装所需的依赖:
npm install node-sass
(对于 Sass)或npm install less less-loader
(对于 Less)。 - 安装配置相应的 webpack loader。
- 在
webpack.config.js
文件中添加相应的配置。
- 安装所需的依赖:
说一下你对虚拟 DOM(vnode)的理解
虚拟 DOM(Virtual DOM)是 React 等现代前端框架中的一个核心概念。它是一个用 JavaScript 对象表示的树状结构,模拟了真实 DOM 的结构。虚拟 DOM 存在于内存中,不是真实渲染到页面上的 DOM。它允许开发者以声明式的方式编写代码,而不必直接操作真实的 DOM。
当组件的状态或属性发生变化时,React 会先更新虚拟 DOM 树,并通过 Diff 算法(如 React 的协调算法)比较新旧虚拟 DOM 的差异,从而计算出需要更新的最小 DOM 集合。这种方式减少了直接操作真实 DOM 的次数,提高了渲染性能。
虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个性能更高?为什么?
虚拟 DOM 的性能通常比直接操作原生 DOM 更高。原因如下:
- 减少直接操作真实 DOM 的次数:虚拟 DOM 允许开发者以声明式的方式编写代码,React 会负责将虚拟 DOM 的变化映射到真实 DOM 上。这种方式减少了直接操作真实 DOM 的次数,从而提高了性能。
- 利用 Diff 算法优化更新:当组件的状态或属性发生变化时,React 会使用 Diff 算法比较新旧虚拟 DOM 的差异,从而计算出需要更新的最小 DOM 集合。这避免了不必要的 DOM 操作,提高了渲染效率。
- 结构共享和持久化数据结构:像 immutable.js 这样的库提供了结构共享和持久化数据结构的功能,进一步减少了内存分配和数据复制的开销,提高了性能。
React 项目使用 Hooks 时需要遵守哪些原则?
React Hooks 是 React 16.8 引入的新特性,使函数组件可以使用状态和其他 React 特性。使用 Hooks 时需要遵守以下原则:
- 只在最顶层调用 Hooks:Hooks 必须在函数组件的最顶层调用,而不能在循环、条件语句或嵌套函数中调用。这一规则确保了每次组件渲染时 Hooks 的调用顺序保持一致。
- 只在 React 函数组件和自定义 Hooks 中调用 Hooks:Hooks 只能在 React 的函数组件和自定义 Hooks 中调用,不能在普通的 JavaScript 函数中使用。
React Native 和 React 有什么区别?
React Native 和 React 有以下主要区别:
- 框架作用的平台不同:React 是将浏览器作为渲染平台,而 React Native 是用来开发真正原生渲染的 iOS 和 Android 移动应用的 JS 框架。
- 工作原理有差别:React 的工作原理是基于虚拟 DOM,通过比较新旧虚拟 DOM 的差异来更新真实 DOM。而 React Native 的工作原理是调用 Objective-C 的 API 去渲染 iOS 组件,调用 Java API 去渲染 Android 组件。
- 渲染周期不同:React 的渲染周期开始于组件挂载到 DOM 之后,接着 React 进入渲染周期并根据需要渲染组件。而 React Native 的生命周期与 React 基本相同,但在渲染上因为依赖于桥接,并不在 UI 主线程运行。
- 元素和样式处理不同:在 React 中,视图最终需要渲染成普通的 HTML 元素,并使用 CSS 样式。而在 React Native 中,所有元素都会被平台指定的 React 组件替换(如 iOS 中的 UIView),并使用一种标准的方法来处理样式(主要通过 flexbox 进行布局)。
在 React 中,调用 setState 会更新哪些生命周期函数?
在 React 中,调用 setState
会触发组件的更新流程,这通常涉及到以下生命周期函数(在类组件中):
- shouldComponentUpdate:在组件接收到新的 props 或 state 之前调用,用于判断是否需要继续执行更新流程。如果返回
false
,则不会继续更新流程。 - getSnapshotBeforeUpdate:在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在更新之前捕获一些信息(例如滚动位置)。此生命周期方法的返回值将作为
componentDidUpdate
的第三个参数。 - componentDidUpdate:在更新发生后立即调用。可以在这个方法中执行 DOM 操作或更新其他状态。
需要注意的是,在函数组件中,没有这些生命周期函数的概念。但是,可以使用 useEffect
Hook 来模拟类似的生命周期行为。例如,可以在 useEffect
的依赖项数组中包含 state
变量,以便在 state
更新时执行副作用。