(React组件基础)前端八股文修炼Day6

一 类组件与函数组件有什么异同

在React中,类组件和函数组件是创建组件的两种主要方式。随着React的发展,尤其是自Hooks在React 16.8中引入以来,函数组件的功能变得更加强大,使得它们能够更加方便地与类组件相竞争。下面是类组件与函数组件在不同方面的异同:

类组件

特征:

  • 使用ES6的类语法定义。
  • 必须包含render()方法,其返回React元素。
  • 可以使用React生命周期方法(如componentDidMount, shouldComponentUpdate等)。
  • 状态(state)和属性(props)通过this关键字访问。
  • 直到Hooks的引入,类组件是唯一可以使用内部状态和生命周期方法的方式。

示例:

import React, { Component } from 'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { /* 初始状态 */ };
  }

  render() {
    return 
Hello, {this.props.name}
; } }

函数组件

特征:

  • 使用普通的JavaScript函数(或箭头函数)定义。
  • 直接返回React元素。
  • 在React 16.8之前主要用于无状态组件。引入Hooks之后,函数组件可以使用状态(useState)和其他React特性(如生命周期的替代品useEffect)。
  • 组件的状态和属性通过函数参数访问。

示例:

import React from 'react';

function MyComponent(props) {
  return 
Hello, {props.name}
; } // 或使用箭头函数 const MyComponent = (props) =>
Hello, {props.name}
;

异同

:

  • 定义方式:类组件通过类定义,函数组件通过函数定义。
  • 状态和生命周期方法:类组件可以通过this.state和生命周期方法直接管理状态和副作用。函数组件通过Hooks(如useState, useEffect等)管理状态和副作用。
  • this关键字:类组件中可以通过this访问实例的属性和状态,函数组件中没有this
  • 构造函数:类组件可以有一个构造函数,用于初始化状态等,函数组件没有构造函数,但可以通过Hooks初始化状态。

:

  • 组件返回:无论是类组件还是函数组件,都必须返回React元素。
  • Props:两种组件都接收props作为参数。
  • 功能:随着Hooks的引入,函数组件现在几乎可以执行类组件能做的所有事情,包括使用状态、副作用、上下文(Context)、引用(Refs)等。

总的来说,随着React Hooks的引入,函数组件的能力得到了极大的增强,使得开发者能够以更简洁和函数式的方式编写组件,同时也保留了类组件的大部分能力。

话术试炼

在面试中讨论类组件与函数组件的异同时,你需要简洁且准确地表达这两种组件类型的关键特点。以下是你可以在面试时使用的讲解框架:

引入:

首先,可以简要介绍React组件和它们的核心作用:

“React组件是React应用的构建块,它们定义了应用界面的一部分。组件可以是类组件或函数组件,两者都可以接收输入(称为props),并返回需要渲染到页面上的React元素。”

类组件:

然后,对类组件进行描述:

“类组件是使用ES6类语法创建的,它们继承自React.Component。类组件的特点是拥有状态(state)和生命周期方法,比如componentDidMount来执行组件挂载后的操作,以及componentDidUpdate来处理组件更新后的行为。状态可以通过this.state访问和更新,通常使用this.setState方法。”

函数组件:

接着,描述函数组件:

“函数组件则更简洁,它们是普通的JavaScript函数,返回React元素。在Hooks引入之前,函数组件被认为是无状态的,只能接收props并渲染。但自从React
16.8引入Hooks后,函数组件也可以通过useState钩子管理状态,使用useEffect处理副作用等,从而拥有了类似类组件的能力。”

异同点:

最后,总结它们的异同:

“类组件和函数组件的主要区别在于语法和组件特性。类组件通过类和继承来定义,拥有显式的生命周期方法和状态管理,而函数组件使用函数和Hooks来实现相同的功能。随着Hooks的引入,函数组件可以做几乎所有类组件能做的事情,但用起来更简洁,代码量通常更少。”

“在实践中,React团队鼓励新的组件使用函数组件和Hooks,因为这种方式更现代,且更容易整合React的未来特性。然而,类组件在一些特定情况下仍然有其用武之地,比如当你需要使用错误边界(Error
Boundaries)时,目前只能通过类组件实现。”

结语:

“总之,无论是类组件还是函数组件,选择哪一个取决于特定的场景和开发者的个人偏好。了解两者的区别可以帮助我们更好地决定在特定的情况下使用哪种类型的组件。”

二 React refs

什么是React refs?

在React中,refs是一种可以存储对DOM节点或React元素实例的直接引用的机制。通俗地说,当你需要在React的数据流(props和state)之外直接访问一个组件的DOM元素时,你会使用refs。

refs的作用

