前端 React 面试题汇总


1.你知道哪些React hooks?

  • useState:用于管理功能组件中的状态。
  • useEffect:用于在功能组件中执行副作用,例如获取数据或订阅事件。
  • useContext:用于访问功能组件内的 React 上下文的值。
  • useRef:用于创建对跨渲染持续存在的元素或值的可变引用。
  • useCallback:用于记忆函数以防止不必要的重新渲染。
  • useMemo:用于记忆值,通过缓存昂贵的计算来提高性能。
  • useReducer:用于通过reducer函数管理状态,类似于Redux的工作原理。
  • useLayoutEffect:与useEffect类似,但效果在所有DOM突变后同步运行。

这些 hooks 提供了强大的工具来管理状态、处理副作用以及重用 React 功能组件中的逻辑。

2.什么是虚拟DOM?

虚拟 DOM 是 React 中的一个概念,其中创建实际 DOM(文档对象模型)的轻量级虚拟表示并将其存储在内存中。它是一种用于优化 Web 应用程序性能的编程技术。

当 React 组件的数据或状态发生更改时,虚拟 DOM 会被更新,而不是直接操作真实 DOM。然后,虚拟 DOM 计算组件的先前状态和更新状态之间的差异,称为“比较”过程。

一旦识别出差异,React 就会高效地仅更新真实 DOM 的必要部分以反映更改。这种方法最大限度地减少了实际 DOM 操作的数量,并提高了应用程序的整体性能。

通过使用虚拟 DOM,React 提供了一种创建动态和交互式用户界面的方法,同时确保最佳效率和渲染速度。

3. 如何渲染元素数组?

要渲染元素数组,可以使用该map()方法迭代该数组并返回一个新的 React 元素数组。

const languages = [
  "JavaScript",
  "TypeScript",
  "Python",
];

function App() {
  return (
    <div>
      <ul>{languages.map((language) => <li>{language}</li>)}</ul>
    </div>
  );
}

4. 受控组件和非受控组件有什么区别?

受控组件和非受控组件之间的区别在于它们如何管理和更新其状态

受控组件是状态由 React 控制的组件。组件接收其当前值并通过 props 更新它。当值改变时它也会触发回调函数。这意味着该组件不存储其自己的内部状态。相反,父组件管理该值并将其传递给受控组件。

import { useState } from 'react'; 

function App() { 
  const [value, setValue] = useState(''); 

  return ( 
    <div> 
      <h3>Controlled Component</h3> 
      <input name="name" value={name} onChange={(e) => setValue(e.target.value)} />
      <button onClick={() => console.log(value)}>Get Value</button> 
    </div> 
  ); 
} 

另一方面,不受控制的组件使用 refs 或其他方法在内部管理自己的状态。它们独立存储和更新状态,不依赖 props 或回调。父组件对不受控组件的状态控制较少。

import { useRef } from 'react'; 

function App() { 
  const inputRef = useRef(null); 

  return ( 
    <div className="App"> 
      <h3>Uncontrolled Component</h3> 
      <input type="text" name="name" ref={inputRef} /> 
      <button onClick={() => console.log(inputRef.current.value)}>Get Value</button> 
    </div> 
  ); 
} 

5. 基于类的 React 组件和函数式 React 组件有什么区别?

基于类的组件和函数组件之间的主要区别在于它们的定义方式和使用的语法。

基于类的组件被定义为 ES6 类并扩展该类React.Component。他们使用该render方法返回定义组件输出的 JSX (JavaScript XML)。this.state类组件可以通过和访问组件生命周期方法和状态管理this.setState()

class App extends React.Component {
  state = {
    value: 0,
  };

  handleAgeChange = () => {
    this.setState({
      value: this.state.value + 1 
    });
  };

  render() {
    return (
      <>
        <p>Value is {this.state.value}</p>
        <button onClick={this.handleAgeChange}>
        Increment value
        </button>
      </>
    );
  }
}

