用AI前端整理React面试题 No.1

321 阅读19分钟

介绍

利用GPT的强大语言能力,整理了大量的前端题目,并将这些题目整合成了一份题目集合,希望能够帮到你。后期将不断更新。如果文章出现错误,请在评论区指出,我将会进行修改。如果有想要了解的题目也可以评论区留言,会加入后续更新列表。希望大家多多点赞关注。

本期题目

  1. React的生命周期方法有哪些?它们的作用是什么?
  2. 什么是React的状态(state)和属性(props)?它们之间有什么区别?
  3. React中的组件通信有哪些方式?它们的优缺点是什么?
  4. 什么是React的上下文(Context)?如何使用上下文实现组件之间的数据共享?
  5. React中的高阶组件(Higher Order Component)是什么?如何使用高阶组件实现组件复用?
  6. 什么是React的Hooks?它们有哪些常用的Hooks?如何使用Hooks实现组件的状态管理和副作用处理? React的Hooks的实现原理是什么?
  7. 什么是React的路由(Routing)?如何使用React-Router实现路由功能?

1、React的生命周期方法有哪些?它们的作用是什么?

在 React 中,类组件有以下生命周期方法。这些方法在组件的不同阶段被调用,以便你在合适的时机执行特定操作。这些生命周期方法可以分为三个主要阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。

  1. 挂载阶段:

    • constructor(props): 构造函数,用于初始化组件的状态和绑定方法。
    • static getDerivedStateFromProps(props, state): 在组件挂载前和更新前被调用。用于根据当前的属性和状态计算新的状态。返回一个新的状态对象或者 null 表示不进行任何更新。
    • render(): 必需的方法,负责组件的渲染。根据 propsstate 返回一个 React 元素。
    • componentDidMount(): 在组件挂载到 DOM 后立即调用。通常用于获取数据、设置订阅、设置事件监听器等。
  2. 更新阶段:

    • static getDerivedStateFromProps(props, state): 在组件挂载前和更新前被调用。用于根据当前的属性和状态计算新的状态。返回一个新的状态对象或者 null 表示不进行任何更新。
    • shouldComponentUpdate(nextProps, nextState): 根据属性和状态的变化决定是否重新渲染组件。返回 true 则更新,返回 false 则不更新。默认返回 true。可以用来优化组件性能。
    • render(): 必需的方法,负责组件的渲染。根据 propsstate 返回一个 React 元素。
    • getSnapshotBeforeUpdate(prevProps, prevState): 在 DOM 更新之前被调用,可以捕获更新前的某些信息(如滚动位置),在 componentDidUpdate 中使用。
    • componentDidUpdate(prevProps, prevState, snapshot): 在组件更新后立即调用。可以用来根据属性和状态的变化执行相应的操作,如获取新数据、DOM 操作等。
  3. 卸载阶段:

    • componentWillUnmount(): 在组件卸载和销毁之前调用。通常用于执行清理操作,如取消订阅、移除事件监听器等。

请注意,自 React 16.3 起,以下生命周期方法已被声明为不推荐使用,并在未来的 React 版本中将被删除:

  • componentWillMount()
  • componentWillReceiveProps(nextProps)
  • componentWillUpdate(nextProps, nextState)

在新的 React 代码中,建议使用其他生命周期方法或者 Hooks 来替代这些已被废弃的方法。

2、 什么是React的状态(state)和属性(props)?它们之间有什么区别?

React 中的状态(state)和属性(props)都是用于存储和管理组件数据的方式。它们在概念上和使用上有一些重要的区别:

  1. 状态(state)

    • 状态是组件内部管理的可变数据。
    • 状态可以随着用户交互、外部数据源等因素而改变。
    • 当状态发生变化时,组件会重新渲染。
    • 状态的改变通常通过 setState() 方法(在类组件中)或 useState() Hook(在函数组件中)实现。
    • 状态应该尽量保持局部化,只在需要使用或修改状态的组件内部进行管理。
  2. 属性(props)

    • 属性是从父组件传递给子组件的数据。
    • 属性在子组件中是只读的,子组件不应直接修改传入的属性。
    • 当属性发生变化时,子组件会重新渲染。
    • 属性可以用于传递数据、回调函数和组件配置等。
    • 通过属性,组件可以在不同层次的组件之间共享数据和行为。