refs主要有以下作用:

  1. 控制焦点、文本选择或媒体播放:比如,你可以在组件加载完成后立即给一个输入框加上焦点。

  2. 触发强制动画:有时你需要直接修改DOM来执行动画,而不是通过状态变化来实现。

  3. 集成第三方DOM库:当你需要使用那些需要直接操作DOM以集成到React应用中的库时。

  4. 读取DOM信息:如果你需要读取某个元素的尺寸或位置等信息,refs可以提供一个方便的途径来读取这些信息。

在render中访问refs

在React的render方法中,你不应该访问refs。这是因为render方法的目的是返回一个元素描述对象(也就是React元素),这个过程应该是纯净无副作用的。Ref的赋值实际上会在React处理render方法返回的结果之后发生,这意味着在render方法内部,ref还没有被赋予一个DOM节点。

简单地说,在render方法中尝试访问ref是不可靠的,因为此时组件尚未挂载或更新完成,ref还没有指向任何东西。如果你尝试在render方法中访问一个ref,可能会得到undefined或者是上一次渲染时ref的值。

正确的做法是在组件的生命周期方法中访问refs,例如componentDidMountcomponentDidUpdate,或者在函数组件中使用useEffect Hook。在这些生命周期阶段,如果组件已经被挂载或更新,那么ref将会是一个指向DOM节点的有效引用。

示例

这是一个类组件中使用ref的例子:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    this.myRef.current.focus(); // 此时可以安全地访问ref
  }

  render() {
    return ;
  }
}

这是一个函数组件中使用ref的例子:

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

function MyComponent() {
  const myRef = useRef(null);

  useEffect(() => {
    myRef.current.focus(); // 在useEffect中访问ref
  }, []);

  return ;
}

在这两个例子中,ref都被用来在组件挂载完成后立即给input元素设置焦点。注意,在函数组件中我们使用了useRefuseEffect Hooks。

话术试炼

面向面试官讲解Reactrefs时,你的目标是清晰、准确且全面地介绍refs的概念、用途、使用方法以及相关的最佳实践。以下是一个框架,帮助你系统地讲解:

引入React refs

  1. 定义:“在React中,refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。它是’references’的缩写,主要用于直接操作DOM。”

  2. 为什么需要Refs:“虽然React强烈推荐使用声明式方法来处理应用的状态和数据流,但在某些情况下,我们仍然需要直接访问DOM节点。例如,管理焦点、触发动画或集成第三方DOM库。”

使用场景

  1. 控制焦点:“一个常见的使用案例是管理输入框的焦点,比如在表单验证失败时自动聚焦到错误的输入框上。”

  2. 触发动画:“在需要通过直接修改DOM元素属性来触发动画时,refs可以直接修改元素的style或触发动画API。”

  3. 集成第三方库:“当使用需要直接对DOM操作的第三方库时,比如D3.js生成图表,refs可以提供直接的DOM访问能力。”

创建Refs

  1. 使用React.createRef:“在类组件中,我们通常在构造函数中通过React.createRef创建ref,并将其赋值给类的一个属性。”

  2. 使用useRef Hook:“在函数组件中,可以使用useRef Hook来创建refs。这个Hook既可以用于DOM访问,也可以用作渲染周期之间的持久化存储。”

访问Refs

  1. 在类组件中:“通过this.myRef.current访问创建的ref指向的DOM节点。”

  2. 在函数组件中:“使用myRef.current来访问ref。”

React.forwardRef

  1. 介绍:“React.forwardRef是一个React提供的API,它允许你将ref自动地通过组件传递给其子组件。这在高阶组件或函数作为子组件的情况下特别有用。”

最佳实践

  1. 避免过度使用:“尽管refs在某些情况下很有用,但我们仍然应该优先考虑使用React的数据流(props和state)来解决问题。”

  2. 确保正确的使用时机:“避免在组件的render方法中访问refs,因为这可能会导致不一致的行为。相反,应该在componentDidMountcomponentDidUpdate生命周期方法中访问它们,或在函数组件中使用useEffect。”

结语

最后,你可以总结:“总的来说,Reactrefs提供了一个逃生舱,允许我们在必要时直接操作DOM。正确使用refs,可以有效地解决那些超出React数据流范畴的特定问题。”

三 React 事件React的事件和普通的HTML事件有什么不同​

React事件处理系统与传统HTML(DOM)事件处理有几个关键区别。这些区别使得React的事件处理更加一致,易于管理,同时也提高了跨浏览器的兼容性。下面是React事件与普通HTML事件之间的一些主要不同点:

