函数组件代码的组织

如果接触过 React 中的类组件,那么可能会对其中各个处理方法以面向对象的方式组织在一起的代码组织形式印象比较深刻。但是在函数组件中,之前类组件中要实现的功能都被压缩到了一个函数中,这虽然使得定义一个组件所需要的代码量变少了,但是也给代码的清晰组织带来了一定的挑战。

其实在一个函数组件的定义中,对于其中代码的组织可以按照代码所执行的功能来进行分区,一般来说,根据组件中对于不同类型内容的使用顺序,可以将函数组件中的代码划分为数据区、计算数据区、事件处理定义区、副作用区、渲染区五个部分。

以下通过一个比较复杂的示例来展示一下函数组件中各个分区中内容的样子。

// 首先需要引入函数组件中所需要的功能
import { useEffect, useMemo, useState } from "react";

// 如果使用的是Typescript,那么就需要定义函数组件的props类型,
// props类型定义可以使用type,也可以使用interface
type ShelfProps = {
  books: Book[];
};

// 以下是函数组件的定义,可以使用function关键字定义,也可以使用箭头函数定义,
// 但是需要注意要导出组件
export function Shelf(props: ShelfProps) {
  // 数据区
  const [selectedBook, setSelectedBook] = useState<Book | null>(null);

  // 计算数据区
  const bookAmount = useMemo(() => props.books.length, [props.books]);

  // 事件处理定义区
  const select = (book: Book) => {
    setSelectedBook(book);
  };
  const unselectAll = () => {
    setSelectedBook(null);
  };

  // 副作用区
  useEffect(() => {
    setSelectedBook(null);
  }, []);

  // 渲染区
  return (
    <div>
      {props.books.map((book) => (
        <BookDetail book={book} onSelect={select} />
      ))}
      <p>书籍总数:{bookAmount}</p>
      <button onClick={unselectAll}>取消选择</button>
    </div>
  );
}

其实在日常的编码过程中,数据区和计算数据区中的内容常常会混在一起书写,没有特别明显的界限,在这个区域里主要要完成的任务是准备组件中所要使用的各种数据。

事件处理定义区主要用来定义组件中所要用到的各种交互功能,把它排在数据区后面是因为事件处理的落差中往往需要使用到数据区中定义的各种数据,根据先定义再使用的原则,事件处理定义区自然就需要排在数据区后面。

函数式组件的一个特点就是相同的输入应该产生相同的输出,也就是只要函数组件的入参内容是一样的,那么函数组件渲染出来的内容也应该是一样的。但是在整个组件的业务逻辑处理过程中,往往还需要做一些与输出无关或者需要配合数据完成其他动作的事情,这些操作在 React 中统统被称为副作用操作。在代码中最常见的就是使用useEffect引导的语句块。

渲染区就比较好理解了,这里就是组织和形成函数组件输出内容的位置。函数组件渲染输出的内容实际上就是函数的返回值,在函数组件中,返回的内容就是一个 React 节点。

Tip

一个组件中只能返回一个React节点,也就是只能有唯一的一个父级元素。如果需要返回若干平行元素的组合,需要使用之后章节中的Fragment概念。