简单来说,状态(state)是组件内部管理的可变数据,而属性(props)是从父组件传递给子组件的数据。状态通常用于组件内部的数据管理和交互,而属性用于组件间的数据和行为共享。组件应该遵循 "单向数据流" 原则,即数据应该从顶层组件向下流动,通过属性传递给子组件,而不是在组件树中任意修改。当需要修改由父组件传递的属性时,通常会通过回调函数(也作为属性传递)来通知父组件进行修改。

3、React中的组件通信有哪些方式?它们的优缺点是什么?

在 React 中,组件通信主要有以下几种方式:

  1. 父子组件通信:通过 props 传递数据

    • 优点:简单直接,适用于简单的组件层次结构。
    • 缺点:当组件层次较深时,逐层传递数据会导致冗余和难以维护。
  2. 子父组件通信:通过回调函数

    • 优点:实现子组件向父组件传递数据或请求,使得数据流更加清晰。
    • 缺点:当组件层次较深时,逐层传递回调函数也会导致冗余和难以维护。
  3. 兄弟组件通信:通过共同的父组件

    • 优点:将数据流集中在父组件中,使数据流更加清晰。
    • 缺点:当组件层次较深或兄弟组件较多时,父组件的管理压力会增加。
  4. 跨层级组件通信:通过 Context API

    • 优点:避免逐层传递数据和回调函数,简化组件层次结构。
    • 缺点:可能导致组件过度依赖 Context,降低组件的可复用性。
  5. 任意组件通信:通过事件总线(Event Bus)

    • 优点:实现任意组件之间的通信,不受组件层次结构限制。
    • 缺点:事件管理可能变得复杂,可能导致组件之间的耦合过高。
  6. 全局状态管理:通过 Redux、MobX 等状态管理库

    • 优点:集中管理全局状态,提供统一的数据流,更易于维护和扩展。
    • 缺点:引入额外的库和概念,可能导致学习曲线较陡峭。

根据具体的项目需求和组件层次结构,可以选择适合的组件通信方式。对于简单的组件层次结构,父子组件通信和子父组件通信是非常直接的方法。当组件层次较深或需要全局状态管理时,可以考虑使用 Context API、事件总线或状态管理库。

4、什么是React的上下文(Context)?如何使用上下文实现组件之间的数据共享?

React的上下文(Context)是一种在组件树中共享数据的方法。通过在祖先组件中设置Context的值,在后代组件中可以轻松地访问它。这使得在应用程序中传递数据变得更加容易和可靠,避免了在组件之间传递大量的props或使用全局变量。

在React中,通过创建一个Context对象来定义一个Context。例如:

const MyContext = React.createContext(defaultValue);

可以通过Context对象提供者(Provider)和使用者(Consumer)来使用Context。提供者通过将一个value属性传递给Provider组件来传递数据,而使用者可以使用Consumer组件来获取数据。例如:

// 父组件
<MyContext.Provider value={/* some value */}>
  <ChildComponent />
</MyContext.Provider>

// 子组件
<MyContext.Consumer>
  {value => /* do something with value */}
</MyContext.Consumer>

当提供者更新时,所有使用者都会自动更新。可以在组件中使用多个Context,并且使用者只会获取最接近的Provider组件的值。还可以使用ContextType或useContext Hooks来在组件中直接访问Context的值,例如:

// 在类组件中使用ContextType
class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    const value = this.context;
    /* do something with value */
  }
}

// 在函数组件中使用useContext
function MyFunction() {
  const value = useContext(MyContext);
  /* do something with value */
}

需要注意的是,Context应该被谨慎使用,因为它会将数据共享到应用程序的所有组件中。因此,应该尽可能将Context的使用限制在必要的情况下,并避免过度使用。

5、React中的高阶组件(Higher Order Component)是什么?如何使用高阶组件实现组件复用?

什么是高阶组件

高阶组件(Higher-Order Component,简称 HOC)是React中的一种高级技巧,用于实现组件逻辑的复用。高阶组件本质上是一个函数,它接收一个组件作为参数,然后返回一个新的组件,这个新组件在原始组件的基础上扩展或修改了一些功能。HOC 通过将组件包装在新的组件容器中,可以在不改变原始组件的情况下添加额外的功能。

