[React] 2023年最新面试题

[React] 2023年最新面试题

      • 1. class 组件与函数组件的区别
        • 1. 语法不同
        • 2. 状态的管理不同
        • 3. 声明周期方法不同
      • 2. react 18 新特性有那些?
        • 新增 createRoot API
        • 自动批处理
        • 过渡更新
        • 新的Hook
      • 3. redux 和 react-redux 的区别
      • 4. redux 中间件的原理
      • 5. setState 发生了什么 ,render 函数做了什么
      • 6. 虚拟DOM, Fiber
        • 虚拟DOM
        • Fiber
      • 7. useEffect 与 useLayoutEffect 的区别? 同步异步
      • 8. useCallback 和 useMemo的区别
      • 9. Webpack 打包原理

1. class 组件与函数组件的区别

1. 语法不同

类组件: 使用 ES6 的类语法定义组件。具有 render 方法,并在类组件中使用 this 来引用组件实例。

函数组件: 使用函数定义组件,通常是无状态的。在 React 16.8 之后,可以使用 Hook 来在函数组件中使用状态和其他 React 特性。

2. 状态的管理不同

类组件: 使用 this.state 和 this.setState 来管理组件的状态。

函数组件: 使用 useState Hook 来在函数组件中添加状态。

3. 声明周期方法不同

类组件: 有一系列生命周期方法,如 componentDidMount、componentDidUpdate、componentWillUnmount 等,用于处理组件的生命周期事件。

函数组件: 使用 useEffect Hook 来处理函数组件的生命周期事件