函数组件被定义为简单的 JavaScript 函数。他们接受 props 作为参数并直接返回 JSX。功能组件无权访问生命周期方法或状态。然而,随着 React 16.8 中 React Hooks 的引入,功能组件现在可以管理状态并使用其他功能,例如上下文和效果。

import { useState } from 'react';

const App = () => {
  const [value, setValue] = useState(0);

  const handleAgeChange = () => {
    setValue(value + 1);
  };

  return (
      <>
        <p>Value is {value}</p>
        <button onClick={handleAgeChange}>
        Increment value
        </button>
      </>
  );
}

一般来说,函数组件被认为更简单、更容易阅读和测试。建议尽可能使用函数式组件,除非有特定需要基于类的组件。

6. 组件的生命周期方法有哪些?

生命周期方法是一种挂钩组件生命周期不同阶段的方法,允许您在特定时间执行特定代码。

以下是主要生命周期方法的列表:

  1. constructor:这是创建组件时调用的第一个方法。它用于初始化状态和绑定事件处理程序。在函数组件中,可以将useState钩子用于类似的目的。
  2. render:该方法负责渲染 JSX 标记并返回要在屏幕上显示的内容。
  3. componentDidMount:组件在 DOM 中渲染后立即调用此方法。它通常用于初始化任务,例如 API 调用或设置事件侦听器。
  4. componentDidUpdate:当组件的 props 或 state 改变时调用此方法。它允许您执行副作用、根据更改更新组件或触发其他 API 调用。
  5. componentWillUnmount:在组件从 DOM 中删除之前调用此方法。它用于清理在 中设置的任何资源componentDidMount,例如删除事件侦听器或取消计时器。

一些生命周期方法,如componentWillMountcomponentWillReceivePropscomponentWillUpdate,已被弃用或替换为替代方法或挂钩。

至于“this”,它指的是类组件的当前实例。它允许您访问组件内的属性和方法。在函数式组件中,不使用“this”,因为函数未绑定到特定实例。

7. 使用useState有什么特点?

useState返回一个状态值和一个更新它的函数。

const [value, setValue] = useState('Some state');

在初始渲染期间,返回的状态与作为第一个参数传递的值匹配。该setState函数用于更新状态。它采用新的状态值作为参数,并对组件的重新渲染进行排队。该setState函数还可以接受回调函数作为参数,该函数将之前的状态值作为参数。

8. 使用useEffect有什么特点?

useEffect 钩子允许在功能组件中执行副作用。 在功能组件的主体(即 React 渲染阶段)中不允许使用突变、订阅、定时器、日志记录和其他副作用。这可能会导致混乱的错误和用户界面的不一致。 因此,建议使用 useEffect。传递给 useEffect 的函数将在呈现提交到屏幕后执行,或者如果您将依赖关系数组作为第二个参数传递,那么每当其中一个依赖关系发生变化时,该函数就会被调用。

useEffect(() => {
  console.log('Logging something');
}, [])

9. 如何跟踪功能组件的卸载?

通常,useEffect 创建的资源需要在组件离开屏幕前进行清理或重置,例如订阅或计时器标识符。 为此,传递给 useEffect 的函数可以返回一个清理函数。清理函数将在组件从用户界面移除之前运行,以防止内存泄漏。此外,如果组件渲染多次(通常是这种情况),则会在执行下一个效果之前清理前一个效果。

useEffect(() => {
  function handleChange(value) {
    setValue(value);
  }
  SomeAPI.doFunction(id, handleChange);

  return function cleanup() {
    SomeAPI.undoFunction(id, handleChange);
  };
})

10. React 中的 props 是什么?

Props 是从父组件传递给组件的数据。props 是只读的,无法更改。

// Parent component
const Parent = () => {
  const data = "Hello, World!";

  return (
    <div>
      <Child data={data} />
    </div>
  );
};

// Child component
const Child = ({ data }) => {
  return <div>{data}</div>;
};

