React 面试题
1. React中keys的作用是什么?
Keys是React用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,需要保证某个元素的key在其同级元素中具有唯一性。在React Diff算法中,React会借助元素的Key值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React还需要借助key值来判断元素与本地状态的关联关系。
2. 当你调用setState
的时候,发生了什么事?
将传递给setState
的对象合并到组件的当前状态,这将启动一个和解的过程,构建一个新的react元素树,与上一个元素树进行对比(diff),从而进行最小化的重渲染。
3. 为什么setState
的参数是一个callback而不是一个对象?
因为this.props
和this.state
的更新可能是异步的,不能依赖它们的值去计算下一个state。
4. 状态(state)和属性(props)之间有何区别?
State是一种数据结构,用于组件挂载时所需数据的默认值,可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。Props(properties的简写)则是组件的配置,由父组件传递给子组件,并且就子组件而言,props是不可变的(immutable)。组件不能改变自身的props,但是可以把其子组件的props放在一起(统一管理)。Props也不仅仅是数据,回调函数也可以通过props传递。
5. 应该在React组件的何处发起Ajax请求?
在React组件中,应该在componentDidMount
中发起网络请求。这个方法会在组件第一次“挂载”(被添加到DOM)时执行,在组件的生命周期中仅会执行一次。更重要的是,不能保证在组件挂载之前Ajax请求已经完成,如果在未挂载的组件上调用setState
将不起作用。在componentDidMount
中发起网络请求可以保证有一个组件可以更新。
6. React中的三种构建组件的方式?
React.createClass()、ES6 class和无状态函数。
7. React中refs的作用是什么?
Refs是React提供给我们的安全访问DOM元素或者某个组件实例的句柄。我们可以为元素添加ref属性然后在回调函数中接受该元素在DOM树中的句柄,该值会作为回调函数的第一个参数返回。另外,refs并不是类组件的专属,函数式组件同样能够利用闭包暂存其值。
8. 请描述下react diff原理(常考,大厂必考)?
把树形结构按照层级分解,只比较同级元素;给列表结构的每个单元添加唯一的key属性,方便比较;React只会匹配相同class的component(这里面的class指的是组件的名字);合并操作,调用component的setState
方法的时候,React将其标记为dirty,到每一个事件循环结束,React检查所有标记dirty的component重新绘制选择性子树渲染。开发人员可以重写shouldComponentUpdate
提高diff的性能。
9. 说说React优势?
- React速度很快:引入虚拟DOM,性能好。
- 跨浏览器兼容:虚拟DOM提供标准化API,在IE8中也没问题。
- 一切都是component:代码模块化,重用性高,可维护性好。
- 单向数据流:Flux架构实现单向数据层。
- 同构、纯粹的javascript:预渲染有助于搜索引擎优化。
- 兼容性好:可使用Requires、Browerify和webpack等工具。
10. 说说react生命周期函数?
- 初始化阶段:
getDefaultProps
获取实例的默认属性;getInitialState
获取每个实例的初始化状态;componentWillMount
组件即将被装载、渲染到页面上;render
组件生成虚拟的DOM节点;componentDidMount
组件真正被装载之后。
- 运行中状态:
componentWillReceiveProps
组件将要接收到属性时调用;shouldComponentUpdate
组件接收到新属性或新状态时调用(可返回false阻止更新);componentWillUpdate
组件即将更新,不能修改属性和状态;render
组件重新描绘;componentDidUpdate
组件已经更新 。
- 销毁阶段:
componentWillUnmount
组件即将销毁。
11. React组件中怎么做事件代理?
虽然很多资料都说React的事件是会被代理到document上,但官网未明确说明。可通过Chrome浏览器的Event Listeners面板查看元素的绑定事件来证明。例如有#child和#parent两个元素,分别绑定点击事件,通过该面板可看到:#child元素绑定了两个点击事件,一个是通过React绑定的,一个是通过addEventListener绑定的;通过addEventListener绑定的事件真的绑定到#child元素上,而通过React绑定的事件,其实是代理绑定到document上。
React模拟DOM事件冒泡机制,但实际原理并非真正的DOM事件冒泡。#child和#parent绑定的事件都代理到document上,当事件流冒泡到document上时,会依次触发document上绑定的两个事件。React在document上绑定了一个dispatchEvent函数,在执行dispatchEvent的过程中,其内部会依次执行#child和#parent上绑定的事件。
React禁止事件冒泡时,通过React绑定的事件,其回调函数中的event对象是经过React合成的SyntheticEvent,与原生的DOM事件的event不是一回事。在React中,e.nativeEvent才是原生DOM事件的那个event,虽然React的合成事件对象也同样实现了stopPropagation接口,但React合成事件对象的e.stopPropagation只能阻止React模拟的事件冒泡,不能阻止真实的DOM事件冒泡,也不能阻止已经触发元素的多个事件的依次执行。只有原生事件对象的stopImmediatePropagation能做到阻止DOM事件的进一步捕获或者冒泡,且该元素的后续绑定的相同事件类型的事件也被一并阻止。
12. this
的各种情况
- call、apply、bind指的
this
是谁就是谁(bind不会调用,只会将当前的函数返回 )。例如fun.call(obj, a, b)
、fun.apply(obj, [1])
、fun.bind(obj, a, b)()
。
- 以函数形式调用时,
this
永远都是window。
- 以方法的形式调用时,
this
是调用方法的对象。
- 以构造函数的形式调用时,
this
是新创建的那个对象。
- 使用call和apply调用时,
this
是指定的那个对象。
- 箭头函数:箭头函数的
this
看外层是否有函数。如果有,外层函数的this
就是内部箭头函数的this
;如果没有,就是window。
- 特殊情况:通常意义上
this
指针指向为最后调用它的对象。这里需要注意的一点就是如果返回值是一个对象,那么this
指向的就是那个返回的对象,如果返回值不是一个对象那么this
还是指向函数的实例。
13. 介绍Promise,异常捕获,如何进行异常处理?
Promise是解决回调地狱的好工具,比起直接使用回调函数,Promise的语法结构更加清晰,代码的可读性大大增加。使用Promise经常遇到老旧浏览器没有Promise全局对象的问题,可以使用es6-promise-polyfill解决。该polyfill可以使用页面标签直接引入,也可以通过es6的import方法引入,引入后会在window对象中加入Promise对象,从而可以全局使用Promise。
在Promise中进行异常处理,可以在reject回调和catch中处理异常。但Promise规定如果一个错误在reject函数中被处理,那么Promise将从异常常态中恢复过来,这意味着接下来的then方法将接收到一个resolve回调。大多数时候希望发生错误时,Promise处理当前的异常并中断后续的then操作。
14. React怎么做数据的检查和变化
- props:组件属性,专门用来连接父子组件间通信,父组件传输父类成员,子组件可以利用但不能编辑父类成员。在React中,父组件给子组件传递数据时,通过给子组件设置props的方式,子组件取得props中的值,即可完成数据传递,被传递数据的格式可以是任何js可识别的数据结构。props一般只作为父组件给子组件传递数据用,不要试图去修改自己的prop。
- state:专门负责保存和改变组件内部的状态。如果组件内部的属性发生变化,使用state。例如通过
this.setState({})
来更新state,React会实时监听每个组件的props和state的值,一旦有变化,会立刻更新组件,将结果重新渲染到页面上。
15. 介绍常见的react优化方式
React渲染性能优化有三个方向:
- 减少计算的量:对应到React中就是减少渲染的节点或者降低组件渲染的复杂度。不要在渲染函数中进行不必要的计算,如数组排序、数据转换、订阅事件、创建事件处理器等,渲染函数中不应放置太多副作用;减少不必要的嵌套,滥用高阶组件/RenderProps可能导致不必要的节点嵌套,可优先使用props、React Hooks代替;使用虚拟列表优化“长列表”和“复杂组件树”场景,减少渲染节点;采用惰性渲染,只在必要时渲染对应的节点,如Tab组件的panel可在激活时再渲染;选择合适的样式方案,在样式运行时性能方面,CSS > 大部分CSS-in-js > inline style。
- 利用缓存:对应到React中就是如何避免重新渲染,利用函数式编程的memo方式来避免组件重新渲染。简化props,复杂的props可能违背“单一职责”,且难以维护,影响shallowCompare效率,可将复杂组件拆解;使用不变的事件处理器,避免使用箭头函数形式的事件处理器,因为每次渲染时箭头函数都会创建新的事件处理器,导致组件重新渲染,可使用实例方法或useCallback包装事件处理器;使用不可变数据,使状态变得可预测,让
shouldComponentUpdate
的“浅比较”更可靠和高效,相关工具有immutable.js、immer、immutability - helper以及seamless - immutable;简化state,只将需要组件响应变动或渲染到视图中的数据放到state中,避免不必要的数据变动导致组件重新渲染;使用recompose精细化比对,控制shouldComponentUpdate
方法,如pure()、shouldUpdate()、onlyUpdateForKeys()等方法,还可扩展如omitUpdateForKeys忽略比对某些key。
- 精确重新计算的范围:对应到React中就是绑定组件和状态关系,精确判断更新的“时机”和范围,只重新渲染“脏”的组件,或者说降低渲染范围。避免滥用Context,Context的Value变动会导致所有依赖该Context的组件全部forceUpdate,应明确状态作用域,只放置必要的、关键的、被大多数组件所共享的状态,如鉴权状态,并且粗粒度地订阅Context 。
16. React Router 中如何处理 404 错误?
在 React Router 中,可以通过设置一个没有 path 属性的 Route 组件来处理 404 错误。当所有其他路由都不匹配时,这个没有 path 属性的 Route 组件就会被渲染,通常会渲染一个自定义的 404 页面。例如:
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route component={NotFound} />
</Switch>
这里的 NotFound 组件就是自定义的 404 页面组件。
17. 解释 React 中的高阶组件(HOC)及其用途
高阶组件(Higher - Order Component,HOC)是一个函数,它接收一个组件作为参数,并返回一个新的组件。高阶组件的主要用途包括:
- 代码复用:可以将一些通用的逻辑封装在高阶组件中,然后应用到多个组件上,避免代码重复。
- 状态管理:可以在高阶组件中管理状态,并将状态和操作传递给被包装的组件。
- 代码分割:可以根据需要动态加载组件,实现代码分割。
- 增强组件功能:例如添加日志记录、错误处理等功能。
18. React 中的 Context 是什么,如何使用它进行跨组件通信?
Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的每一层传递 props。使用步骤如下:
- 创建 Context:使用
React.createContext
创建一个 Context 对象。
const MyContext = React.createContext(defaultValue);
- 提供 Context:使用 Context.Provider 组件来提供数据,Provider 组件接收一个 value 属性,用于传递数据。
<MyContext.Provider value={/* some value */}>
{/* 子组件 */}
</MyContext.Provider>
- 消费 Context:子组件可以通过
MyContext.Consumer
组件或者 useContext
Hook 来获取 Context 中的数据。
// 使用 Consumer
<MyContext.Consumer>
{value => /* 使用 value */}
</MyContext.Consumer>
// 使用 useContext Hook
const value = useContext(MyContext);
19. 如何在 React 中进行代码分割和懒加载?
- 使用 React.lazy 和 Suspense:
React.lazy
函数允许你动态导入组件,Suspense
组件用于在组件加载完成之前显示一个加载指示器。
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
- 路由级代码分割:在 React Router 中,可以使用
React.lazy
和 Suspense
进行路由级的代码分割。
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));
<Switch>
<Suspense fallback={<div>Loading...</div>}>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Suspense>
</Switch>
20. React 中的受控组件和非受控组件有什么区别?
- 受控组件:表单元素的值由 React 组件的 state 控制,每次表单元素的值发生变化时,都会触发一个事件处理函数来更新 state。例如:
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
}
- 非受控组件:表单元素的值由 DOM 本身控制,通过
ref
来获取表单元素的值。例如:
class UncontrolledInput extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (event) => {
event.preventDefault();
console.log(this.inputRef.current.value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.inputRef} />
<button type="submit">Submit</button>
</form>
);
}
}
21. 如何在 React 中实现组件的动画效果?
- 使用 CSS 动画:可以通过添加和移除 CSS 类名来触发 CSS 动画。例如,使用
react-transition-group
库来管理组件的进入和离开动画。
import { CSSTransition } from 'react-transition-group';
const Fade = ({ in: inProp }) => (
<CSSTransition
in={inProp}
timeout={300}
classNames="fade"
unmountOnExit
>
<div>
{/* 组件内容 */}
</div>
</CSSTransition>
);
// CSS 样式
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 300ms;
}
- 使用 React Spring 等动画库:
React Spring
是一个用于创建物理动画的库,它可以实现更复杂的动画效果。
22. 解释 React 中的 Fiber 架构及其优势
Fiber 是 React 16.x 版本引入的新协调算法。它的主要优势包括:
- 可中断渲染:Fiber 架构将渲染过程拆分成多个小任务,可以在浏览器空闲时执行这些任务,避免长时间阻塞主线程,从而提高应用的响应性能。
- 优先级调度:可以为不同的更新任务分配不同的优先级,优先处理高优先级的任务,例如用户交互产生的更新。
- 更好的错误处理:Fiber 可以更好地处理渲染过程中的错误,避免整个应用崩溃。
23. 如何在 React 中使用 Redux 进行状态管理?
- 安装 Redux 和 React - Redux:
npm install redux react-redux
- 创建 Reducer:Reducer 是一个纯函数,它接收当前的 state 和一个 action,返回一个新的 state。
const initialState = {
count: 0
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
};
case 'DECREMENT':
return {
...state,
count: state.count - 1
};
default:
return state;
}
};
- 创建 Store:使用
createStore
函数创建一个 Redux store。
import { createStore } from 'redux';
const store = createStore(counterReducer);
- 连接 React 组件和 Redux store:使用
react-redux
库的 Provider
组件将 store 提供给整个应用,使用 connect
函数或者 useSelector
和 useDispatch
Hooks 来连接组件和 store。
import { Provider } from 'react-redux';
import { connect } from 'react-redux';
const App = () => {
return (
<Provider store={store}>
{/* 应用组件 */}
</Provider>
);
};
const Counter = (props) => {
return (
<div>
<p>Count: {props.count}</p>
<button onClick={props.increment}>Increment</button>
<button onClick={props.decrement}>Decrement</button>
</div>
);
};
const mapStateToProps = (state) => {
return {
count: state.count
};
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
24. React 中的 PropTypes 是什么,如何使用它?
PropTypes
是 React 提供的一个用于类型检查的工具,它可以帮助我们在开发过程中发现组件 props 类型不匹配的问题。使用步骤如下:
- 导入 PropTypes:
import PropTypes from 'prop-types';
- 定义组件的 propTypes:在组件定义之后,使用
propTypes
属性来定义组件的 propTypes。
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = (props) => {
return <div>{props.name}</div>;
};
MyComponent.propTypes = {
name: PropTypes.string.isRequired
};
export default MyComponent;
这里定义了 MyComponent
组件的 name
prop 必须是一个字符串,并且是必需的。
25. 如何在 React 中处理表单提交?
可以通过以下步骤在 React 中处理表单提交:
- 创建表单组件:使用
form
元素创建表单,并为表单元素添加 onChange
和 onSubmit
事件处理函数。
class FormComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
email: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const { name, value } = event.target;
this.setState({ [name]: value });
}
handleSubmit(event) {
event.preventDefault();
console.log('Form submitted:', this.state);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}
placeholder="Name"
/>
<input
type="email"
name="email"
value={this.state.email}
onChange={this.handleChange}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}
}
这里通过 handleChange
函数更新表单元素的值,通过 handleSubmit
函数处理表单提交事件。
26. 解释 React 中的合成事件(SyntheticEvent)
合成事件是 React 对原生 DOM 事件的封装,它提供了跨浏览器的一致性接口。合成事件的主要特点包括:
- 跨浏览器兼容性:合成事件在不同浏览器中表现一致,避免了处理不同浏览器事件的差异。
- 事件委托:React 采用事件委托的方式处理合成事件,将所有事件绑定到 document 上,提高了性能。
- 事件对象:合成事件对象的接口与原生 DOM 事件对象类似,但它是跨浏览器的。例如,
e.preventDefault()
用于阻止默认行为,e.stopPropagation()
用于阻止事件冒泡。
27. 如何在 React 中使用第三方库(如 Lodash)?
- 安装第三方库:使用
npm
或 yarn
安装第三方库,例如 npm install lodash
。
- 导入和使用:在 React 组件中导入第三方库并使用。
import React from 'react';
import _ from 'lodash';
const MyComponent = () => {
const numbers = [1, 2, 3, 4, 5];
const sum = _.sum(numbers);
return <div>Sum: {sum}</div>;
};
export default MyComponent;
这里导入了 Lodash 库,并使用 _.sum
函数计算数组的总和。
28. React 中的 useState
和 useReducer
Hook 有什么区别?
useState
:用于管理简单的状态,它接收一个初始值,并返回一个包含当前状态和更新状态函数的数组。例如:
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
useReducer
:用于管理复杂的状态逻辑,它接收一个 reducer 函数和一个初始状态,并返回一个包含当前状态和分发 action 函数的数组。例如:
import React, { useReducer } from 'react';
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
</div>
);
};
useReducer
更适合处理多个状态相互依赖、状态更新逻辑复杂的情况。
29. 如何在 React 中使用 useEffect
Hook 进行副作用操作?
useEffect
Hook 用于在组件渲染后执行副作用操作,例如数据获取、订阅事件、操作 DOM 等。useEffect
接收一个回调函数和一个依赖数组。
import React, { useEffect } from 'react';
const MyComponent = () => {
useEffect(() => {
console.log('Component rendered');
});
return <div>My Component</div>;
};
import React, { useEffect } from 'react';
const MyComponent = () => {
useEffect(() => {
console.log('Component mounted');
return () => {
console.log('Component unmounted');
};
}, []);
return <div>My Component</div>;
};
- 有依赖数组:回调函数会在组件挂载和依赖项发生变化时执行。
import React, { useState, useEffect } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count changed: ${count}`);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
30. React 中的 useContext
Hook 如何使用?
useContext
Hook 用于在函数组件中消费 Context。使用步骤如下:
- 创建 Context:使用
React.createContext
创建一个 Context 对象。
const MyContext = React.createContext();
- 提供 Context:使用 Context.Provider 组件来提供数据。
<MyContext.Provider value={/* some value */}>
{/* 子组件 */}
</MyContext.Provider>
- 消费 Context:在函数组件中使用
useContext
Hook 来获取 Context 中的数据。
import React, { useContext } from 'react';
const MyComponent = () => {
const value = useContext(MyContext);
return <div>{value}</div>;
};
31. 如何在 React 中使用 useMemo
和 useCallback
Hook 进行性能优化?
useMemo
:用于缓存计算结果,避免在每次渲染时都进行复杂的计算。它接收一个回调函数和一个依赖数组,只有当依赖项发生变化时,才会重新计算回调函数的结果。
import React, { useMemo, useState } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
const expensiveValue = useMemo(() => {
// 复杂的计算
return count * 2;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
useCallback
:用于缓存函数,避免在每次渲染时都创建新的函数实例。它接收一个回调函数和一个依赖数组,只有当依赖项发生变化时,才会返回一个新的函数实例。
import React, { useCallback, useState } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
使用 useMemo
和 useCallback
可以避免不必要的渲染和计算,提高组件的性能。
32. 解释 React 中的 Portal 及其用途
Portal 是 React 提供的一种将子节点渲染到父组件 DOM 层次结构之外的方式。它的主要用途包括:
- 模态框和弹出框:可以将模态框和弹出框渲染到
body
元素下,避免受到父组件样式的影响。
- 提示框和下拉菜单:可以将提示框和下拉菜单渲染到合适的位置,避免被父组件的
overflow
样式裁剪。
使用 ReactDOM.createPortal
方法创建 Portal,示例如下:
import React from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ children }) => {
return ReactDOM.createPortal(
<div className="modal">
{children}
</div>,
document.body
);
};
export default Modal;
33. 如何在 React 中处理错误边界(Error Boundaries)?
错误边界是一种 React 组件,它可以捕获并处理其子组件树中发生的 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 以显示错误信息
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}
// 使用错误边界
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
34. React 中的 forwardRef
有什么作用?
forwardRef
用于在组件之间传递 ref
。通常情况下,组件不能直接将 ref
传递给子组件,但使用 forwardRef
可以实现这一点。例如:
import React, { forwardRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
const ParentComponent = () => {
const inputRef = React.createRef();
return (
<div>
<MyInput ref={inputRef} placeholder="Enter text" />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
};
这里通过 forwardRef
将 ref
从 ParentComponent
传递到了 MyInput
组件。
35. 如何在 React 中使用自定义 Hook?
自定义 Hook 是一个以 use
开头的 JavaScript 函数,它可以使用其他 Hook。自定义 Hook 可以用于复用状态逻辑。例如,创建一个自定义 Hook 来处理表单输入:
import React, { useState } from 'react';
const useInput = (initialValue) => {
const [value, setValue] = useState(initialValue);
const handleChange = (event) => {
setValue(event.target.value);
};
return {
value,
onChange: handleChange
};
};
const MyForm = () => {
const nameInput = useInput('');
const emailInput = useInput('');
return (
<form>
<input {...nameInput} placeholder="Name" />
<input {...emailInput} placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
};
这里的 useInput
就是一个自定义 Hook,它封装了处理表单输入的逻辑。
36. React 中的 StrictMode
有什么作用?
StrictMode
是一个用于帮助开发者发现潜在问题的工具组件。它可以在开发模式下启用额外的检查和警告,帮助开发者编写更健壮的代码。StrictMode
可以检查以下问题:
- 识别不安全的生命周期方法:例如在
componentWillReceiveProps
、componentWillMount
等不安全的生命周期方法中使用。
- 检测意外的副作用:例如在渲染过程中执行副作用操作。
- 检测过时的 context API:提醒开发者使用新的 context API。
使用
StrictMode
很简单,只需要将需要检查的组件包裹在 <React.StrictMode>
标签中即可。
import React from 'react';
const App = () => {
return (
<React.StrictMode>
{/* 应用组件 */}
</React.StrictMode>
);
};
37. 如何在 React 中使用 React Router 进行路由导航?
- 安装 React Router:
npm install react-router-dom
- 创建路由配置:使用
BrowserRouter
、Route
和 Switch
组件来配置路由。
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './Home';
import About from './About';
const App = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Router>
);
};
export default App;
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about
以下是剩余的 React 相关面试题:
38. 简述 React 中的混合模式(Concurrent Mode)
混合模式(Concurrent Mode)是 React 18 引入的一种新渲染模式,它让 React 可以在渲染过程中暂停、继续和中断渲染,从而实现更流畅的用户体验。其核心优势在于能够处理高优先级的更新,比如用户输入,而不会被低优先级的渲染任务阻塞。例如,在一个复杂页面渲染时,用户点击按钮触发的交互可以立即响应,而不是等待页面全部渲染完成。它通过时间切片(Time Slicing)和优先级调度(Priority Scheduling)等机制来实现这些功能。
39. React 中如何进行服务端渲染(SSR)
React 进行服务端渲染(SSR)通常借助 Next.js 等框架,或者手动实现。手动实现步骤如下:
- 创建 React 应用:构建正常的 React 组件和应用逻辑。
- 搭建 Node.js 服务器:使用 Express 等框架搭建服务器。
- 在服务器端渲染 React 组件:使用
ReactDOMServer.renderToString
或 ReactDOMServer.renderToStaticMarkup
方法将 React 组件渲染为 HTML 字符串。
- 将渲染后的 HTML 发送给客户端:服务器将渲染好的 HTML 字符串发送给客户端浏览器。
- 在客户端进行水合(Hydration):客户端使用
ReactDOM.hydrateRoot
方法将服务器渲染的静态 HTML 转换为可交互的 React 应用。
40. 如何在 React 中进行性能分析和优化
- 性能分析:
- 使用 React DevTools:可以查看组件的渲染时间、Props 和 State 的变化等信息。
- 使用浏览器的开发者工具:如 Chrome 的 Performance 面板,分析页面的加载和渲染性能。
- 使用
React.memo
和 useMemo
、useCallback
:避免不必要的渲染和计算。
- 优化策略:
- 减少渲染节点:避免创建过多的 DOM 元素。
- 优化状态管理:合理使用
state
和 props
,避免不必要的状态更新。
- 使用虚拟列表:处理大量数据列表时,只渲染可见区域的元素。
- 代码分割:使用
React.lazy
和 Suspense
进行代码分割,减少初始加载时间。
41. 解释 React 中的 Fragment
Fragment 是 React 中的一个轻量级容器,用于在不添加额外 DOM 节点的情况下对多个元素进行分组。在 JSX 中,不能直接返回多个元素,需要将它们包裹在一个父元素中。使用 Fragment 可以避免添加不必要的 DOM 节点,从而减少 DOM 层级。例如:
import React from 'react';
const MyComponent = () => {
return (
<React.Fragment>
<p>First paragraph</p>
<p>Second paragraph</p>
</React.Fragment>
);
};
export default MyComponent;
也可以使用短语法 <></>
来表示 Fragment。
42. React 中如何处理跨域请求
- 使用代理服务器:在开发环境中,可以在
package.json
中配置 proxy
字段,将请求代理到后端服务器。例如:
{
"proxy": "http://localhost:3001"
}
- 在后端服务器设置 CORS 头:在生产环境中,需要在后端服务器设置 CORS(跨域资源共享)头,允许前端域名访问后端 API。例如,在 Node.js 的 Express 服务器中可以使用
cors
中间件:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// 其他路由和逻辑
- 使用 JSONP:JSONP(JSON with Padding)是一种跨域数据交互的方法,通过动态创建
<script>
标签来实现。但 JSONP 只支持 GET 请求,安全性相对较低。
43. 如何在 React 中实现组件的懒加载
- 使用
React.lazy
和 Suspense
:React.lazy
用于动态导入组件,Suspense
用于在组件加载过程中显示加载提示。例如:
import React, { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
const App = () => {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
- 路由级懒加载:在 React Router 中,可以使用
React.lazy
和 Suspense
实现路由级的懒加载。例如:
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
44. React 中如何进行状态持久化
- 使用本地存储(Local Storage 或 Session Storage):可以在组件的
useEffect
钩子中监听状态的变化,将状态存储到本地存储中,在组件初始化时从本地存储中读取状态。例如:
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(() => {
const storedCount = localStorage.getItem('count');
return storedCount ? parseInt(storedCount) : 0;
});
useEffect(() => {
localStorage.setItem('count', count);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default App;
- 使用 IndexedDB:对于大量数据的存储,可以使用 IndexedDB 进行状态持久化。它是一种基于浏览器的数据库,可以存储结构化数据。
- 使用 Redux - Persist:如果使用 Redux 进行状态管理,可以使用
redux - persist
库来实现状态的持久化。它可以将 Redux 的状态存储到本地存储或其他存储介质中,并在应用启动时恢复状态。
45. 如何在 React 中实现无限滚动加载
- 监听滚动事件:在组件中监听
window
或滚动容器的 scroll
事件,当滚动到底部时触发加载更多数据的操作。
- 计算滚动位置:通过比较滚动容器的
scrollTop
、clientHeight
和 scrollHeight
来判断是否滚动到底部。例如:
import React, { useState, useEffect } from 'react';
const InfiniteScroll = () => {
const [data, setData] = useState([]);
const [page, setPage] = useState(1);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(`https://api.example.com/data?page=${page}`);
const newData = await response.json();
setData(prevData => [...prevData, ...newData]);
};
fetchData();
}, [page]);
useEffect(() => {
const handleScroll = () => {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 200) {
setPage(prevPage => prevPage + 1);
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};
export default InfiniteScroll;
- 使用第三方库:也可以使用
react - infinite - scroll - component
等第三方库来实现无限滚动加载,这些库封装了滚动监听和加载逻辑,使用起来更加方便。
46. React 中如何处理文件上传
- 创建文件上传表单:使用
<input type="file">
元素创建文件上传表单。
- 监听文件选择事件:在组件中监听
input
元素的 change
事件,获取用户选择的文件。
- 上传文件:使用
FormData
对象将文件和其他数据封装成表单数据,然后使用 fetch
或 axios
等工具将表单数据发送到服务器。例如:
import React, { useState } from 'react';
const FileUpload = () => {
const [selectedFile, setSelectedFile] = useState(null);
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);
};
const handleSubmit = async (event) => {
event.preventDefault();
if (selectedFile) {
const formData = new FormData();
formData.append('file', selectedFile);
try {
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
console.log('File uploaded successfully');
} else {
console.log('File upload failed');
}
} catch (error) {
console.error('Error uploading file:', error);
}
}
};
return (
<form onSubmit={handleSubmit}>
<input type="file" onChange={handleFileChange} />
<button type="submit">Upload</button>
</form>
);
};
export default FileUpload;
47. 解释 React 中的高阶组件和渲染属性(Render Props)的区别
- 高阶组件(HOC):是一个函数,接收一个组件作为参数,并返回一个新的组件。它主要用于代码复用和状态管理。例如:
const withLogging = (WrappedComponent) => {
return (props) => {
console.log('Props received:', props);
return <WrappedComponent {...props} />;
};
};
const MyComponent = (props) => {
return <div>{props.message}</div>;
};
const LoggedComponent = withLogging(MyComponent);
- 渲染属性(Render Props):是一种通过传递一个函数作为 prop 来共享代码的技术。这个函数返回一个 React 元素,允许组件之间共享逻辑。例如:
const MouseTracker = ({ render }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
return (
<div onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
};
const App = () => {
return (
<MouseTracker
render={(position) => (
<p>Mouse position: {position.x}, {position.y}</p>
)}
/>
);
};
区别在于高阶组件是通过函数返回新组件来复用逻辑,而渲染属性是通过传递函数作为 prop 来复用逻辑。
48. 如何在 React 中使用 WebSocket 进行实时通信
- 创建 WebSocket 连接:在组件的
useEffect
钩子中创建 WebSocket 连接,并监听连接的打开、消息接收和关闭事件。
- 发送和接收消息:使用 WebSocket 的
send
方法发送消息,监听 message
事件接收服务器发送的消息。例如:
import React, { useEffect, useState } from 'react';
const WebSocketComponent = () => {
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
useEffect(() => {
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => {
console.log('WebSocket connection opened');
});
socket.addEventListener('message', (event) => {
setMessages(prevMessages => [...prevMessages, event.data]);
});
socket.addEventListener('close', () => {
console.log('WebSocket connection closed');
});
return () => {
socket.close();
};
}, []);
const handleSendMessage = () => {
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => {
socket.send(inputValue);
setInputValue('');
});
};
return (
<div>
<ul>
{messages.map((message, index) => (
<li key={index}>{message}</li>
))}
</ul>
<input
type="text"
value={inputValue}
onChange={(event) => setInputValue(event.target.value)}
/>
<button onClick={handleSendMessage}>Send</button>
</div>
);
};
export default WebSocketComponent;
49. React 中如何进行国际化(i18n)
- 使用
react - i18next
库:react - i18next
是一个流行的 React 国际化库,它结合了 i18next
核心库和 React 组件。
- 配置语言资源:创建不同语言的翻译文件,例如
en.json
和 zh.json
,并使用 i18next
加载这些资源。
- 使用
useTranslation
Hook 或 withTranslation
HOC:在组件中使用 useTranslation
Hook 或 withTranslation
HOC 来获取翻译函数,实现文本的国际化。例如:
import React from 'react';
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation();
return (
<div>
<p>{t('welcome_message')}</p>
</div>
);
};
export default MyComponent;
50. 如何在 React 中使用动画库(如 Framer Motion)
- 安装 Framer Motion:
npm install framer - motion
- 使用
motion
组件:Framer Motion 提供了 motion
组件,用于创建动画元素。可以通过设置 animate
、initial
和 transition
属性来定义动画效果。例如:
import React from 'react';
import { motion } from 'framer-motion';
const AnimatedBox = () => {
return (
<motion.div
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 1 }}
>
<p>Animated Box</p>
</motion.div>
);
};
export default AnimatedBox;
- 使用
useMotionValue
和 useTransform
等 Hook:可以使用这些 Hook 来创建更复杂的动画效果,例如根据滚动位置进行动画。
51. 解释 React 中的虚拟 DOM 是如何工作的
- 虚拟 DOM 是一种轻量级的 JavaScript 对象:它是真实 DOM 的抽象表示,React 用它来描述 UI 的状态。
- 渲染过程:当组件的
state
或 props
发生变化时,React 会创建一个新的虚拟 DOM 树。
- Diff 算法:React 会将新的虚拟 DOM 树与旧的虚拟 DOM 树进行比较(Diff),找出它们之间的差异。
- 更新真实 DOM:根据 Diff 的结果,React 只更新真实 DOM 中发生变化的部分,从而减少对真实 DOM 的操作次数,提高性能。
52. React 中如何进行单元测试
- 使用 Jest 和 React Testing Library:Jest 是一个流行的 JavaScript 测试框架,React Testing Library 用于测试 React 组件。
- 安装依赖:
npm install --save - dev jest @testing-library/react @testing-library/jest-dom
- 编写测试用例:例如,测试一个简单的组件:
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
test('renders text', () => {
render(<MyComponent />);
const element = screen.getByText(/Hello, World!/i);
expect(element).toBeInTheDocument();
});
- 运行测试:在
package.json
中配置测试脚本,然后运行 npm test
执行测试。
53. 如何在 React 中使用 Service Worker 实现离线支持
- 创建 Service Worker 文件:创建一个 JavaScript 文件,例如
service - worker.js
,在其中编写 Service Worker 的逻辑,如缓存静态资源和拦截网络请求。
- 注册 Service Worker:在 React 应用的入口文件(如
index.js
)中注册 Service Worker。例如:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service - worker.js')
.then(registration => {
console.log('Service Worker registered successfully:', registration);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
});
}
- 缓存资源:在 Service Worker 文件中使用
caches
API 缓存静态资源,如 HTML、CSS、JavaScript 和图片等。
- 拦截网络请求:使用
fetch
事件拦截网络请求,优先从缓存中获取资源,如果缓存中没有则从网络获取。
54. React 中如何处理多语言切换
- 使用
react - i18next
库:
- 配置语言资源:创建不同语言的翻译文件,如
en.json
、zh.json
等。
- 初始化
i18next
:在应用入口文件中初始化 i18next
并加载语言资源。
- 创建语言切换组件:在组件中使用
i18next.changeLanguage
方法切换语言。例如:
import React from 'react';
import i18next from 'i18next';
const LanguageSwitcher = () => {
const handleLanguageChange = (language) => {
i18next.changeLanguage(language);
};
return (
<div>
<button onClick={() => handleLanguageChange('en')}>English</button>
<button onClick={() => handleLanguageChange('zh')}>中文</button>
</div>
);
};
export default LanguageSwitcher;
55. 如何在 React 中使用 SVG 图标
- 直接在 JSX 中使用 SVG 代码:将 SVG 代码直接嵌入到 JSX 中。例如:
import React from 'react';
const MyIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10 - 4.48 10 - 10S17.52 2 12 2zm0 18c - 4.41 0 - 8 - 3.59 - 8 - 8s3.59 - 8 8 - 8 8 3.59 8 8 - 3.59 8 - 8 8z" />
</svg>
);
};
export default MyIcon;
- 使用 SVG 图标库:如
react - icons
库,它包含了很多流行的图标集,如 Font Awesome、Material Icons 等。安装相应的图标集后,直接在组件中导入和使用图标。例如:
import React from 'react';
import { FaHome } from 'react-icons/fa';
const HomeIcon = () => {
return <FaHome />;
};
export default HomeIcon;
56. React 中如何进行代码分割以提高性能
- 路由级代码分割:使用
React.lazy
和 Suspense
对路由组件进行代码分割。例如:
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
- 组件级代码分割:对于大型组件,可以使用
React.lazy
和 Suspense
进行组件级的代码分割,只在需要时加载组件。
57. 如何在 React 中实现拖拽功能
- 使用 HTML5 的 Drag and Drop API:在组件中监听
dragstart
、dragover
、drop
等事件,实现拖拽功能。例如:
import React, { useState } from 'react';
const DragAndDrop = () => {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const handleDragStart = (event, index) => {
event.dataTransfer.setData('text/plain', index);
};
const handleDrop = (event, targetIndex) => {
const sourceIndex = parseInt(event.dataTransfer.getData('text/plain'));
const newItems = [...items];
const [removed] = newItems.splice(sourceIndex, 1);
newItems.splice(targetIndex, 0, removed);
setItems(newItems);
};
const handleDragOver = (event) => {
event.preventDefault();
};
return (
<div>
{items.map((item, index) => (
<div
key={index}
draggable
onDragStart={(event) => handleDragStart(event, index)}
onDragOver={handleDragOver}
onDrop={(event) => handleDrop(event, index)}
>
{item}
</div>
))}
</div>
);
};
export default DragAndDrop;
- 使用第三方库:如
react - dnd
库,它提供了更高级的拖拽功能和更好的兼容性。
58. 解释 React 中的 Suspense
组件的作用
Suspense
组件用于在等待异步操作完成时显示一个加载指示器。它通常与 React.lazy
一起使用,用于处理代码分割和懒加载的组件。当懒加载的组件正在加载时,Suspense
组件会渲染其 fallback
属性指定的内容,当组件加载完成后,再渲染实际的组件。例如:
import React, { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
const App = () => {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
这样可以在组件加载过程中提供更好的用户体验,避免页面出现空白。