React 面试题

1. React中keys的作用是什么?

Keys是React用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,需要保证某个元素的key在其同级元素中具有唯一性。在React Diff算法中,React会借助元素的Key值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React还需要借助key值来判断元素与本地状态的关联关系。

2. 当你调用setState的时候,发生了什么事?

将传递给setState的对象合并到组件的当前状态,这将启动一个和解的过程,构建一个新的react元素树,与上一个元素树进行对比(diff),从而进行最小化的重渲染。

3. 为什么setState的参数是一个callback而不是一个对象?

因为this.propsthis.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 和 SuspenseReact.lazy 函数允许你动态导入组件,Suspense 组件用于在组件加载完成之前显示一个加载指示器。
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}
  • 路由级代码分割:在 React Router 中,可以使用 React.lazySuspense 进行路由级的代码分割。
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 - Reduxnpm 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 函数或者 useSelectoruseDispatch 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 类型不匹配的问题。使用步骤如下:

  • 导入 PropTypesimport 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 元素创建表单,并为表单元素添加 onChangeonSubmit 事件处理函数。
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)?

  • 安装第三方库:使用 npmyarn 安装第三方库,例如 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 中的 useStateuseReducer 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 中使用 useMemouseCallback 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>
  );
};

使用 useMemouseCallback 可以避免不必要的渲染和计算,提高组件的性能。

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 错误,防止整个应用崩溃。可以通过创建一个类组件,并实现 componentDidCatchgetDerivedStateFromError 生命周期方法来定义错误边界。

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>
  );
};

这里通过 forwardRefrefParentComponent 传递到了 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 可以检查以下问题:

  • 识别不安全的生命周期方法:例如在 componentWillReceivePropscomponentWillMount 等不安全的生命周期方法中使用。
  • 检测意外的副作用:例如在渲染过程中执行副作用操作。
  • 检测过时的 context API:提醒开发者使用新的 context API。 使用 StrictMode 很简单,只需要将需要检查的组件包裹在 <React.StrictMode> 标签中即可。
import React from 'react';

const App = () => {
  return (
    <React.StrictMode>
      {/* 应用组件 */}
    </React.StrictMode>
  );
};

37. 如何在 React 中使用 React Router 进行路由导航?

  • 安装 React Routernpm install react-router-dom
  • 创建路由配置:使用 BrowserRouterRouteSwitch 组件来配置路由。
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;
  • 导航:可以使用 Link 组件进行导航。
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.renderToStringReactDOMServer.renderToStaticMarkup 方法将 React 组件渲染为 HTML 字符串。
  • 将渲染后的 HTML 发送给客户端:服务器将渲染好的 HTML 字符串发送给客户端浏览器。
  • 在客户端进行水合(Hydration):客户端使用 ReactDOM.hydrateRoot 方法将服务器渲染的静态 HTML 转换为可交互的 React 应用。

40. 如何在 React 中进行性能分析和优化

  • 性能分析
    • 使用 React DevTools:可以查看组件的渲染时间、Props 和 State 的变化等信息。
    • 使用浏览器的开发者工具:如 Chrome 的 Performance 面板,分析页面的加载和渲染性能。
    • 使用 React.memouseMemouseCallback:避免不必要的渲染和计算。
  • 优化策略
    • 减少渲染节点:避免创建过多的 DOM 元素。
    • 优化状态管理:合理使用 stateprops,避免不必要的状态更新。
    • 使用虚拟列表:处理大量数据列表时,只渲染可见区域的元素。
    • 代码分割:使用 React.lazySuspense 进行代码分割,减少初始加载时间。

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.lazySuspenseReact.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.lazySuspense 实现路由级的懒加载。例如:
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 事件,当滚动到底部时触发加载更多数据的操作。
  • 计算滚动位置:通过比较滚动容器的 scrollTopclientHeightscrollHeight 来判断是否滚动到底部。例如:
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 对象将文件和其他数据封装成表单数据,然后使用 fetchaxios 等工具将表单数据发送到服务器。例如:
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 - i18nextreact - i18next 是一个流行的 React 国际化库,它结合了 i18next 核心库和 React 组件。
  • 配置语言资源:创建不同语言的翻译文件,例如 en.jsonzh.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 Motionnpm install framer - motion
  • 使用 motion 组件:Framer Motion 提供了 motion 组件,用于创建动画元素。可以通过设置 animateinitialtransition 属性来定义动画效果。例如:
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;
  • 使用 useMotionValueuseTransform 等 Hook:可以使用这些 Hook 来创建更复杂的动画效果,例如根据滚动位置进行动画。

51. 解释 React 中的虚拟 DOM 是如何工作的

  • 虚拟 DOM 是一种轻量级的 JavaScript 对象:它是真实 DOM 的抽象表示,React 用它来描述 UI 的状态。
  • 渲染过程:当组件的 stateprops 发生变化时,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.jsonzh.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.lazySuspense 对路由组件进行代码分割。例如:
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.lazySuspense 进行组件级的代码分割,只在需要时加载组件。

57. 如何在 React 中实现拖拽功能

  • 使用 HTML5 的 Drag and Drop API:在组件中监听 dragstartdragoverdrop 等事件,实现拖拽功能。例如:
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;

这样可以在组件加载过程中提供更好的用户体验,避免页面出现空白。

ON THIS PAGE