11. 什么是状态管理器?你使用或了解哪些状态管理器?

状态管理器是一种帮助管理应用程序状态的工具或库。它为存储和管理数据提供了一个集中的存储空间或容器,应用程序中的不同组件都可以访问和更新这些数据。 状态管理器可以解决几个问题。首先,将数据和与之相关的逻辑从组件中分离出来是一种很好的做法。其次,当使用本地状态并在组件之间传递时,由于组件有可能深嵌套,代码可能会变得错综复杂。通过全局存储,我们可以从任何组件访问和修改数据。 除了 React Context,Redux 或 MobX 也常用作状态管理库。

12. 在什么情况下可以使用本地状态,什么时候应该使用全局状态?

如果本地状态仅在一个组件中使用并且不打算将其传递给其他组件,则建议使用本地状态。本地状态也用在表示列表中单个项目的组件中。但是,如果组件分解涉及嵌套组件且数据沿层次结构传递,则最好使用全局状态。

13. Redux中的reducer是什么,它有哪些参数?

reducer是一个纯函数,以 stateaction 为参数。在reducer中,我们会跟踪接收到的操作类型,并根据它修改状态,返回一个新的状态对象。

export default function appReducer(state = initialState, action) {
  // The reducer normally looks at the action type field to decide what happens
  switch (action.type) {
    // Do something here based on the different types of actions
    default:
      // If this reducer doesn't recognize the action type, or doesn't
      // care about this specific action, return the existing state unchanged
      return state
  }
}

14. 什么是 Action,如何更改 Redux 中的状态?

Action 是一个简单的 JavaScript 对象,其字段必须具有类型的字段。

{
  type: "SOME_TYPE"
}

可以选择添加一些数据作为 payload。为了要改变状态,就必须调用调度函数,并向其传递动作

{
  type: "SOME_TYPE",
  payload: "Any payload",
}

15. Redux 实现了哪种模式?

Redux 实现了Flux 模式,它是应用程序的可预测状态管理模式。它通过引入单向数据流和应用程序状态的集中存储来帮助管理应用程序的状态。

16. Mobx 实现哪种模式?

Mobx 实现了观察者模式,也称为发布-订阅模式。

17. 使用 Mobx 的特点是什么?

Mobx 提供了类似observable和的装饰器computed来定义可观察的状态和反应函数。用action修饰的动作用于修改状态,确保跟踪所有更改。Mobx 还提供自动依赖跟踪、不同类型的反应、对反应性的细粒度控制,以及通过 mobx-react 包与 React 无缝集成。总体而言,Mobx 通过根据可观察状态的变化自动执行更新过程来简化状态管理。

8. 如何访问Mobx状态下的变量?

可以通过使用装饰器 observable 将变量定义为可观察来访问状态中的变量。这是一个例子:

import { observable, computed } from 'mobx';

class MyStore {
  @observable myVariable = 'Hello Mobx';

  @computed get capitalizedVariable() {
    return this.myVariable.toUpperCase();
  }
}

const store = new MyStore();
console.log(store.capitalizedVariable); // Output: HELLO MOBX

store.myVariable = 'Hi Mobx';
console.log(store.capitalizedVariable); // Output: HI MOBX

在本例中,myVariable 使用 observable 装饰器定义为可观测变量。然后就可以使用 store.myVariable 访问该变量。对 myVariable 所做的任何更改都会自动触发依赖组件或反应的更新。

19.Redux和Mobx有什么区别?

  • Redux 是一种更简单、更有主见的状态管理库,它遵循严格的单向数据流,并提倡不变性。它需要更多的模板代码和显式更新,但与 React 的集成度很高。
  • Mobx 提供的 API 更灵活、更直观,模板代码更少。它允许你直接修改状态,并自动跟踪变化以获得更好的性能。在 Redux 和 Mobx 之间做出选择取决于您的具体需求和偏好。

20.什么是JSX?

