1. 在 React 中,useState
和 useReducer
有什么区别?在什么场景下应该选择使用 useReducer
?
useState
是简单状态管理的钩子,适合管理单个状态值,比如一个布尔开关、一个计数器等,调用 setState
更新状态时,它会自动合并新状态。而 useReducer
基于 reducer
函数,类似于 Redux 中的 reducer 概念,接收一个 reducer
函数和初始状态,通过派发不同的 action
来更新状态。useReducer
更适合管理复杂、有逻辑依赖关系的状态,例如购物车中商品的增减、库存计算等场景,它能更好地组织状态更新逻辑,使代码更具可维护性和可预测性。在状态更新依赖于之前状态,或者存在多个相关状态的联动更新时,使用 useReducer
会更合适。
2. 什么是 React 的 Fiber 架构?它对 React 的渲染机制带来了哪些改进?
Fiber 架构是 React 16 之后引入的一种新的渲染架构。在之前的同步渲染模式下,一旦 React 开始渲染,就会一直占用主线程,直到渲染完成,这可能导致页面卡顿。而 Fiber 架构将渲染任务拆分成一个个小的工作单元,每个单元都可以暂停、恢复和优先级调度。它带来的改进主要有三点:一是实现了可中断的渲染,React 可以在渲染过程中让出主线程,优先处理高优先级任务,比如用户输入事件,提升页面响应性;二是支持优先级调度,根据任务的紧急程度,比如动画、交互等任务设置高优先级,数据获取等任务设置低优先级,合理分配渲染时间;三是错误边界处理更灵活,当某个组件渲染出错时,不会导致整个应用崩溃,React 可以回退到上一个正确的状态,继续进行其他部分的渲染 。
3. 请解释 React 中的事件系统,为什么 React 的事件需要通过 合成事件
来处理,而不是原生事件?
React 的事件系统基于 合成事件
,它并不是原生 DOM 事件,而是 React 自己实现的一套事件机制。React 将所有原生事件统一绑定到最外层的 document
上,当事件触发时,React 会根据事件类型和组件的虚拟 DOM 结构,找到对应的 React 组件实例,再执行绑定在该组件上的事件处理函数。使用 合成事件
主要有几个原因:一是兼容性,合成事件
抹平了不同浏览器之间原生事件的差异,比如不同浏览器对事件对象的属性支持不一致,在 合成事件
中会进行统一处理;二是性能优化,减少了内存占用,不需要为每个 DOM 元素都绑定原生事件监听器;三是方便事件的统一管理和处理,React 可以在事件处理前后添加一些自定义的逻辑,比如事件的合成、冒泡处理,还能与 React 的批处理机制结合,将多个状态更新合并为一次,提高渲染性能 。
4. 在 React 项目中,如何避免组件的不必要重新渲染?请结合 React.memo
、useCallback
和 useMemo
进行说明。
React.memo
用于对函数组件进行性能优化,它会对组件的 props
进行浅比较,如果 props
没有发生变化,组件就不会重新渲染。但它只对 props
有效,对于函数组件内部状态变化导致的重新渲染无法避免。useCallback
用于缓存函数,当函数依赖的依赖项没有变化时,返回的函数引用保持不变。在父组件向子组件传递函数作为 props
时,如果不使用 useCallback
,每次父组件重新渲染,函数都会重新创建,导致子组件不必要的重新渲染,使用 useCallback
可以解决这个问题。useMemo
用于缓存计算结果,只有当依赖项发生变化时,才会重新计算返回值。比如在组件中进行复杂的计算,使用 useMemo
可以避免在每次组件重新渲染时都进行重复计算,从而避免不必要的重新渲染。实际使用中,当子组件只依赖 props
且 props
不常变化时,可以用 React.memo
;当传递函数作为 props
时,用 useCallback
缓存函数;当有计算逻辑且依赖项变化频率较低时,用 useMemo
缓存计算结果 。
5. React 中的 context
API 解决了什么问题?使用 context
可能会带来哪些潜在问题?
context
API 主要解决了跨层级组件之间的数据传递问题。在传统的 React 数据传递中,数据需要从顶层组件一层一层地通过 props
传递到深层子组件,这种方式在组件层级较深时会非常繁琐,代码冗余度高。而 context
可以让数据在组件树中直接传递,不需要逐层传递 props
,提高了开发效率,比如在多语言切换、主题切换等场景中,可以通过 context
方便地将语言、主题数据传递给所有需要的组件。但使用 context
也有潜在问题:一是数据流向不清晰,因为数据不再遵循传统的从上到下的传递方式,在大型项目中,可能会导致代码难以理解和维护;二是可能引发不必要的重新渲染,当 context
中的数据发生变化时,所有消费该 context
的组件都会重新渲染,即使有些组件实际上并不依赖这些变化的数据,从而影响性能 。
6. 请描述 React 中 setState
的异步更新机制。如果想要在 setState
之后立即获取更新后的值,应该怎么做?
在 React 中,setState
是异步的,这是因为 React 为了提高性能,会将多次 setState
的调用合并成一次更新,减少页面的重绘和回流次数。在 React 的合成事件和生命周期函数中,setState
表现为异步,比如在 onClick
事件处理函数或者 componentDidMount
中调用 setState
,React 会将这些状态更新放入一个队列中,等当前事件循环结束后,再统一进行批量更新。如果想要在 setState
之后立即获取更新后的值,可以使用 setState
的第二个参数,它是一个回调函数,会在状态更新并重新渲染完成后执行,在这个回调函数中就可以获取到最新的状态值。另外,在原生 DOM 事件或者 setTimeout 等异步函数中调用 setState
,由于不在 React 的控制范围内,setState
会表现为同步,因为此时 React 无法进行批量更新 。
7. 什么是 React 中的 高阶组件(Higher-Order Components,HOC)
?它和 自定义 hooks
有什么异同?
高阶组件(HOC)
是一个函数,它接收一个组件作为参数,返回一个新的增强组件。它主要用于复用组件逻辑,比如权限验证、数据获取等逻辑可以通过 HOC 抽离出来,应用到多个组件上。HOC 通过包裹原组件,在不修改原组件内部代码的情况下,为其添加新的功能或修改其行为。而 自定义 hooks
是一个函数,它以 use
开头,用于在函数组件中复用状态逻辑和副作用逻辑。它可以访问 React 的状态和生命周期等特性,并且可以在不同的函数组件之间共享逻辑。相同点在于它们都用于复用代码逻辑;不同点在于,HOC 是对组件进行包装,会产生额外的组件层级,可能导致调试困难和 props
透传问题,而自定义 hooks 只是逻辑的复用,不会增加组件层级;HOC 更侧重于组件的增强和修改,自定义 hooks 更专注于状态和副作用逻辑的复用;HOC 可以在类组件和函数组件中使用,自定义 hooks 只能在函数组件中使用 。
8. 在 React 项目中,如何进行性能优化?请从组件层面、代码层面和构建层面分别举例说明。
从组件层面来说,可以使用 React.memo
、shouldComponentUpdate
(类组件)来避免组件不必要的重新渲染,合理拆分组件,让每个组件职责单一,减少组件的复杂度。还可以使用虚拟列表,比如 react - virtualized
库,在处理大量数据列表时,只渲染可见区域的列表项,提高性能。从代码层面,使用 useCallback
和 useMemo
缓存函数和计算结果,避免重复计算和不必要的重新渲染;减少内联函数的使用,因为内联函数每次渲染都会重新创建;合理使用 key
值,在列表渲染时,key
值要保证唯一且稳定,帮助 React 高效地进行 diff 算法,确定哪些元素发生了变化。从构建层面,使用 Webpack 或 Vite 进行代码压缩、Tree - shaking 去除未使用的代码,开启代码分割,将代码拆分成多个 chunk,实现按需加载,减少首屏加载时间;配置 CDN 加速,将第三方库等资源从 CDN 加载,提高资源加载速度 。
9.React 中的 props
和 state
有什么区别?在父子组件和兄弟组件之间,数据传递分别有哪些常见方式?
props
是父组件传递给子组件的数据,它是单向流动的,子组件只能接收和使用 props
,不能直接修改它,props
主要用于向子组件传递配置信息、数据和回调函数等。而 state
是组件内部自身管理的状态,它可以在组件内部通过 setState
进行更新,状态的变化会触发组件的重新渲染。在父子组件之间传递数据,父组件可以通过 props
向子组件传递数据和函数,子组件可以通过回调函数将数据传递回父组件。对于兄弟组件之间的数据传递,一种常见方式是通过它们的共同父组件进行中转,一个兄弟组件调用父组件的函数,将数据传递给父组件,父组件再通过 props
将数据传递给另一个兄弟组件;还可以使用状态管理库,如 Redux、MobX 等,将数据存储在全局状态中,兄弟组件从全局状态中获取和更新数据 。
10. 在 React 中,如何实现一个自定义的 hook
?请举例说明一个实际场景下自定义 hook
的使用,例如处理异步请求的 hook
。
实现一个自定义 hook
,首先函数名必须以 use
开头,然后在函数内部可以使用其他 React hooks 来实现特定逻辑。以处理异步请求的 hook
为例:
import { useState, useEffect } from'react';
const useFetch = (url) => { const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => { const fetchData = async () => { try { const response = await fetch(url);
const result = await response.json();
setData(result); setLoading(false);
} catch (err) { setError(err); setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useFetch;
在实际组件中使用时:
import React from'react';
import useFetch from './useFetch';
const App = () => { const { data, loading, error } = useFetch('https://example.com/api/data');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>; return ( <div> {data.map((item) => ( <div key={item.id}>{item.name}</div> ))} </div> );
};
export default App;
在这个自定义 hook
中,使用 useState
管理数据、加载状态和错误状态,通过 useEffect
在组件挂载时发起异步请求,并且根据请求结果更新状态。这样,在不同的组件中需要进行异步数据获取时,都可以复用这个 hook
,避免重复编写异步请求的代码 。