发布于 2026-01-05 8 阅读
0

React 设计模式(第一部分)

React 设计模式(第一部分)

替代文字

本文最初发表于 bugfender.com:React设计模式(第 1 部分)

经过几个月的 React 学习,我特别关注的一个主题是设计模式。在本文中,我将分享我的主要发现。

注意:有些模式侧重于状态管理概念,但我们可以避免Redux, Mobx使用第三方状态管理工具,因为它们与本文的主题无关。

渲染属性

React 文档非常丰富

“渲染属性”是指一种在 React 组件之间共享代码的技术,它使用一个值为函数的属性。

简单来说,它就是一个带有函数值的 prop。这个函数是一个需要渲染的组件。你可能在以下地方见过它React Router

<Route
  path='/about'
  render={(props) => (
    <About {...props} isLoad={true} />
  )}
/>
Enter fullscreen mode Exit fullscreen mode

这种模式的主要目的是更新同级组件的属性。它使组件更易于重用,并帮助我们更轻松地实现“关注点分离”。

我们以以下场景为例:

  • 我们需要开发一个 Form 组件。
  • 在里面 From 我们有 p 和 input
  • 这 input 是用户输入的内容。
  • 显示 p 用户输入的内容。

我们可以简单地创建类似这样的东西:

import React, { useState } from "react";
export default function Input(props) {
  return (
    <>
      <input
        type="text"
        value={props.value}
        onChange={props.onChange}
      />
    </>
  );
}

export default function Form() {
  const [value, setValue] = useState("");
  return (
    <form>
      <Input onChange={e => setValue(e.target.value)}/>
      <p>{value}</p>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

这种方法存在两个问题:

1. 在这种情况下我们不使用“关注隔膜”的概念,因为 Input 应该控制的是 Value 而不是 Form

2. 我们的组件可重复使用性和灵活性不高。

我们可以重构代码,并像这样使用渲染属性:

import React, { useState } from "react";

function Input(props) {
  const [value, setValue] = useState("");
  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      {props.render && props.render(value)}
    </>
  );
}

export default function Form() {
  return (
    <form>
      <Input render={(value) => <p>{value}</p>} />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

这样,Input组件就能控制值,而且更具可重用性(可以用不同的元素实现相同的功能)。

HOC - 高阶组件

Higher-Order Components本质上,它是一个函数,接收一个组件作为参数,并返回一个包含特定业务逻辑的新组件。你可能在 Redux 中见过它:

export default connect(mapStateToProps , mapDispatchToProps)(From);
Enter fullscreen mode Exit fullscreen mode

借助此功能Higher-Order Components,您可以为应用程序的公共(全局)函数编写单独的功能,并在项目中的不同组件上重用它。

我们来看另一个场景:

  • 我们需要开发两个 menu 组件。
  • 在第一个组件中,我们有一个 button 需要阻止菜单点击事件的组件。
  • 第二个组件也是一个 button,但这次我们需要 处理 菜单点击事件。

问题是我们需要两种菜单——一种带有stopPropagation功能,另一种不带功能。

我们可以Higher-Order Components这样使用:

import React from "react";
import "./style.css";

function stopPropagation(WrappedComponent) {
  return function(){
    const handleClick = event => {
      event.stopPropagation();
      WrappedComponent.handleClick()
    };
     return <WrappedComponent onClick={handleClick} />;
  }
}

function Button(props){
  const handleClick = () => console.log("button clicked!");
  Button.handleClick = handleClick; 
  return <button onClick={props.onClick || handleClick}>Click Me</button>;
}

function Menu(props) {
  const openMenu = () => console.log("menu opened!");
  return (
    <div onClick={openMenu} className="menu">
      <h1>Menu</h1>
      {props.children}
    </div>
  );
}

export default function App() {
  const ButtonPropagation = stopPropagation(Button);
  return (
    <div>
      <Menu>
        <ButtonPropagation />
      </Menu>
      <Menu>
        <Button />
      </Menu>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

演示链接

让我们来分析一下这段代码:

  • App 组件读取我们提到的两个值 Menus 。
  • Menu 组件读取标题和子元素(在本例中为 Button)。
  • Button 有一个按钮元素,带有点击事件。 **handleClick 这是该事件的基本功能。** 我们需要使用导出此函数 Button.handleClick= handleClick (在类组件中,您可以使用  static)。
  • 这 stopPropagation 是高阶组件。它接收一个组件(Button 在我们的例子中),并将具有新能力的组件(在我们的例子中 stopPropagation)发送回去。

这是一个简单的用法示例Higher-Order Components。我们可以直接使用它stopPropagation,而无需在不同的组件中重复编写代码。更重要的是,我们还可以创建其他“按钮”高阶组件,例如 preventDefault 和 queueClick。

好了,文章第一部分就到这里。在第二部分,我将讨论Context模式、Presentational and Container Components模式和compound components模式。

感谢阅读。希望您喜欢这篇教程并有所收获。如果您有任何补充,请留言。如果您想了解更多信息,以下是一些关于该主题的优秀资源:

文章来源:https://dev.to/tomeraitz/react-design-patterns-part-1-aj1