从零开始React系列: React组件类型详解

1,367 阅读5分钟

引言

对于React组件类型, 我相信大家一定有一些疑惑和不清晰的地方。而官方文档和网上的教程大多仅告诉我们如何定义/使用组件, 其中组件的差异和选用的理由并没有细说。本文将介绍React中常见的组件类型,以及它们的特点和使用场景。

React组件类型有哪些?

在React中,组件类型分为两种:函数式组件类组件,具体可以细分为以下几种:

  1. Function Component(函数组件):这是最简单的组件类型,它只是一个接收 props 作为参数并返回 React 元素的 JavaScript 函数。
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

这边介绍另一种函数式组件的写法:

type Props = { name: string };
const Greeting: React.FC<Props> = ({ name }) => {
  return <div>Hello {name}</div>;
}

React.FC 是一个泛型接口,它定义了一个 React 函数组件的类型,这个类型指定了组件接收的 props 类型和组件返回的元素类型。使用 React.FC 可以让我们更方便地定义函数组件,并且 TypeScript 可以在编译时检查我们的组件是否符合类型定义。

  1. Class Component(类组件):这是使用 ES6 类语法定义的组件类型,它可以拥有自己的状态生命周期方法
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}
  1. PureComponent(纯组件):这是一个优化过的类组件,它可以浅比较 props 和 state 的变化,以决定是否需要重新渲染组件。
class Person extends React.PureComponent {
  render() {
    return <p>Name: {this.props.name}</p>;
  }
}
  1. Memo Component(记忆组件):这是一个优化过的函数组件,它可以浅比较 props 的变化,以决定是否需要重新渲染组件。
const MemoizedPerson = React.memo(Person);
  1. Higher-Order Component(高阶组件):这是一个函数,它接收一个组件作为参数并返回一个新的组件。高阶组件可以用来封装公共的逻辑,例如用于授权、日志记录、数据预处理等。
function withLogger(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log(`Component ${WrappedComponent.name} mounted.`);
    }

    componentWillUnmount() {
      console.log(`Component ${WrappedComponent.name} unmounted.`);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

const LoggedCounter = withLogger(Counter);
  1. Render Prop Component(渲染属性组件):这是一个使用 render props 模式实现的组件类型,它通过 props 传递一个函数给子组件,以控制子组件的渲染逻辑。
class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return this.props.render(this.state);
  }
}

const App = () => (
  <MouseTracker render={(mouse) => (
    <p>The mouse position is {mouse.x}, {mouse.y}.</p>
  )} />
);
  1. Hooks(钩子函数):这是 React 16.8 引入的新特性,它可以让函数组件拥有自己的状态和生命周期方法,以及实现其他功能,例如使用上下文、访问 DOM 元素等。
import { useState, useEffect } from 'react'

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    console.log(`Count: ${count}`);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

变种组件的使用场景

PureComponent(纯组件)

使用 PureComponent 可以避免不必要的重新渲染。

这句话比较抽象, 我们举一个场景来说明:

假设我们有一个展示商品信息的组件,它的props包含商品名称、价格、描述等信息,其中价格是一个复杂的对象,包含原价、折扣价、折扣信息等属性。在这种情况下,如果我们使用普通的函数组件来实现这个展示组件,每次父组件重新渲染时,即使商品价格没有改变,由于价格对象的引用地址发生了变化,组件也会重新渲染,造成性能浪费。

import React, { PureComponent } from 'react';

class ProductInfo extends PureComponent {
  render() {
    const { name, price, description } = this.props;

    return (
      <div>
        <h2>{name}</h2>
        <p>{description}</p>
        <h3>Price</h3>
        <p>Original Price: {price.originalPrice}</p>
        <p>Discounted Price: {price.discountedPrice}</p>
        <p>Discount Info: {price.discountInfo}</p>
      </div>
    );
  }
}

export default ProductInfo;

在上面的代码中,我们使用了 PureComponent 来定义展示商品信息的组件。在 PureComponent 中,React 会自动进行浅比较(shallow comparison)来判断是否需要重新渲染组件,如果 props 和 state 的引用地址没有变化,就不会重新渲染组件,从而避免了不必要的性能开销。

Memo Component(记忆组件)

Memo Component(记忆组件)适用于在父组件中传递给子组件的 props 只有部分属性发生变化,而子组件仅仅依赖于这些变化的属性。这种情况下,Memo Component 可以帮助避免不必要的重新渲染,提高应用性能。

假设有一个包含多个联系人的列表,用户可以根据名字或邮箱地址搜索联系人。列表项组件将会根据传入的联系人数据进行展示,并且只有在搜索条件改变时才会重新渲染:

import React, { memo } from 'react';

// 列表项组件
const ContactListItem = memo(({ contact }) => {
  console.log('Rendering ContactListItem', contact.name);
  return (
    <div className="contact">
      <h2>{contact.name}</h2>
      <p>{contact.email}</p>
    </div>
  );
});

// 列表组件
const ContactList = ({ contacts, search }) => {
  console.log('Rendering ContactList');
  const filteredContacts = contacts.filter(contact =>
    contact.name.includes(search) || contact.email.includes(search)
  );
  return (
    <div className="contact-list">
      {filteredContacts.map(contact => (
        <ContactListItem key={contact.id} contact={contact} />
      ))}
    </div>
  );
};

export default ContactList;

在上面的代码中,我们使用了 memo 函数来包装 ContactListItem 组件,这样它就变成了一个记忆组件。当 ContactList 组件的 contactssearch 属性发生改变时,只有被 memo 包装的 ContactListItem 组件会重新渲染,而不是每个列表项都重新渲染。这样可以显著提高应用性能,特别是在列表数据较多的情况下。

总结

React提供了多种类型的组件,让我们可以根据不同的场景和需求选择合适的组件。本文介绍了React中常见的组件类型,以及它们的特点和使用场景。希望本文能够帮助大家更好地理解和使用React。