默认情况下,在 react 中使用以下语法创建元素。

const someElement = React.createElement(
  'h3',
  {className: 'title__value'},
  'Some Title Value'
);

但我们通常这样去写:

const someElement = (
  <h3 className='title__value'>Some Title Value</h3>
);

这正是被称为 jsx 的标记。这是一种语言扩展 简化了代码和开发的感知

21.什么是 Props 透传?

Props 透传是指通过多层嵌套组件传递 props 的过程,即使某些中间组件不直接使用这些 props。这可能会导致代码结构复杂且繁琐。

// Parent component
const Parent = () => {
  const data = "Hello, World!";

  return (
    <div>
      <ChildA data={data} />
    </div>
  );
};

// Intermediate ChildA component
const ChildA = ({ data }) => {
  return (
    <div>
      <ChildB data={data} />
    </div>
  );
};

// Leaf ChildB component
const ChildB = ({ data }) => {
  return <div>{data}</div>;
};

在此示例中,dataprop 从 Parent 组件传递到 ChildA,然后从 ChildA 传递到 ChildB,即使 ChildA 不直接使用该 prop。当存在许多级别的嵌套或当组件树中更靠下的组件需要访问数据时,这可能会成为问题。它会使代码更难维护和理解。

可以通过使用其他模式(如上下文或状态管理库(如 Redux 或 MobX))来代替 Props 透传。这些方法允许组件访问数据,而不需要通过每个中间组件传递 props。

22. 如何有条件地渲染元素?

可以使用任何条件运算符,包括三元。