实现一个高阶组件来实现组件复用,可以遵循以下步骤:

  1. 创建一个高阶组件函数,这个函数接收一个组件作为参数。
  2. 在高阶组件函数内部,定义一个新的组件类或函数式组件。
  3. 在新的组件内部,你可以实现一些额外的逻辑、属性或方法。
  4. 将传入的组件作为子组件渲染,可以将需要传递的属性和方法作为 props 传递给它。
  5. 返回新的组件。

下面是一个简单的高阶组件示例,用于向传入的组件添加一个新的 prop

import React from 'react';

// 高阶组件函数,接收一个组件作为参数
function withExtraProp(WrappedComponent, extraProp) {
  // 返回一个新的组件,这个组件会添加一个额外的 prop
  return function WithExtraProp(props) {
    // 合并原始 props 和额外的 prop
    const newProps = { ...props, extraProp };

    // 渲染传入的组件,并将新的 props 传递给它
    return <WrappedComponent {...newProps} />;
  };
}

// 原始组件
function MyComponent(props) {
  return (
    <div>
      <h1>{props.title}</h1>
      <p>{props.extraProp}</p>
    </div>
  );
}

// 使用高阶组件包装原始组件,并传入额外的 prop
const EnhancedComponent = withExtraProp(MyComponent, 'This is an extra prop');

export default EnhancedComponent;

应用场景

高阶组件(Higher-Order Components,简称 HOC)通常用于以下几种场景:

  1. 代码复用:当你有多个组件需要共享相同的逻辑或功能时,可以通过 HOC 将这些共享逻辑抽离出来,创建一个高阶组件来实现代码的复用。
  2. 控制 props:HOC 可以用于操作和管理传递给包装组件的 props。例如,你可以在 HOC 中为组件添加、修改或删除某些 props,以便将特定的属性和方法传递给包装组件。
  3. 动态添加或修改组件的生命周期方法:在某些情况下,你可能需要在组件的生命周期方法中执行一些额外的操作。通过 HOC,你可以在不改变原始组件的基础上,扩展或修改其生命周期方法。
  4. 权限控制和认证:可以通过 HOC 实现对组件访问权限的控制。例如,你可以创建一个需要登录才能访问的高阶组件,当用户未登录时,渲染一个登录提示,当用户已登录时,才渲染包装的组件。
  5. 数据加载和状态管理:HOC 可以用于加载数据并将其作为 props 传递给包装的组件。这种方式在集中处理数据请求和管理状态时非常有用,例如使用 Redux 进行状态管理时。
  6. 样式和主题:你可以使用 HOC 为多个组件提供统一的样式或主题。例如,可以创建一个高阶组件,它接收一个主题对象,并将该主题对象作为 prop 传递给包装的组件。

以下是一个简单的权限控制高阶组件示例:

import React from 'react';

function withAuthorization(WrappedComponent) {
  return class WithAuthorization extends React.Component {
    state = {
      isAuthenticated: false
    };

    componentDidMount() {
      // 模拟获取用户登录状态
      setTimeout(() => {
        this.setState({ isAuthenticated: true });
      }, 1000);
    }

    render() {
      const { isAuthenticated } = this.state;

      if (isAuthenticated) {
        return <WrappedComponent {...this.props} />;
      } else {
        return <p>Please log in to view this content.</p>;
      }
    }
  };
}

function ProtectedComponent(props) {
  return <div>Protected content. You are logged in.</div>;
}

const AuthorizedComponent = withAuthorization(ProtectedComponent);

export default AuthorizedComponent;

在这个示例中,我们创建了一个 withAuthorization 高阶组件,当用户已登录时,渲染包装的组件;当用户未登录时,显示一个登录提示。这种方式可以轻松地为多个组件提供相同的权限控制逻辑。

6、什么是React的Hooks?它们有哪些常用的Hooks?如何使用Hooks实现组件的状态管理和副作用处理?

Hooks简介

React Hooks 是 React 16.8 之后引入的一项功能,它允许你在不使用类组件的情况下,在函数式组件中使用状态和生命周期特性。Hooks 提供了一种更简洁、更易于理解和更具可维护性的方式来实现组件的状态管理和副作用处理。

常用的React Hooks

