【手撕面试题】React(高频知识点二)
每天10道题,100天后,搞定所有前端面试的高频知识点,加油!!!在看文章的同时,希望不要直接看答案,先思考一下自己会不会,如果会,自己的答案是什么?想过之后再与答案比对,是不是会更好一点,当然如果你有比我更好的答案,欢迎评论区留言,一起探讨技术之美。
目录
面试官:React的严格模式如何使用?有什么用处?
面试官:请你简述一下useCallBack和useMeno的使用场景
面试官:请你简述一下useEffect的清除机制是什么?在什么时候执行?
面试官:在React为什么调用setState而不是直接改变state?
面试官:React的并发模式是如何执行的?
面试官:简述一下React中setState的第二个参数作用是什么?
面试官:请你简述一下useEffect与useLayoutEffect的区别?
面试官:简述React中key的作用?解决的是哪一类问题?
面试官:简述如何解决props层级过深问题?
面试官:React组件命名推荐的方式是哪个?
面试官:React的严格模式如何使用?有什么用处?
我:呃~,React的严格模式(Strict Mode)是一种用于帮助开发者发现潜在问题的工具,它不会渲染任何UI,而是通过额外的检查来帮助找出可能会导致问题的代码或不推荐的API使用方式,它是在开发模式下运行的,仅在开发环境中有效,对生产环境没有影响。
在React中启用严格模式非常简单,只需要在应用的根组件(通常是index.js 或 App.js)中将整个应用包裹在<React.StrictMode>组件内即可,启用严格模式后React会在开发过程中进行额外的检查,代码如下:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
React严格模式的作用和用处:
1)帮助检测不安全的生命周期方法
2)检测意外的副作用,会额外地调用组件的render方法两次
3)启用废弃的 API 检查
4)检测意外的副作用与内存泄漏,如果组件中存在可能导致内存泄漏的副作用会警告开发者
5)帮助提升性能并提供更严谨的代码质量检查
面试官:请你简述一下useCallBack和useMeno的使用场景
我:呃~,它们都是React中的性能优化钩子,帮助开发者在组件渲染时避免不必要的计算和重新创建的操作,目的都是为了优化性能,通过缓存计算结果或者函数避免不必要的重复计算或函数重新创建,以下是它们的详细使用场景:
useCallback:缓存函数,用于避免函数在每次渲染时都被重新创建。它返回的是一个回调函数,只有在依赖项改变时才会更新。
下面这个示例中如果没有useCallback,每次ParentComponent渲染时,handleClick函数都会被重新创建,这会导致ChildComponent不必要的重新渲染,而使用useCallback后,只有在count更新时handleClick才会被更新:
import React, { useState, useCallback } from 'react';
const ChildComponent = React.memo(({ onClick }) => {
console.log('Child component rendered');
return <button onClick={onClick}>Click me</button>;
});
const ParentComponent = () => {
const [count, setCount] = useState(0);
// 使用 useCallback 确保 onClick 函数只有在 count 更新时才会变化
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [count]); // 依赖 count
return (
<div>
<ChildComponent onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
};
export default ParentComponent;
useMemo:缓存值,用于避免在每次渲染时都进行昂贵的计算,它返回一个值,只有当依赖项发生变化时才会重新计算。
在下面这个示例中,expensiveValue只有在count更新时才会重新计算,如果没有useMemo每次父组件重新渲染时,expensiveValue都会重新计算可能会影响性能,尤其是在计算逻辑很复杂时:
import React, { useState, useMemo } from 'react';
const ExpensiveComponent = ({ value }) => {
console.log('ExpensiveComponent rendered');
return <div>Computed Value: {value}</div>;
};
const ParentComponent = () => {
const [count, setCount] = useState(0);
// 计算一个昂贵的值,只有当 count 更新时才会重新计算
const expensiveValue = useMemo(() => {
console.log('Calculating expensive value...');
return count * 2;
}, [count]); // 依赖 count
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<ExpensiveComponent value={expensiveValue} />
</div>
);
};
export default ParentComponent;
面试官:请你简述一下useEffect的清除机制是什么?在什么时候执行?
我:呃~,useEffect的清除机制是用来清理副作用(side effects)的,例如订阅、计时器、网络请求等,当组件重新渲染或卸载时useEffect可以执行清理操作,它的执行时机有以下两种情况:
1)组件卸载时:useEffect的清理函数在组件卸载时会被调用,用于清理副作用。
2)依赖项变化:useEffect的依赖项数组中的值发生变化,会先执行清理函数再执行新的副作用。
在下面的例子当中,useEffect只会在组件挂载时启动定时器,在组件卸载时返回的清理函数会清除定时器防止内存泄漏:
useEffect(() => {
// 创建副作用,如订阅或计时器
const timer = setTimeout(() => {
console.log('Time’s up!');
}, 1000);
// 清除副作用
return () => {
clearTimeout(timer); // 清除定时器
};
}, []); // 空依赖数组表示只在组件挂载和卸载时执行
面试官:在React为什么调用setState而不是直接改变state?
我:呃~,调用setState而不是直接改变state是为了确保组件的状态更新和视图渲染过程符合React的声明式编程模型,具体原因如下:
1)触发重新渲染:React会根据setState调用来决定是否需要重新渲染组件,直接修改state不会触发渲染过程,从而可能导致UI和应用状态不同步。
2)批量更新优化:React使用批量更新机制来优化性能,多个setState调用可能会被合并为一次更新,从而减少不必要的渲染次数,直接修改state则无法利用这一优化。
3)异步更新:setState是异步的,React会在适当的时机更新组件状态,这有助于性能优化并确保正确的更新顺序,直接修改 state 会立即修改状态可能导致不一致的UI和行为。
4)生命周期控制:setState会触发React的生命周期方法(如 componentDidUpdate),帮助开发者在状态更新后执行副作用或进一步操作,直接修改state无法触发这些生命周期方法。
因此,setState是React管理组件状态和确保UI更新的一种关键机制。
面试官:React的并发模式是如何执行的?
我:呃~,React的并发模式(Concurrent Mode)是一种新的渲染模式,旨在提高用户界面的响应性和流畅度,尤其是在处理大型应用和复杂更新时,它通过允许React在后台分配任务来优化渲染过程,使得应用在性能瓶颈处能够更加灵活地进行任务调度从而提升用户体验:
主要特点:
1)任务优先级:通过任务的优先级管理来决定哪些渲染任务应该优先执行。
2)可中断渲染:渲染可以是可中断的,这种机制可以避免界面卡顿,提升用户的交互体验。
3)Suspense和Lazy Loading:允许React延迟渲染某些内容,直到它们准备好。
4)自动批处理:允许React自动批处理多个状态更新,减少渲染次数并提升性能。
举例:假设我们有一个大列表它会在用户输入时进行过滤,假设过滤操作非常耗时,如果在输入时直接过滤并渲染整个列表会导致UI卡顿或延迟响应,在并发模式下我们可以将过滤操作作为低优先级任务,在用户输入时通过startTransition来推迟它的执行。
startTransition将更新query标记为低优先级任务,这样即使用户快速输入React也能保证高优先级的任务(如输入响应)优先执行,避免渲染时的卡顿:
import React, { useState, useTransition } from 'react';
function SearchList({ items }) {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const filteredItems = items.filter(item => item.includes(query));
const handleChange = (e) => {
const value = e.target.value;
startTransition(() => {
setQuery(value); // 在低优先级任务中更新查询
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? <div>Loading...</div> : null}
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
面试官:简述一下React中setState的第二个参数作用是什么?
我:呃~,setState的第二个参数是一个回调函数,它在状态更新完成后执行,具体来说它在React完成了状态更新并且组件重新渲染之后被调用,其作用如下:
1)访问最新的状态:当希望在状态更新后做一些操作,比如发起网络请求、操作DOM或者更新外部系统等。
2)执行副作用:如果某些副作用操作依赖于组件状态的变化,第二个参数提供了一个钩子,确保状态更新完成后再执行这些操作。
举例:点击按钮时setState更新了count状态,并且第二个参数提供的回调函数会在状态更新完成并且组件重新渲染后执行,输出最新的count值:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
// 更新状态并在状态更新后执行某些操作
this.setState({ count: this.state.count + 1 }, () => {
console.log('状态更新完成,最新的 count 值:', this.state.count);
});
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
);
}
}
面试官:请你简述一下useEffect与useLayoutEffect的区别?
我:呃~,两者都是React Hook,用于在组件的生命周期中执行副作用(side effects)这两者之间的主要区别在于执行时机:
1)useEffect:在浏览器绘制之后执行,当组件渲染(包括虚拟 DOM 更新)完成后React会将副作用任务放到一个队列中等待浏览器绘制(即更新屏幕内容)之后才执行useEffect。
优势在于:避免阻塞页面渲染,因此适用于一些对性能要求不高的副作用,如数据请求或其他非同步操作。
2)useLayoutEffect:在浏览器绘制之前执行,与useEffect相似但它会在React完成DOM更新和布局计算后浏览器开始渲染屏幕之前同步执行,这意味着它会在浏览器绘制之前立即运行,这对于一些需要直接操作DOM或影响布局的副作用非常有用。
优势在于:适用于需要立即修改DOM或做布局调整的操作,因为它会阻止浏览器进行绘制直到副作用完成。
两者的区别如下所示:
特性 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 浏览器绘制完成后执行,即在屏幕渲染后。 | DOM 更新后,浏览器绘制前执行,即在布局和绘制之前。 |
常见用途 | 异步操作、数据获取、事件监听等副作用。 | 读取 DOM 属性、布局计算、同步影响布局的操作。 |
性能影响 | 不会阻塞屏幕渲染,适用于不影响页面渲染的副作用。 | 可能会导致页面渲染延迟,因为它会阻塞浏览器绘制,适用于需要立即修改布局的操作。 |
使用时机:
1)useEffect:大多数副作用都不需要立即影响布局或DOM,尤其是涉及到异步操作、API 请求、事件监听等操作时,应该使用useEffect,它不会阻塞页面渲染,性能上更友好。
2)useLayoutEffect:当需要对DOM或布局进行修改并且这些修改必须在浏览器渲染之前完成时,应该使用useLayoutEffect,例如获取元素尺寸、执行布局调整,或者需要直接控制DOM时。
面试官:简述React中key的作用?解决的是哪一类问题?
我:呃~,key是一个特殊的属性,主要用于帮助React在更新组件时区分哪些元素发生了变化,key解决的问题是列表渲染时的元素重排和性能优化:
key的作用:用来标识组件列表中的每一项确保每个列表项都有一个独特的标识符,React使用key来确定哪些项是新增的、删除的或更改的,从而高效地更新DOM;在渲染列表时会根 key来追踪每个元素的身份,如果没有key或key值不唯一可能会误判哪些元素需要更新,导致不必要的重新渲染,甚至错误的UI状态。
解决问题:主要解决了列表渲染中的重排问题,尤其是当列表项顺序发生变化、删除或新增时准确地识别元素,避免不必要的 DOM 更新。
const items = ['Apple', 'Banana', 'Cherry'];
function FruitList() {
return (
<ul>
{items.map((item, index) => (
<li key={item}>{item}</li>
))}
</ul>
);
}
面试官:简述如何解决props层级过深问题?
我:呃~,props层级过深是指组件层级嵌套过多导致需要一层一层地将数据通过props传递给子组件的问题,这种情况会让代码变得冗长难以维护,特别是当 props 深入多层时可能会使得父子组件之间的耦合度过高。
解决props层级过深问题的关键是减少不必要的数据传递,可以通过下面几种方法解决:
1)Context API:简化跨层级传递数据。
2)状态管理库:如 Redux、Recoil 等管理全局状态。
3)组件拆分与重构:提高组件的独立性和复用性。
4)Render Props 和 HOC:分离逻辑,减少层级传递。
5)React Hooks:使用 useContext 和 useReducer 等钩子优化状态管理。
通过这些方式可以有效减少React中因props层级过深而带来的问题,提高应用的可维护性和性能。
面试官:React组件命名推荐的方式是哪个?
我:呃~,组件的命名通常有一些推荐的最佳实践,遵循这些命名规则可以帮助提升代码的可读性、可维护性和一致性,以下是一些 React 组件命名的常见约定和推荐方式:
1)使用 PascalCase 进行组件命名(每个单词首字母大写)。
2)组件文件名通常与组件名相同,使用小写字母和短横线(kebab-case)分隔。
3)根据组件的功能或角色选择合适的前缀或后缀(如 Container、ButtonPrimary 等)。
4)在组合组件时使用有描述性的命名(如 HeaderWithSearch)。
5)保持命名一致性和清晰性,避免使用过于通用的名称(如 App)。
遵循这些命名约定可以确保代码结构清晰、一致,有助于团队协作和代码维护。