return (
  <div>
    {isVisible && <span>I'm visible!</span>}
  </div>
);

return (
  <div>
    {isOnline ? <span>I'm online!</span> : <span>I'm offline</span>}
  </div>
);

if (isOnline) {
  element = <span>I'm online!</span>;
} else {
  element = <span>I'm offline</span>;
}

return (
  <div>
    {element}
  </div>
);

23. useMemo 的用途是什么?它是如何工作的?

用于缓存和记忆计算结果。计算结果。useMemo 仅在任何依赖项的值发生变化时才会重新计算记忆值。这种优化有助于避免昂贵的计算。

对于第一个参数,函数接受一个执行计算的回调,对于第二个参数,函数接受一个依赖关系数组,只有当至少一个依赖关系发生变化时,函数才会重新执行计算。

const memoValue = useMemo(() => computeFunc(paramA, paramB), [paramA, paramB]);

24. useCallback 的用途是什么?它是如何工作的?

useCallback 钩子将返回回调的记忆化版本,只有当其中一个依赖项的值发生变化时,回调才会发生变化。 这在将回调传递给依赖链接平等性的优化子组件时非常有用,可以防止不必要的呈现。

const callbackValue = useCallback(() => computeFunc(paramA, paramB), [paramA, paramB]);

25. useMemo 和 useCallback 有什么区别?

  1. useMemo用于记忆计算结果,而useCallback用于记忆函数本身。
  2. useMemo如果依赖项未更改,则缓存计算值并在后续渲染时返回该值。
  3. useCallback缓存函数本身并返回相同的实例,除非依赖项已更改。

26.什么是React上下文?

React Context 是一项功能,它提供了一种在组件树中传递数据的方法,而无需在每一层手动传递道具。它允许您创建一个全局状态,树中的任何组件都可以访问该状态,无论其位置如何。当您需要在多个未通过道具直接连接的组件之间共享数据时,上下文就非常有用。

React Context API 由三个主要部分组成:

  1. createContext(创建上下文): 该函数用于创建新的上下文对象。
  2. Context.Provider: 该组件用于为上下文提供值。它封装了需要访问该值的组件。
  3. Context.Consumer 或 useContext 钩子: 该组件或钩子用于从上下文中获取值。它可以在上下文提供者的任何组件中使用。

通过使用 React Context,可以避免props 透传(通过多级组件传递props),并在更高层次上轻松管理状态,从而使代码更有条理、更高效。

27. useContext 的用途是什么?它是如何工作的?

在典型的 React 应用程序中,数据是使用道具从上到下(从父组件到子组件)传递的。但是,这种使用方法对于某些类型的道具(例如所选语言、用户界面主题)来说可能过于繁琐,因为这些道具必须传递给应用程序中的许多组件。
(例如,所选语言、用户界面主题)来说可能过于繁琐,因为它们必须传递给应用程序中的许多组件。上下文提供了一种在组件间共享此类数据的方法,而无需明确地将道具传递到树的每一层。 树的每一级传递道具。
当上下文值发生变化时,调用 useContext 的组件总是会被重新渲染。
上下文值发生变化时,调用 useContext 的组件总是会被重新渲染。如果重新渲染组件的成本很高,可以使用 memoization 对其进行优化。

const App = () => {
  const theme = useContext(ThemeContext);

  return (
    <div style={{ color: theme.palette.primary.main }}>
      Some div
    </div>
  );
}

28. useRef 的用途是什么?它是如何工作的?

useRef 返回一个可修改的 ref 对象,即一个属性。该对象的当前值由传递的参数初始化。返回的对象将在组件的整个生命周期内持续存在,不会因呈现而改变。 通常的使用情况是以命令式方式访问后代对象。也就是说,我们可以使用 ref 明确地引用 DOM 元素。

const App = () => {
  const inputRef = useRef(null);

  const buttonClick = () => {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={buttonClick}>Focus on input tag</button>
    </>
  )
}

29. 什么是React.memo()?

React.memo() 是一个高阶组件。如果组件总是以不变的道具渲染相同的内容,可以在某些情况下将其封装在 React.memo() 调用中以提高性能,从而记住渲染结果。这意味着 React 将使用上次渲染的结果,避免重新渲染。React.memo() 只影响对道具的更改。如果功能组件被封装在 React.memo 中并使用了 useState、useReducer 或 useContext,那么当状态或上下文发生变化时,它将被重新渲染。

import { memo } from 'react';

const MemoComponent = memo(MemoComponent = (props) => {
  // ...
});

30.什么是React Fragment?

从组件返回多个元素是 React 中的常见做法。片段允许形成子元素列表,而无需在 DOM 中创建不必要的节点。

<>
  <OneChild />
  <AnotherChild />
</>
// or
<React.Fragment>
  <OneChild />
  <AnotherChild />
</React.Fragment>

31.什么是React调和?

调和是 React 的一种算法,用于区分一棵元素树和另一棵元素树,以确定需要替换的部分。 调和是我们过去所说的虚拟 DOM 背后的算法。其定义听起来是这样的:当您渲染 React 应用程序时,描述应用程序的元素树会在预留内存中生成。然后,这棵树就会被包含在呈现环境中,例如,在浏览器应用程序中,它会被转化为一组 DOM 操作。应用状态更新时,会生成新的元素树。新的树会与之前的树进行比较,以便准确计算和启用重新绘制更新后的应用程序所需的操作。 了解更多

32. 为什么使用map()时需要key?

这些键可帮助 React 确定哪些元素已被更改、添加或移除。必须指定这些键,这样 React 才能匹配 数组元素。选择键的最佳方法是使用一个能将列表项与其相邻项明确区分开来的字符串。通常,您会使用数据中的 ID 作为键。

const languages = [
  {
    id: 1,
    lang: "JavaScript",
  },
  {
    id: 2,
    lang: "TypeScript",
  },
  {
    id: 3,
    lang: "Python",
  },
];

const App = () => {
  return (
    <div>
      <ul>{languages.map((language) => (
        <li key={`${language.id}_${language.lang}`}>{language.lang}</li>
      ))}
      </ul>
    </div>
  );
}

33. 如何在 Redux Thunk 中处理异步操作?

要使用 Redux Thunk,需要将其作为中间件导入。Action创建者不应只返回一个对象,而应返回一个将dispatch 作为参数的函数。

export const addUser = ({ firstName, lastName }) => {
  return dispatch => {
    dispatch(addUserStart());
  }

  axios.post('https://jsonplaceholder.typicode.com/users', {
    firstName,
    lastName,
    completed: false
  })
  .then(res => {
    dispatch(addUserSuccess(res.data));
  })
  .catch(error => {
    dispatch(addUserError(error.message));
  })
}

34. 如何跟踪功能组件中对象字段的变化?

需要使用 useEffect 钩子,并将对象的字段作为依赖数组传递。

useEffect(() => {
  console.log('Changed!')
}, [obj.someField])

35.如何访问DOM元素?

引用是使用 React.createRef()useRef() 钩子创建的,并通过 ref 属性附加到 React 元素上。通过访问创建的引用,我们可以使用 ref.current 访问 DOM 元素。

const App = () => {
  const myRef = useRef(null);

  const handleClick = () => {
    console.log(myRef.current); // Accessing the DOM element
  };

  return (
    <div>
      <input type="text" ref={myRef} />
      <button onClick={handleClick}>Click Me</button>
    </div>
  );
}

export default App;

36.什么是自定义钩子?

自定义钩子是一种允许您在不同组件之间重复使用逻辑的功能。它是一种封装可重用逻辑的方法,以便在多个组件之间轻松共享和重用。自定义钩子是通常以 *use * 开头的函数,可在需要时调用其他钩子。

37.什么是公共API?

在索引文件中,公共 API 通常是指暴露给外部模块或组件并可供其访问的接口或函数。 下面是一个表示公共 API 的索引文件的代码示例:

// index.js

export function greet(name) {
  return `Hello, ${name}!`;
}

export function calculateSum(a, b) {
  return a + b;
}

在此示例中,index.js 文件充当公共 API,其中导出函数greet()和,并且可以通过导入函数从其他模块访问它们。calculateSum()其他模块可以导入并使用这些函数作为其实现的一部分:

// main.js

import { greet, calculateSum } from './index.js';

console.log(greet('John')); // Hello, John!
console.log(calculateSum(5, 3)); // 8

通过从索引文件导出特定函数,我们定义了模块的公共 API,允许其他模块使用这些函数。

38. 创建自定义钩子的规则是什么?

  1. 钩子名称以 "use "开头。
  2. 如有需要,请使用现有钩子。
  3. 不要有条件地调用钩子。
  4. 将可重复使用的逻辑提取到自定义钩子中。
  5. 自定义钩子必须是纯函数。
  6. 自定义钩子可以返回值或其他钩子。
  7. 以描述性的方式命名自定义钩子。

39.什么是SSR(服务器端渲染)?

服务器端渲染(SSR)是一种用于在服务器上渲染网页并将完全渲染后的网页发送到客户端显示的技术。它允许服务器生成网页的完整 HTML 标记,包括动态内容,并作为对请求的响应发送给客户端。

在传统的客户端呈现方法中,客户端会收到一个最小的 HTML 页面,然后向服务器提出额外的数据和资源请求,用于在客户端呈现页面。这会导致初始页面加载时间变慢,并对搜索引擎优化(SEO)产生负面影响,因为搜索引擎爬虫很难索引 JavaScript 驱动的内容。

使用 SSR 时,服务器通过执行必要的 JavaScript 代码来生成最终的 HTML,从而完成网页的渲染。这意味着客户端可以从服务器接收完全呈现的网页,从而减少了对额外资源请求的需求。SSR 可改善初始页面加载时间,并允许搜索引擎轻松索引内容,从而提高搜索引擎优化效果。

SSR 常用于 React 的 Next.js 和 Vue.js 的 Nuxt.js 等框架和库中,以启用服务器端呈现功能。这些框架会为您处理服务器端呈现逻辑,从而更容易实现 SSR。

40.使用SSR有什么好处?

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java