常用的 React Hooks 包括:

  1. useState:用于在函数式组件中添加状态。它返回一个状态变量和一个更新该状态变量的函数。
  2. useEffect:用于处理副作用(如数据请求、订阅、计时器等)。它接受一个函数,该函数在组件渲染后执行,可以用来处理一些副作用操作。还可以返回一个清除函数,用于清除副作用。
  3. useContext:用于访问 React 上下文。它允许你在组件中直接访问上下文的值,而无需使用 Context.Consumer
  4. useReducer:用于处理更复杂的状态逻辑。它接受一个 reducer 函数和一个初始状态,返回当前状态和一个与 dispatch 函数关联的 action
  5. useRef:用于访问和操作 DOM 元素。它返回一个可变的 ref 对象,该对象的 .current 属性被初始化为传递的参数(默认为 null)。
  6. useMemouseCallback:用于优化组件性能。useMemo 可以用来缓存计算结果,而 useCallback 可以用来缓存函数。

Hooks使用实例

useState和useEffect

下面是一个使用 useStateuseEffect 实现组件状态管理和副作用处理的示例:

import React, { useState, useEffect } from 'react';

function Example() {
  // 声明一个名为 “count” 的 state 变量,初始值为 0
  const [count, setCount] = useState(0);

  // 类似于 componentDidMount 和 componentDidUpdate 的组合
  useEffect(() => {
    // 更新文档的标题
    document.title = `You clicked ${count} times`;

    // 类似于 componentWillUnmount 的操作,用于清除副作用
    return () => {
      console.log('Clean up side effects');
    };
  }, [count]); // 仅在 count 发生变化时重新运行副作用

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Example;

在这个例子中,我们使用 useState Hook 来添加一个名为 count 的状态变量,并使用 setCount 函数来更新这个状态。然后,我们使用 useEffect Hook 来处理副作用,在组件挂载和更新时,更改文档的标题。同时,我们还提供了一个清除函数,用于在组件卸载时清除副作用。

使用 Hooks 可以使我们更容易地在函数式组件中实现状态管理和生命周期方法,从而简化组件代码并提高代码的可读性和可维护性。与类组件相比,函数式组件加上 Hooks 通常更简洁,这也有助于减少组件之间的逻辑重复。

useContext和useReducer

以下是一个使用 useContextuseReducer 的示例,实现一个简单的计数器功能,同时通过上下文共享状态:

import React, { createContext, useContext, useReducer } from 'react';

// 创建一个上下文
const CounterContext = createContext();

// 定义一个 reducer 函数
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      return state;
  }
};

function CounterProvider({ children }) {
  // 使用 useReducer 初始化 state 和 dispatch 函数
  const [state, dispatch] = useReducer(counterReducer, 0);

  // 使用上下文提供 state 和 dispatch
  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {children}
    </CounterContext.Provider>
  );
}

function Counter() {
  // 使用 useContext 获取上下文中的 state 和 dispatch
  const { state, dispatch } = useContext(CounterContext);

  return (
    <div>
      <p>Count: {state}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  );
}

export default App;

在这个示例中,我们首先创建了一个 CounterContext 上下文。然后,我们定义了一个 counterReducer 函数来处理计数器的逻辑。接下来,我们创建了一个 CounterProvider 组件,该组件使用 useReducer 初始化 state 和 dispatch 函数,并通过上下文共享它们。最后,我们在 Counter 组件中使用 useContext 获取上下文中的 statedispatch,实现计数器的加减功能。

总之,React Hooks 提供了一种在函数式组件中使用状态和生命周期方法的方式,使得我们可以更轻松地实现状态管理和副作用处理,同时提高代码的可读性和可维护性。

useMemouseCallback 都是 React Hooks 中的优化工具,它们可以帮助你避免不必要的组件重新渲染和函数重新计算,从而提高组件性能。当组件依赖于昂贵的计算结果或需要保持引用稳定性时,这两个 Hooks 非常有用。

useMemo 用于缓存函数的计算结果。当依赖项发生变化时,它将重新计算结果并返回。这可以避免在每次组件渲染时执行昂贵的计算。

useCallback 用于缓存函数。它返回一个包装过的函数,该函数的引用在依赖项未发生变化时保持不变。这对于向子组件传递回调函数时非常有用,因为它可以防止子组件在父组件的回调函数引用发生变化时不必要地重新渲染。

useMemo和useCallback

以下是 useMemouseCallback 的实际应用示例:

import React, { useState, useMemo, useCallback } from 'react';

function computeExpensiveValue(a, b) {
  // 模拟昂贵的计算
  console.log('Expensive computation running...');
  return a * b;
}

