提升React开发质量和可维护性的几个原则

S 单一责任原则 (Single Responsibility Principle)

React 中的 "单一责任原则"(Single Responsibility Principle,SRP)表明,每个组件都应有一个明确的责任或角色。换句话说,React 组件应该只做一件事,而且要做得好。

通过确保每个组件专注于特定任务,该原则有助于保持代码库的整洁和可维护性。例如,如果您有一个按钮组件,那么它的职责应仅限于呈现按钮和处理用户交互。任何无关的任务,如数据获取或状态管理,都应由单独的组件或模块来处理。

通过遵守 SRP,您的 React 代码会变得更有条理、更易于理解、更不易出错,从而提高开发和维护效率。

让我们来看看这段示例代码。

ini 复制代码
const BookList = () => {
  const [books, setBooks] = useState<IBook[]>([]);

  const fetchBooks = async () => {
    try {
      const response = await fetch("https://api.com/books");
      const data = await JSON.parse(response);
      setBooks(data);
    } catch (err: any) {
      console.log(err.message);
    }
  };

  useEffect(() => {
    fetchBooks();
  }, []);

  return (
    <div>
      {books?.map((book) => (
        <Book title={book.title} image={book.image} key={book.id} />
      ))}
    </div>
  );
};

这里的代码看起来非常标准。它的工作就是获取图书数据并进行渲染。

但是,该代码违反了这一原则,因为该代码中渲染和获取数据两个功能,并不是单一的

我们可以做的是简单地将渲染和获取之间的逻辑分开,组件将只接收最终数据,而不关心获取逻辑。

typescript 复制代码
  // useGetBooks.hook.tsx
const useGetBooks = () => {
  // We moved the fetching logic into a custom hook
  const [books, setBooks] = useState<IBook[]>([]);

  const fetchBooks = async () => {
    try {
      const response = await fetch("https://api.com/books");
      const data = await JSON.parse(response);
      setBooks(data);
    } catch (err: any) {
      console.log(err.message);
    }
  };

  useEffect(() => {
    fetchBooks();
  }, []);

  // Return only the final books result
  return { books }
}
javascript 复制代码
// BookList.component.tsx
const BookList = () => {
  // We call the hook and retrieve the books
  const { books } = useGetBooks();

  return (
    <div>
      {books?.map((book) => (
        <Book title={book.title} image={book.image} key={book.id} />
      ))}
    </div>
  );
};

现在,我们虽然把他们拆成了两个文件,但是他们它们各司其职,看起来非常整洁!

开闭原则

开放扩展意味着您应该能够向 React 组件添加新功能或特性,而无需更改其现有代码。

关闭修改一旦设计并实现了 React 组件,您应该尽可能避免直接更改其源代码。

ini 复制代码
const Book = ({ title, image, type, onClickFree, onClickPremium }: IBook) => {
  const handleReadPremium = () => {
    // Some logic
    onClickPremium();
  };

  const handleReadFree = () => {
    // Some logic
    onClickFree();
  };
  return (
    <div>
      <img src={image} />
      <p>{title}</p>
      {type === "Premium" && (
        <button onClick={handleReadPremium}>Add to cart +</button>
      )}
      {type === "Free" && <button onClick={handleReadFree}>Read</button>}
    </div>
  );
};

上面的代码违反了这一原则,因为需要进行类型检查来呈现特定的功能。

如果有新的书籍类型,那么这个组件也会有进一步的修改。我们看看该怎么解决它吧!

javascript 复制代码
const Book = ({ title, image, children }: IBook) => {
  return (
    <div>
      <img src={image} />
      <p>{title}</p>
      {children}
    </div>
  );
};

我们删除了不必要的代码并创建了一个新的 props,这样children其他组件就可以通过将其作为子组件传递来扩展该组件。

javascript 复制代码
// We extend Book component to create a new component
const PremiumBook = ({ title, image, onClick }: IBook) => {
  return (
    <Book title={title} image={image}>
      <button onClick={onClick}>Add to cart +</button>
    </Book>
  );
};