1. 事件包装

  • React事件是合成事件(SyntheticEvent):React为了确保跨浏览器一致性,将原生事件包装在SyntheticEvent对象中。这意味着无论你在哪个浏览器上使用React,事件对象都将保持一致的属性和方法。
  • HTML原生事件直接暴露:在传统的HTML中,事件处理器直接接触到浏览器提供的原生事件对象。

2. 事件命名约定

  • React使用驼峰命名法:在React中,所有的事件都是以驼峰命名法书写的,例如onClickonSubmit等。
  • HTML使用小写:在传统的HTML事件中,事件名称通常都是全小写的,如onclickonsubmit等。

3. 事件委托

  • React自动进行事件委托:React自动将所有事件处理函数委托到文档的最高层级。这意味着,即使你有成千上万的组件监听相同的事件,React也只会在DOM树中使用一个单独的事件监听器。这有助于减少内存占用并提高性能。
  • HTML事件需要手动设置事件委托:在传统HTML中,如果你需要使用事件委托,必须手动实现。

4. 事件处理方式

  • React中通过传递函数来绑定事件处理器:你需要将一个函数传递给事件处理器属性,如onClick={handleClick}
  • HTML中可以直接使用字符串:在传统的HTML中,你可以在事件属性中直接编写JavaScript代码,如onclick="handleClick()"

5. 性能和优化

  • React通过合成事件和事件委托优化性能:React的事件系统通过合成事件和自动事件委托的方式,减少了内存的占用,并且减少了直接与DOM交互的次数,这有助于提升应用的性能。
  • HTML中性能优化依赖开发者:在传统HTML中,开发者需要自己考虑如何优化事件处理,比如何时使用事件委托等。

6. 自动绑定this

  • React类组件中的事件处理方法通常需要手动绑定this:在React类组件中,如果你想在事件处理函数中使用this来访问组件实例,你需要手动绑定this,或使用箭头函数来自动绑定。
  • HTML中的this直接指向触发事件的元素:在传统HTML的事件处理函数中,this指向绑定事件的DOM元素。

结论

React的事件处理系统提供了一种更为一致、简洁且性能优化的方式来处理Web应用中的事件。通过合成事件和事件委托,React使得事件处理在不同的浏览器中表现一致,同时还优化了性能。尽管需要学习新的模式和命名约定,但这些设计决策最终使得在React中处理事件变得更加高效和直观。

四 React 组件中怎么做事件代理?它的原理是什么?

在React中进行事件代理通常是指利用事件冒泡机制来在一个父级组件上处理多个子组件的事件。在传统的DOM操作中,事件代理是一种常见的技术,用于避免给每个子元素添加事件监听器,而是将事件监听器添加到其共同的父元素上,利用事件冒泡(事件向上传递到父元素)的特性来捕获子元素的事件。

React中的事件代理

React自身实际上在内部已经在顶层使用了事件代理。当你在React组件中添加像onClick这样的事件处理器时,React并不会将事件处理器直接绑定到相应的元素本身,而是绑定到文档的根节点。当事件发生并冒泡到根节点时,React会根据它的内部映射来确定事件源,并执行相应的处理函数。

如果你想在你的组件中显式地实现事件代理,你可以在父组件上设置一个事件监听器,然后根据事件对象中的信息来确定是哪个子组件触发了事件,并执行相应的逻辑。

示例

假设你有一个列表,并希望在点击列表项时,告知是哪个列表项被点击了:

class List extends React.Component {
  handleClick = (event) => {
    // event.target 会是点击的具体子项
    // 判断逻辑可以基于 event.target 的特定属性,如 data-* 属性
    alert(event.target.getAttribute('data-key'));
  }

  render() {
    return (
      
    {this.props.items.map((item, index) => (
  • {item.text}
  • ))}
); } }

在上述代码中,

    元素上的onClick处理函数就充当了事件委托者的角色。每个
  • 子元素在被点击时,事件会冒泡到
      ,然后被单一的事件监听器捕获和处理。

      原理

      事件代理的工作原理建立在DOM事件的冒泡机制上。当你点击一个DOM元素时,这个事件不仅仅只在这个元素上触发,而是会沿着DOM树向上传递,直到根节点。

      由于React在内部使用了它自己的事件系统来实现跨浏览器一致性,它实质上也采用了类似于事件代理的机制。它在DOM树最顶层维护了一个事件监听器映射,当一个事件发生时,React会使用这个映射来决定调用哪个组件的哪个事件处理函数。这样做的好处是减少了真实DOM上的事件监听器数量,从而优化了性能和内存使用。同时,这也简化了事件处理函数的管理,因为在组件卸载时,React可以保证移除相关的事件处理函数,避免内存泄漏。

      五 React中受控组件和非受控组件是什么? 区别是?

      在React中,表单元素(如