react面试题整理
目录
- 📌 1. 什么是 React?它有哪些核心特性?
- 📌 2. 什么是 JSX?为何使用?
- 📌 3. React 中的组件有哪些类型?
- 📌 4. 什么是 Hook?你用过哪些?
- 📌 5. useEffect 的作用是什么?
- 📌 6. 如何实现列表渲染与 Key 的作用?
- 📌 7. 什么是受控组件与非受控组件?
- 📌 8. 事件绑定与事件对象
- 📌 9. 如何优化 React 性能?
- 📌 10. React 路由(react-router-dom)基本用法?
- 📌 11. 什么是 Redux?如何与 React 结合使用?
- 📌 12. 什么是 Context?用来解决什么问题?
- 📌 13. React 中如何实现组件通信?
- 📌 14. 什么是虚拟 DOM?它的优点?
- 📌 15. React 与 Vue 的对比
- 📌 16. Umi 路由配置与动态路由
- 📌 17. React 性能优化常见技巧
- 📌 18. 数据可视化大屏开发的性能优化
- 📌 19. React 中的错误边界是什么?如何使用?
- 📌 20. React 中的高阶组件(HOC)是什么?有什么用途?
- 📌 21. React 中的生命周期
📌 1. 什么是 React?它有哪些核心特性?
React 是一个用于构建用户界面的 JavaScript 库,主要用于构建单页应用(SPA)。由 Facebook 维护。它的核心思想是将 UI 拆分成多个小的、可复用的组件,通过组合这些组件来构建复杂的界面。
核心特性:
组件化开发(Component-Based):将页面拆分成多个独立的组件,每个组件负责一个特定的功能或 UI 部分,提高代码的可维护性和复用性。
虚拟 DOM(Virtual DOM):是 React 使用的轻量级 JavaScript 对象,用来描述 DOM 结构。React 通过对比新旧虚拟 DOM 的差异,只更新需要更新的真实 DOM 节点,减少直接操作 DOM 的次数,提高性能。
单向数据流(One-way Data Flow):数据的流动是单向的,使得数据的流向更加清晰,便于调试和维护。
声明式编程(Declarative UI):通过描述 UI 的最终状态,而不是如何去实现这个状态,让代码更加简洁和易于理解。
JSX 语法扩展:允许在 JavaScript 代码中写 HTML,使代码更直观,便于理解 UI 结构,同时支持变量、函数表达式。
const Hello = () => <h1>Hello, React!</h1>;
📌 2. 什么是 JSX?为何使用?
JSX 是 JavaScript 的语法扩展,允许你在 JavaScript 代码中写 HTML。它并不是一种新的语言,而是一种语法糖,最终会被 Babel 转译为 React.createElement ()。
好处:
- 代码更直观:可以直观地看到 UI 的结构,降低了代码的理解成本。
- 支持变量和表达式:可以在 JSX 中使用 JavaScript 变量和表达式,使代码更加灵活。
const name = '小明';
const greet = <h1>你好,{name}!</h1>;
📌 3. React 中的组件有哪些类型?
1、函数组件(Function Component) ✅ 推荐
- 函数组件是一种纯函数,它接收 props 作为参数,并返回一个 React 元素。函数组件没有自己的状态和生命周期方法,代码简洁,易于理解和测试。
- 示例:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
📌 4. 什么是 Hook?你用过哪些?
Hook 是 React 16.8 引入的特性,使函数组件具备状态管理和副作用处理的能力,解决了类组件的一些问题,如代码复用困难、状态逻辑复杂等。
- useState:声明状态,允许在函数组件中使用状态。
- import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>点击次数:{count}</p>
<button onClick={() => setCount(count + 1)}>点击我</button>
</div>
);
}
- useEffect:副作用处理(如请求数据、订阅事件等),类似于生命周期函数。
useEffect(() => {
console.log("组件挂载");
return () => console.log("组件卸载");
}, []);
- useRef:引用dom或者保存一些可变值,在组件的整个生命周期内保持不变
import { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>聚焦输入框</button>
</>
);
}
- useMemo / useCallback:性能优化,useMemo 用于缓存计算结果,useCallback 用于缓存函数。
import { useMemo, useCallback } from 'react';
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
📌 5. useEffect 的作用是什么?
useEffect 处理副作用逻辑,类似于生命周期函数。它可以在组件渲染后执行一些操作,如数据获取、订阅事件、手动修改 DOM 等。
- 没有依赖:每次渲染都触发,类似于 componentDidMount 和 componentDidUpdate。
useEffect(() => {
console.log("组件渲染");
});
useEffect(() => {
console.log("组件渲染");
return{
//componentWillUnmount
}
});
- 空数组:只在初次渲染时触发(componentDidMount)。
useEffect(() => {
console.log("组件挂载");
return () => console.log("组件卸载");
}, []);
- 有依赖:依赖变化时触发(componentDidUpdate)。
useEffect(() => {
console.log("依赖变化");
}, [dependency]);
总结对比表
| 依赖数组 | 执行时机 | 适用场景 | 风险提示 |
|---|---|---|---|
| 不传(undefined) | 每次渲染(包括首次和更新) | 无依赖的副作用(如强制刷新)类似于 componentDidMount 和 componentDidUpdate。 | 可能导致性能问题 |
| 空数组([]) | 仅挂载和卸载时执行一次 | 初始化操作、订阅、清理副作用类似(componentDidMount)。 | 需确保无隐式依赖 |
| 非空数组 | 依赖项变化时执行 | 同步数据变化的副作用(如网络请求) 类似于(componentDidUpdate) | 需完整声明所有依赖 |
📌 6. 如何实现列表渲染与 Key 的作用?
React 使用 map() 方法来渲染数组。key 是一个特殊的属性,它帮助 React 标识哪些元素更改、添加或删除,从而提高渲染性能。
const list = ['a', 'b', 'c'];
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
📌 7. 什么是受控组件与非受控组件?
- 受控组件:值受 state 控制,推荐使用。每次输入变化时,都会触发 onChange 事件,更新 state 中的值。
import { useState } from 'react';
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input value={value} onChange={e => setValue(e.target.value)} />
);
}
- 非受控组件 用 ref 获取 DOM 值,不需要在每次输入变化时更新 state
import { useRef } from 'react';
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log(inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} type="text" />
<button type="submit">提交</button>
</form>
);
}
📌 8. 事件绑定与事件对象
React 事件命名采用小驼峰命名法,例如 onClick。事件处理函数通过属性传递给组件,事件对象是合成事件,它在不同浏览器中具有一致的接口。
function handleClick(e) {
console.log('点击事件触发', e);
}
<button onClick={(e) => handleClick(e)}>点我</button>
📌 9. 如何优化 React 性能?
- 使用 React.memo:缓存组件,避免无意义重新渲染,适用于函数组件。
react,memo 是react 提供的一个高阶组件(Hoc),用于韩村函数组件的渲染结果,当组件的props未发生变化时,reactmemo会直接服用上一次的渲染结果,而非成型执行组件函数,从而避免无意义的重新渲染,提高性能
const MyComponent = React.memo((props) => {
return <div>{props.value}</div>;
});
适用场景
1、纯展示型组件
组件仅依赖传入的 props,无内部状态(useState)或副作用(useEffect),且无需频繁更新。
const Display = React.memo(({ text }) => {
console.log('Render:', text);
return <div>{text}</div>;
});
2、列表项组件
在长列表中,使用react。memo可避免音符组件重新渲染导致的列表项重复渲染
```javascript
const ListItem = React.memo(({ item }) => {
return <div>{item.name}</div>;
});
3、需要优化的复杂计算组件
组件包含复杂的逻辑或计算,但仅依赖稳定的props,可通过react。memo缓存结果
**import React, { useState } from 'react';
// 定义函数组件
const MyComponent = ({ name }) => {
console.log('Component rendered');
return <div>{name}</div>;
};
// 使用 React.memo 包装组件
const MemoizedComponent = React.memo(MyComponent);
// 或直接内联使用
export default function App() {
const [count, setCount] = useState(0);
return (
<>
<MemoizedComponent name="Alice" />
<button onClick={() => setCount(count + 1)}>Rerender Parent</button>
</>
);
}**
- useMemo, useCallback:缓存结果或函数,避免不必要的计算和函数创建。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
📌 10. React 路由(react-router-dom)基本用法?
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function Home() {
return <h1>主页</h1>;
}
function About() {
return <h1>关于页面</h1>;
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
📌 11. 什么是 Redux?如何与 React 结合使用?
Redux 是一个状态管理库,适合在中大型应用中使用。它采用单向数据流的设计思想,将应用的所有状态集中管理,使得状态的变化可预测和可追溯。
核心概念:
- Store:存储应用的所有状态。
- Action:描述状态的变化,是一个包含 type 属性的对象。
- Reducer:是一个纯函数,根据 action 来更新状态
结合 React 示例:
- // store.js
import { createStore } from 'redux';
const reducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'INCREMENT': return { count: state.count + 1 };
default: return state;
}
};
const store = createStore(reducer);
// App.js
import { Provider, useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>加一</button>
</div>
);
}
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
📌 12. 什么是 Context?用来解决什么问题?
Context 用于解决多层组件传值(props drilling)的问题,允许在组件树中共享数据,而不必显式地通过每个组件传递 props。类似于vue 中的provide/inject(跨层级组件传值)
示例:
**import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}**
📌 13. React 中如何实现组件通信?
- 父子通信:通过 props 传递数据,父组件将数据作为 props 传递给子组件。
function Parent() {
const message = 'Hello from parent';
return <Child message={message} />;
}
function Child(props) {
return <p>{props.message}</p>;
}
- 子传父:通过 props 传递回调函数,子组件调用回调函数将数据传递给父组件。
function Parent() {
const handleChildData = (data) => {
console.log('Received data from child:', data);
};
return <Child onData={handleChildData} />;
}
function Child(props) {
const sendData = () => {
props.onData('Data from child');
};
return <button onClick={sendData}>发送数据给父组件</button>;
}
- 兄弟组件:提升状态到共同的父组件,或者使用 Redux 或 Context 进行状态管理。
- 跨层通信:使用 Context 直接在不同层级的组件之间共享数据。
- 跨组件:使用事件总线或状态管理方案,如 Redux、MobX 等。
📌 14. 什么是虚拟 DOM?它的优点?
虚拟 DOM 是 React 使用的轻量级 JavaScript 对象,用来描述 DOM 结构。它是真实 DOM 的抽象表示,React 通过对比新旧虚拟 DOM 的差异,只更新需要更新的真实 DOM 节点。
优点:
- 减少直接操作 DOM 的次数:直接操作 DOM 是比较昂贵的操作,虚拟 DOM 通过批量更新和差异比较,减少了对真实 DOM 的操作次数,提高了性能。
- 提高性能:通过 diff 算法,只更新发生变化的部分,避免了不必要的渲染。
- 实现跨平台渲染:由于虚拟 DOM 是一个 JavaScript 对象,它可以在不同的平台上进行渲染,如 React Native 可以将虚拟 DOM 渲染为原生组件。
📌 15. React 与 Vue 的对比
| 特性 | React | Vue |
|---|---|---|
| 数据绑定 | 单向数据流 | 双向数据绑定(v-model) |
| 编程范式 | 函数式(Hooks)、JSX | 模板驱动、选项式(Vue 3 支持组合式) |
| 虚拟 DOM | 是 | 是 |
| 状态管理 | Redux、Context、Zustand 等 | Vuex、Pinia |
| 生态工具 | Next.js、Umi、Remix | Nuxt.js |
| 学习曲线 | 高(需要理解 JSX、Hooks 等) | 相对较低(上手快) |
📌 16. Umi 路由配置与动态路由
Umi 提供约定式与配置式路由:
✅ 约定式路由
项目中 pages/ 目录结构自动映射成路由。
pages/
├── index.tsx => /
├── user.tsx => /user
✅ 配置式路由
在 config/config.ts 中配置:
export default {
routes: [
{ path: '/', component: '@/pages/index' },
{ path: '/user', component: '@/pages/user' },
],
};
📌 17. React 性能优化常见技巧
| 技术 / 方法 | 说明 |
|---|---|
| React.memo | 缓存组件,避免无意义重新渲染 |
| useMemo/useCallback | 缓存函数或计算结果 |
| shouldComponentUpdate / PureComponent | 控制类组件更新 |
| 虚拟滚动 | react-window / react-virtualized |
| Profiler | React DevTools 中性能分析工具 |
| 图片优化 | lazyload、webp、CDN |
📌 18. 数据可视化大屏开发的性能优化
可视化项目(如 ECharts/AntV)性能瓶颈常在于渲染与数据量:
✅ 技术层面
- 使用 canvas / webgl 替代 svg 渲染(如 ECharts 默认采用 canvas),提高渲染性能。
- 降低数据粒度,只展示关键数据,减少数据处理量。
- 定时增量更新而非全量刷新图表,减少不必要的渲染。
- 图表初始化使用 echarts.init 缓存实例,避免重复初始化。
✅ 示例代码
import { useEffect } from 'react';
import * as echarts from 'echarts';
function ChartComponent() {
useEffect(() => {
const chart = echarts.init(document.getElementById('main'));
const option = {
// 图表配置
};
chart.setOption(option);
const resizeObserver = () => chart.resize();
window.addEventListener('resize', resizeObserver);
return () => {
window.removeEventListener('resize', resizeObserver);
chart.dispose();
};
}, []);
return <div id="main" style={{ width: '600px', height: '400px' }}></div>;
}
📌 19. React 中的错误边界是什么?如何使用?
错误边界是 React 16 引入的一个特性,用于捕获并处理子组件树中的 JavaScript 错误,防止错误导致整个应用崩溃。错误边界是一个类组件,它定义了 componentDidCatch 或 getDerivedStateFromError 生命周期方法。
示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, errorInfo) {
// 可以在这里记录错误日志
console.log(error, errorInfo);
this.setState({ hasError: true });
}
static getDerivedStateFromError(error) {
// 更新 state 以显示错误 UI
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>发生错误,请稍后重试。</h1>;
}
return this.props.children;
}
}
function BuggyComponent() {
throw new Error('模拟错误');
return <div>这是一个有错误的组件</div>;
}
function App() {
return (
<div>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
📌 20. React 中的高阶组件(HOC)是什么?有什么用途?
高阶组件(Higher-Order Component,简称 HOC)是一个函数,它接收一个组件作为参数,并返回一个新的组件。高阶组件是 React 中复用代码和逻辑的一种方式。
用途:
- 代码复用:可以将一些通用的逻辑封装在高阶组件中,然后应用到多个组件上。
- 状态管理:可以在高阶组件中管理状态,然后将状态和操作传递给子组件。
- 性能优化:可以在高阶组件中进行性能优化,如缓存组件、避免不必要的渲染等。
function withLogging(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log(`${WrappedComponent.name} 组件挂载`);
}
componentWillUnmount() {
console.log(`${WrappedComponent.name} 组件卸载`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
function MyComponent() {
return <div>这是一个组件</div>;
}
const LoggedComponent = withLogging(MyComponent);
function App() {
return <LoggedComponent />;
}
📌 21. React 中的生命周期
一、React 生命周期阶段划分
1. 挂载阶段(Mounting)
组件首次被创建并插入 DOM 的过程:
- constructor()
初始化 state 和绑定事件处理函数,避免在此进行副作用操作(如 API 请求)。
static getDerivedStateFromProps()
静态方法,用于根据 props 更新 state,触发于 render 之前。不常用,可能导致代码复杂。 - render()
返回 JSX 结构,纯函数,不修改 state。 - componentDidMount()
组件已被渲染到 DOM 后执行,适合初始化操作(如 API 请求、订阅事件)。
2. 更新阶段(Updating)
props 或 state 变化触发组件重新渲染:
- static getDerivedStateFromProps()
同上,更新阶段也会触发。
shouldComponentUpdate(nextProps, nextState)
控制组件是否需要重新渲染,默认返回true。可通过浅比较(React.memo)优化性能。 - render()
重新计算并返回新的 JSX。 - getSnapshotBeforeUpdate(prevProps, prevState)
在 DOM 更新前获取当前 DOM 状态(如滚动位置),返回值将作为componentDidUpdate的第三个参数。 - componentDidUpdate(prevProps, prevState, snapshot)
DOM 已更新后执行,可在此操作 DOM 或根据新旧 props/state 执行副作用。
3. 卸载阶段(Unmounting)
组件从 DOM 中移除时:
- componentWillUnmount()
清理工作(如取消订阅、清除定时器),避免内存泄漏。
二、生命周期函数详解与应用场景
1. 挂载阶段核心函数
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this); // 绑定this
}
componentDidMount() {
// 初始化操作:数据获取、订阅事件
fetchData().then(data => this.setState({ data }));
window.addEventListener('resize', this.handleResize);
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
2. 更新阶段性能优化
class ListItem extends React.Component {
// 浅比较props,避免不必要的渲染
shouldComponentUpdate(nextProps) {
return this.props.item.id !== nextProps.item.id;
}
render() {
return <div>{this.props.item.name}</div>;
}
}
3. 卸载阶段资源清理
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
componentDidMount() {
this.timerID = setInterval(() => {
this.setState(prevState => ({ seconds: prevState.seconds + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timerID); // 清除定时器,防止内存泄漏
}
render() {
return <div>Seconds: {this.state.seconds}</div>;
}
}
三、React 16.8+ Hooks 生命周期替代方案
1. useEffect () 替代生命周期函数
import React, { useState, useEffect } from'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于componentDidMount + componentDidUpdate
useEffect(() => {
document.title = `You clicked ${count} times`; // 副作用操作
// 相当于componentWillUnmount(返回清理函数)
return () => {
// 清理工作,如取消订阅
};
}, [count]); // 仅在count变化时执行
return <button onClick={() => setCount(count + 1)}>Click me</button>;
}
2. useLayoutEffect () 与 useEffect () 的区别
- useEffect():异步执行,DOM 更新后触发(适合大多数副作用)。
- useLayoutEffect():同步执行,DOM 更新前触发(适合需要测量 DOM 的场景)。
更多推荐
所有评论(0)