function App() {
  const [num1, setNum1] = useState(1);
  const [num2, setNum2] = useState(1);
  const [count, setCount] = useState(0);

  // 使用 useMemo 缓存计算结果
  const expensiveValue = useMemo(() => computeExpensiveValue(num1, num2), [
    num1,
    num2,
  ]);

  // 使用 useCallback 缓存函数
  const incrementCount = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Expensive value: {expensiveValue}</p>
      <input
        type="number"
        value={num1}
        onChange={(e) => setNum1(Number(e.target.value))}
      />
      <input
        type="number"
        value={num2}
        onChange={(e) => setNum2(Number(e.target.value))}
      />
      <hr />
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment count</button>
    </div>
  );
}

export default App;

在这个示例中,我们使用 useMemo 缓存了昂贵计算的结果,只有当 num1num2 发生变化时才会重新计算。这可以避免每次组件重新渲染时都执行昂贵的计算。同时,我们使用 useCallback 缓存了 incrementCount 函数,这样即使组件重新渲染,该函数的引用也不会发生变化。这对于避免传递给子组件的回调函数引起的不必要的重新渲染非常有用。

需要注意的是,不要过度使用 useMemouseCallback 进行优化,因为过度优化可能导致代码可读性降低,并且在某些情况下,这种优化可能并不会带来显著的性能提升。在实际应用中,你应该根据具体情况来判断是否需要使用这些 Hooks 进行优化。

以下是一些适合使用 useMemouseCallback 的场景:

  1. 当组件依赖于昂贵的计算结果时,可以使用 useMemo 来缓存这些结果,避免每次组件渲染时都重新计算。

  2. 当你需要将回调函数作为 props 传递给子组件时,可以使用 useCallback 来缓存这些函数。这样可以避免子组件因为父组件的回调函数引用发生变化而不必要地重新渲染。这对于性能敏感的应用和大型应用尤为重要。

  3. 当你需要确保对象、数组或函数的引用稳定时,可以使用 useMemouseCallback。这在某些依赖引用相等性检查的库或组件中非常有用,例如使用 React.memo 对子组件进行优化时。

总之,在使用 useMemouseCallback 时,请确保在必要的情况下进行优化,以免过度优化导致代码变得复杂且难以维护。在实际应用中,先要关注组件的功能和逻辑,然后再根据性能要求来考虑使用这些 Hooks。

7、什么是React的路由(Routing)?如何使用React-Router实现路由功能?

React 的路由(Routing)是指在单页面应用(SPA)中根据不同的 URL 路径展示不同的组件或页面。路由可以使用户在不刷新页面的情况下导航到不同的页面,提供更好的用户体验。React 本身并不包含路由功能,但可以通过第三方库(如 React Router)实现。

React Router 是一个流行的 React 路由库,用于实现客户端路由功能。以下是使用 React Router 实现路由功能的基本步骤:

  1. 安装 React Router:首先,你需要安装 React Router。你可以通过运行以下命令来安装: npm install react-router-dom

  2. 引入 React Router 组件:安装完成后,需要在项目中引入 React Router 相关的组件。以下是一些常用的组件:

    • BrowserRouter:提供 HTML5 历史记录 API 的路由器。
    • Route:用于根据 URL 路径渲染对应的组件。
    • Switch:用于包裹多个 Route,确保一次只渲染一个路由。
    • Link:用于创建导航链接。
  3. 配置路由:使用 BrowserRouterRouteSwitch 组件配置路由规则。例如:

import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
      </Switch>
    </BrowserRouter>
  );
}

export default App;

在这个例子中,我们配置了三个路由规则:/(主页)、/about(关于页面)和 /contact(联系我们页面)。Switch 组件确保一次只渲染一个路由。

  1. 创建导航链接:使用 Link 组件创建导航链接,使用户可以点击导航到不同的页面。例如:
import React from 'react';
import { Link } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
        <li>
          <Link to="/contact">Contact</Link>
        </li>
      </ul>
    </nav>
  );
}

export default Navbar;

Link 组件会根据 to 属性创建对应的导航链接。
通过以上步骤,你可以在 React 应用中使用 React Router 实现路由功能。你还可以使用其他 React Router 组件(如 RedirectNavLink 等)来实现更复杂的路由需求。

所有文章

用AI前端整理React面试题 No.1

用AI前端整理React面试题 No.2

用AI前端整理React面试题 No.3

用AI前端整理React面试题 No.4

用AI前端整理React面试题 No.5