const FreeBook = ({ title, image, onClick }: IBook) => {
  return (
    <Book title={title} image={image}>
      <button onClick={onClick}>Read</button>
    </Book>
  );
};

这样,您的 Book 组件将开放 用于扩展,关闭用于修改

L(里氏替换原理)

在 React 中,里氏替换原则 (LSP) 强调了允许子组件无缝替换其父组件同时保持相同的界面和功能的重要性。这使得开发人员能够通过替换组件来构建复杂的用户界面,从而提高代码的可重用性和可维护性。

通过遵守 React 中的 LSP,开发人员创建了可互换组件的层次结构。这意味着基础组件及其派生组件可以在不影响应用程序核心功能的情况下进行交换,从而简化开发、增强代码可读性并促进更好地理解组件行为。从本质上讲,React 中的 Liskov Substitution 鼓励创建灵活且有凝聚力的组件结构,以构建健壮且可维护的用户界面。

实际实现这个并不像解释的那么复杂。让我们看接下来的代码吧。

javascript 复制代码
const DangerButton = () => {
  return <div>Danger</div>;
};

我们要创建一个 DangerButton 组件,但按钮功能不能由 div 代替,这违反了原则。

我们应该做的是像这样返回一个按钮

javascript 复制代码
const DangerButton = () => {
  return <button>Danger</button>;
};

这看起来确实比第一个好一点,但还不够。我们还需要继承按钮本身的所有功能。

typescript 复制代码
interface IDangerButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  children: ReactNode
  // Your extra props if you have
}

const DangerButton = ({ children, ...props }: IDangerButtonProps) => {
  return <button {...props} className="danger">{children}</button>;
};

现在我们已经继承了按钮的所有属性,并将这些属性传递给新按钮。这样,仍然可以使用 DangerButton 的任何实例来代替 Button 的实例,而无需更改程序的行为并遵守里氏替换原则。

I(接口隔离原则)

在 React 中,接口隔离原则 (ISP) 强调了精确创建组件和最小接口的重要性。它鼓励开发人员设计适合特定需求的组件接口,避免不必要的过度开发。

随着组件变得更小、更集中,这种方法会产生更加模块化、可维护和可重用的代码,从而提高 React 应用程序的可读性和可扩展性。ISP 通过倡导精益和专业化的组件接口来鼓励更清洁、高效的代码库。

javascript 复制代码
const Book = ({ book }) => {
  return (
    <div>
      <img src={book.image} alt="Book image" />
      <p>{book.title}</p>
      <p>{book.author}</p>
    </div>
  );
};

我们有一个书籍组件,只需要书籍props中的少量数据,即图像、标题和作者。如果将书作为一个props,我们最终会提供超出组件实际需要的内容,因为书本道具本身可能包含组件不需要的数据

为了解决这个问题,我们可以将 props 限制为仅组件需要的。

javascript 复制代码
const Book = ({ image, title, author }) => {
  return (
    <div>
      <img src={image} alt="Book image" />
      <p>{title}</p>
      <p>{author}</p>
    </div>
  );
};

通过这样做,我们应用了 ISP 原则,从而避免我们组件的客户端依赖于他们不使用的接口。

相关推荐
光影少年1 天前
vite打包优化有哪些
前端·vite·掘金·金石计划
光影少年3 天前
webpack打包优化
webpack·掘金·金石计划·前端工程化
光影少年4 天前
Typescript工具类型
前端·typescript·掘金·金石计划
光影少年10 天前
Promise状态和方法都有哪些,以及实现原理
javascript·promise·掘金·金石计划
光影少年10 天前
next.js和nuxt与普通csr区别
nuxt.js·掘金·金石计划·next.js
光影少年10 天前
js异步解决方案以及实现原理
前端·javascript·掘金·金石计划
光影少年14 天前
前端上传切片优化以及实现
前端·javascript·掘金·金石计划
ZTStory17 天前
JS 处理生僻字字符 sm4 加密后 Java 解密字符乱码问题
javascript·掘金·金石计划
光影少年17 天前
webpack打包优化都有哪些
前端·webpack·掘金·金石计划
冯志浩18 天前
Harmony Next - 手势的使用(二)
harmonyos·掘金·金石计划