const MyFunctionComponent = () => {
  useEffect(() => {
    // 在组件挂载后和更新后执行
    return () => {
      // 在组件卸载前执行
    };
  }, 
  [] // 第二个参数来控制何时运行副作用函数
);
  1. 复杂性:

class 组件语法相对冗长,可能导致较多样板代码。
生命周期方法分散在不同生命周期阶段。

函数组件语法更简洁,减少样板代码。
生命周期相关逻辑集中在 useEffect 中,提高可读性。

  1. 可读性和维护性:

函数组件使代码更易读、易写。
更好地符合 React 社区的最新实践和推荐。

总的来说,函数组件和 Hook 的方式更加推荐,特别是在新项目中。它们使得 React 组件更易于理解、编写和维护,同时提供了更灵活的状态管理和生命周期处理机制。类组件仍然存在,可能在老项目中或特殊场景中使用。

2. react 18 新特性有那些?

新增 createRoot API

[React] 2023年最新面试题_第1张图片

自动批处理

React 18 通过默认做更多批量处理来增加开箱即用性能提升。批量处理指的是 React 为了提高性能将多个 state 更新分组到一个单独的重渲染。如果仍然希望setState之后立即重新渲染,只需要使用flushSync包裹

function handleClick() {
	// React 18 之前的版本
	(/*...*/).then(() => {
		setCount(c => c + 1); // 立刻重新渲染
		setShow(show => !show); // 立刻重新渲染
	});
}

function handleClick() {
	// React 18
	fecth(/*...*/).then(() => {
		ReactDOM.flushSync(() => {
			setCount(c => c + 1); // 立刻重新渲染
			setFlag(f => !f);
		})
	})
}

过渡更新

用于并发渲染的并发新特性,开发者希望能够在Web Platform引入并发渲染,来实现多个渲染任务的并行渲染,其中Suspense就是基于此诞生的。

React18支持并发特性的三个API

  • startTransition() 在屏幕内容之间进行导航,而不会阻塞用户输入
  • useDeferredValue() 节流处理开销巨大的重新渲染
  • useTransition() startTransition的 hooks钩子
import { startTransition } from 'react';

// 紧急更新
setInputValue(input)

// 标记回调函数内的更新为 非紧急更新
startTransition(() => {
	setSearchQuery(input)
})

这个hook适用于设置延迟值

function Page() {
	const [filters, mergeFilter] = useMergeState(defaultFilters);
	const deferedFilters = React.useDeferredValue(filters);
	
	return (
		<>
			<Filters filters={filters} />
			<List filters={deferedFilters}>
		</>
	)
}

useDeferredValue() 会将List组件的渲染变得更加平滑,深层次看来是 defered value 引起的渲染会被标记为不紧急渲染,会被filters引起的渲染进行抢占,进而达到用户快速输入搜索等场景下页面抖动或卡顿问题。

新的Hook
  • useId 用于生成在客户端和服务端两侧都独一无二的 id
  • useTransition 将一些状态更新标记为过渡更新
  • useDeferredValue 允许推迟渲染树的非紧急更新,类似防抖
  • useSyncExternalStore 允许使用第三方状态管理来支持并发模式,并且能通过对 store 进行强制更新实现数据同步。
  • useInsertionEffect 允许 CSS-in-JS 库解决在渲染中注入样式的性能问题。

3. redux 和 react-redux 的区别

react-redux 是一个用于在 React 应用中管理状态(state)的库,它是基于 Redux 的 React 绑定库。Redux 是一种用于管理 JavaScript 应用程序状态的可预测状态容器,而 react-redux 则简化了在 React 应用中使用 Redux 的过程。

4. redux 中间件的原理

Redux 中间件是一种在 action 被发起和 reducer 处理这个 action 之间进行操作的机制。中间件允许你在 Redux 应用中的 dispatch 过程中拦截、检查、或者修改 action 和 state。Redux 中间件的原理涉及到 Redux 的 dispatch 过程、函数式编程的概念,以及中间件的链式调用。

Redux 的基本 dispatch 过程如下:

  1. 应用中的组件通过调用 store.dispatch(action) 来分发 action。

  2. dispatch 被传递给中间件链。中间件链是在创建 store 时通过 applyMiddleware 函数添加的。

  3. 中间件对 action 进行处理,可以取消、修改、或者发起其他 action。

  4. action 最终到达 reducer,reducer 根据 action 的类型进行相应的 state 更新。

  5. state 更新后,通知所有订阅的组件进行重新渲染。

Redux 中间件的原理主要体现在中间件链的设计和执行过程上:

  1. 中间件链的设计: 中间件被组织成一个链表形式,每个中间件都有机会处理 action。这个链表是通过 applyMiddleware 函数创建的。

  2. dispatch 的增强: 中间件提供了一种方式,使得每个 action 发起的时候都可以执行一些额外的逻辑。中间件通过封装 dispatch 函数,并将其传递给链中的下一个中间件。

  3. next 函数的传递: 在中间件链中,每个中间件都接收到一个 next 函数,它代表链中的下一个中间件。中间件可以选择调用 next(action) 来将 action 传递给下一个中间件,也可以选择不调用,中止链的执行。

  4. 异步处理: 由于中间件可以获取 dispatch 函数和 getState 函数,因此它们可以进行异步操作。例如,Redux Thunk 中间件就允许 action 创建函数返回一个函数而不仅仅是一个普通的 action 对象,这个函数可以在异步操作完成后再 dispatch。

整体来说,Redux 中间件的原理是通过改变 dispatch 函数的行为,使其能够在 action 到达 reducer 之前经过一系列的处理。这种机制使得 Redux 的功能能够被灵活地扩展和定制,例如通过中间件实现异步操作、日志记录、时间旅行调试等功能。

5. setState 发生了什么 ,render 函数做了什么

setState

  1. 接受新的状态: setState可以接受一个新的状态对象或一个函数,该函数返回新的状态。这个状态可以包含要更新的部分或全部组件状态。
  2. 将新的状态合并到当前状态: React会将新状态与当前状态进行合并。这是因为setState可能是异步的,React可能会在后台批量处理多个setState调用。
  3. 触发组件更新流程: 一旦新状态被合并,React会触发组件的更新流程。

render函数

  1. 构建虚拟DOM: 当组件被更新时,React首先调用render函数。render函数负责返回一个虚拟DOM(Virtual DOM)树,描述了组件在更新后应该如何呈现。
  2. 对比虚拟DOM和上一次渲染的虚拟DOM: React使用虚拟DOM来提高性能。在render函数返回新的虚拟DOM后,React会将它与上一次渲染的虚拟DOM进行比较,找出实际发生变化的部分。
  3. 计算更新的部分: React会计算出需要进行实际DOM更新的部分,以最小化对实际DOM的操作。
  4. 应用更新: 最后,React将计算出的更新应用到实际DOM上,只更新发生变化的部分,以提高性能。

setState触发了React的更新流程,而render函数则负责描述组件在更新后的状态应该如何呈现,并最终将这个描述应用到实际的DOM上。 React通过使用虚拟DOM和差异算法,尽可能高效地更新实际DOM,以提高性能和用户体验。

6. 虚拟DOM, Fiber

虚拟DOM

虚拟DOM是React的一种概念,它是在内存中维护的一份虚拟的DOM树。当组件的状态发生变化时,React首先在虚拟DOM上进行操作,而不是直接操作实际的DOM。这个虚拟DOM是一个轻量级的 JavaScript 对象树,它映射了真实DOM的结构。

虚拟DOM的工作流程如下:

组件状态更新: 当组件的状态发生变化时,React会创建一个新的虚拟DOM树。

虚拟DOM对比: 新的虚拟DOM树与先前的虚拟DOM树进行比较,找出发生变化的部分。

差异计算: React使用差异算法来计算需要进行更新的最小操作集。

实际DOM更新: 最终,React将计算出的变化应用到实际的DOM上,实现页面的更新。

通过使用虚拟DOM,React可以最小化直接操作实际DOM的次数,从而提高性能。这是因为直接DOM操作通常是昂贵的,而虚拟DOM的比较和计算过程是在内存中进行的,相对来说更快。

Fiber

Fiber是React 16 中引入的一种新的协调引擎,用于更灵活地控制React的渲染过程。在React 16之前,React的渲染过程是递归的,当组件层次很深时,可能导致浏览器阻塞。
Fiber的目标是实现增量式渲染,使React能够在多个帧中分割渲染工作,防止阻塞主线程。Fiber架构引入了一种新的数据结构,可以更灵活地中断、终止或恢复渲染过程,以适应不同的优先级和交互场景。

要点包括:

可中断的渲染: Fiber允许React在执行过程中中断渲染,执行其他高优先级的任务,然后返回并继续渲染。这有助于提高应用的响应性。

优先级调度: Fiber引入了任务调度器,可以根据任务的优先级动态调整渲染的顺序,确保高优先级任务优先完成。

总的来说,虚拟DOM和Fiber是React中两个关键的技术,它们共同为React提供了高效的渲染和更新机制,提高了应用的性能和用户体验。

7. useEffect 与 useLayoutEffect 的区别? 同步异步

useEffect:

  1. 异步执行: useEffect 中的回调函数是异步执行的。这意味着它不会阻塞浏览器渲染,而是会在浏览器完成渲染后执行。因此,它对页面的布局和绘制不会产生直接影响。
  2. 执行时机: useEffect 的效果函数会在浏览器完成渲染后才会被调用。
useEffect(() => {
  // 异步执行的副作用
  console.log('useEffect callback');
}, [/* dependencies */]);

useLayoutEffect

  1. 同步执行: useLayoutEffect 中的回调函数是同步执行的。它会在浏览器布局完成、但在浏览器绘制之前立即执行。这可能会导致一些阻塞,因此应该谨慎使用,以避免对性能产生不利影响。
  1. 执行时机: useLayoutEffect 的效果函数会在浏览器完成布局、但在浏览器绘制之前立即被调用。

如何选择

如果你的副作用不需要同步执行,并且不影响布局,那么使用 useEffect 是更安全的选择,因为它不会导致阻塞。

如果你的副作用依赖于 DOM 的布局信息,并且需要在浏览器绘制之前立即执行,那么 useLayoutEffect 可能更适合。

大多数情况下,优先选择使用 useEffect,因为它更不容易导致性能问题。 useLayoutEffect 应该在确实需要在布局阶段同步执行的情况下使用。

8. useCallback 和 useMemo的区别

useCallbackuseMemo 都是 React 中用于性能优化的 Hook,它们的目标是避免不必要的计算和渲染。尽管它们在使用上有相似之处,但它们的主要区别在于它们的应用场景返回值的类型

useCallback 用于缓存回调函数,以避免在每次渲染时创建新的回调。它的语法如下:

const memoizedCallback = useCallback(
  () => {
    // 回调函数
  },
  [/* 依赖项数组 */]
);

应用场景: 主要用于防止回调函数在每次渲染时都被重新创建,特别是在传递给子组件时,可以避免不必要的子组件重新渲染。

返回值: 返回一个 memoized(记忆的)版本的回调函数

useMemo 用于缓存计算结果,以避免在每次渲染时重新计算。它的语法如下:

const memoizedValue = useMemo(
  () => {
    // 计算结果
    return computeExpensiveValue(a, b);
  },
  [a, b]
);

应用场景: 主要用于优化计算昂贵的值,确保只在相关依赖项变化时进行重新计算。

返回值: 返回一个 memoized(记忆的)版本的计算结果

区别

返回类型: useCallback 返回一个 memoized 的回调函数,而 useMemo 返回一个 memoized 的计算结果。

主要应用场景: useCallback 主要用于缓存回调函数,而 useMemo 主要用于缓存计算结果。

总的来说,useCallback 用于缓存回调函数,而 useMemo 用于缓存计算结果。选择使用哪个取决于你的具体需求。如果你需要缓存一个回调函数以避免不必要的重新渲染,使用 useCallback。如果你需要缓存计算结果以提高性能,使用 useMemo。在一些情况下,你可能需要同时使用这两个 Hook。

9. Webpack 打包原理

Webpack 是一个现代的 JavaScript 应用程序静态模块打包工具。它的打包原理涉及以下关键概念:

入口(Entry): Webpack 从一个或多个入口点开始,根据入口点的依赖关系,递归地构建一个依赖图。

依赖图(Dependency Graph): Webpack 分析入口文件及其所有直接或间接依赖的文件,形成一个依赖图。每个模块都可以是一个文件,也可以是一个通过加载器(Loaders)处理的文件。

加载器(Loaders): 加载器是用于对模块进行转换的工具,它允许你在 import 模块时预处理文件。例如,可以使用 Babel 加载器将 ES6 代码转换为 ES5。

插件(Plugins): 插件用于执行各种任务,例如优化、压缩、拷贝文件等。插件可以在整个构建过程中执行各种任务,包括对生成的 bundle 进行优化和处理。

输出(Output): Webpack 根据依赖图生成一个或多个输出文件,这些文件包含了打包后的代码。输出文件的生成位置和文件名可以通过配置进行指定。

模块(Modules): 在 Webpack 中,一切皆模块。每个文件都被视为一个模块,并且可以包含 JavaScript、CSS、图片等各种类型的资源。

Webpack 的打包过程可以概括为以下步骤:

解析(Resolving): Webpack 从入口开始解析模块的依赖关系,找到每个模块的位置。

加载(Loading): 解析完成后,Webpack 将加载模块,这可能涉及到对文件的读取、转换和编译。

转换(Transformation): 加载器对模块进行转换,例如将 ES6 语法转换为 ES5、将 Sass 转换为 CSS 等。

打包(Bundling): 经过加载和转换后的模块被放置到一个或多个 bundle 中,这是由输出配置指定的。

输出(Output): Webpack 将 bundle 写入文件系统,生成最终的打包结果。

整个过程中,Webpack 通过使用各种配置选项、加载器和插件来灵活地处理不同类型的文件和任务,实现高度可定制的打包过程。Webpack 的灵活性和强大性使得它成为现代前端开发中的主流打包工具。

你可能感兴趣的:(react.js,前端,javascript)