作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Mudassir Ijaz's profile image

Mudassir Ijaz

Mudassir是一个全栈软件工程师,专门研究JavaScript框架和库. 作为一名高级工程师,他构建了许多React应用程序,并在SAP等全球公司工作过, Bit, Kwanso, and VoicePing.

Previous Role

Senior Software Engineer

Years of Experience

5

Previously At

SAP
Share

React设计模式为软件工程师提供了两个关键优势. First, they offer a convenient way of addressing software development problems with tried-and-tested solutions. 其次,它们极大地简化了以较少耦合创建高度相干模块的过程. In this article, 我详细介绍了最关键的特定于react的设计模式和最佳实践, 并检查React中不同用例的通用设计模式的有用性.

Common React Design Patterns

Though general design patterns can be used in React, React developers have the most to gain from React-specific design patterns. 让我们检查一下要点:高阶组件、提供者、 compound components, and hooks.

Higher-order Components (HOC)

Through props, higher-order components (HOC) provide reusable logic to components. 当我们需要一个带有新UI的现有组件功能时,我们使用HOC.

两个代表一个组件和一个高阶组件的盒子组合在一起,形成一个由具有附加功能的组件组成的盒子.

我们将组件与HOC结合起来以获得期望的结果:与原始组件相比,组件具有额外的功能.

在代码中,我们将组件封装在HOC中,它返回我们想要的组件:

// A simple greeting HOC.
const Greetings = ({ name, ...otherProps }) => 
Hello {name}!
; const greetWithName = (BaseComponent) => (props) => ( ); const Enhanced = greetWithName(Greetings)

HOCs can contain any logic; from an architectural standpoint, they are common in Redux.

Provider Design Pattern

Using the provider design pattern,我们可以防止应用程序钻取道具或向树中的嵌套组件发送道具. We can achieve this pattern with the Context API available in React:

import React, { createContext, useContext } from 'react';

export const BookContext = createContext();

export default function App() {
 return (
   
     
   
 )
}

function Book() {
 const bookValue = useContext(BookContext);
 return 

{bookValue}

; }

提供者模式的代码示例演示了如何使用上下文直接将props传递给新创建的对象. Context includes both a provider and consumer of the state; in this example, 我们的提供者是一个应用组件,我们的消费者是一个使用BookContext的图书组件. Here is a visual representation:

Two sets of four boxes with each set labeled A through D. 无上下文集显示从A到B、B到C、B到C、C到D传递道具. The With Context set passes props directly from A to D.

将道具直接从组件A传递到组件D意味着我们正在使用提供者设计模式. 如果没有这种模式,就会发生支柱钻井,B和C作为中间组件.

Compound Components

Compound components 是一组相互补充并协同工作的相关部分的集合吗. A basic example of this design pattern is a card component and its various elements.

由三个矩形组成的卡片组件,代表标签为card的元素.Image, Card.Actions, and Card.Content.

The card component is comprised of its image, actions, and content, which jointly provide its functionality:

import React from 'react';

const Card = ({ children }) => {
  return 
{children}
; }; const CardImage = ({ src, alt }) => { return {alt}; }; const CardContent = ({ children }) => { return
{children}
; }; const CardActions = ({ children }) => { return
{children}
; }; const CompoundCard = () => { return (

Card Title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, 我不认为这是一种暂时的意外,但我认为这是一种巨大的意外.

); }; export default CompoundCard;

复合组件的API提供了一种方便的方式来表达组件之间的连接.

Hooks

React hooks 允许我们管理组件的状态和生命周期过程. They were introduced in early 2019,但是在React版本16中提供了许多额外的钩子.8. Examples of hooks include state, effect, and custom hooks.

React’s state hook (useState) is composed of two elements, 当前值和一个在需要时更新该值的函数, depending on the state:

const [data, setData] = React.useState(initialData);

Let’s examine the state hook with a more detailed example:

import React, { useState } from "react";

 export default function StateInput() {
   const [input, setInput] = useState("");

   const inputHandler = (e) => {
     setInput(e.target.value)
   }

   return (
     
   );
 }

We declare a state with an empty current value ("") and can update its value using the onChange handler.

Class-based components also contain effect hooks (useEffect). The useEffect hook’s functionalities 类似于React之前使用的生命周期方法: componentDidMount, componentWillMount, and componentDidUpdate.

Proficient React developers have likely mastered hooks, HOCs, providers, and compound components; however, 最好的工程师也具备通用的设计模式, such as proxies and singletons, and recognize when to use them in React.

An Introduction to General Design Patterns in React

通用设计模式可以与任何语言或框架一起使用, 不考虑系统需求的任何潜在变化, making the entire system simpler to comprehend and maintain. Additionally, 在讨论系统设计时,使用设计模式可以提高设计者与设计者之间沟通的有效性, 软件专家可以参考用于解决某个问题的模式的名称, 让他们的同伴在他们的脑海中立即想象出高层次的设计.

There are three main categories of design patterns:

  • Creational
  • Structural
  • Behavioral

These patterns are useful in the context of React, but since they’re used in JavaScript programming in general, this knowledge is conveniently transferrable.

Creational Design Patterns in React

创建型设计模式旨在创建适用于各种情况的对象, allowing for more flexibility and reusability.

Builder Design Pattern

The builder design pattern 通过为我们提供要遵循的步骤来简化对象创建, and returning the result of the combined steps:

 const BuildingHouse = ({someProps}) => {
  const [constructHouse, setConstructHouse] = useState({});
  const completingArchitectureWork = () => {
    // Add logic to modify the state of house.
  };
  const completingGrayStructure = () => {
    // Some logic ...
  };
  const completingInteriorDesign = () => {
    // Add some more logic ...
  };
  const completingFinishingWork = () => {
    // Some other logic ...
  };

  //返回一个状态对象的所有更新状态.
  // Passing it as props on child component.
  return (
    
  );
}

构建器模式将复杂对象的生成与其表示分离开来, 允许使用相同的构造方法进行替代表示.

Singleton Design Pattern

The singleton design pattern 是否有一种方法定义一个类,使得只有一个对象可以从它实例化. For example, 我们可以使用singleton来确保当用户从不同的登录方法中选择时只创建一个身份验证实例:

auth组件根据验证类型分为三个新组件:GoogleAuth, AppleAuth, and FacebookAuth.

Suppose we have an AuthComponent along with its singleton method authInstance 它传输类型并根据类型呈现状态更改. We could have an authInstance 三个组件告诉我们是否应该呈现Google, Apple, or Facebook authentication components:

function AuthComponent({ authType }) {
    const [currentAuth, setCurrentAuth] = useState();

    const authInstance = () => {
        if (authType === 'google') {
            setAuth('google-authenticator')
        } else if (authType === 'apple') {
            setAuth('apple-authenticator')
        } else if (authType === 'facebook') {
            setAuth('facebook-authenticator')
        } else {
            // Do some extra logic.
        }
    }

    useEffect(()=>{
     authInstance()
    },[authType])

    return (
        
{currentAuth === 'google-authenticator' ? : currentAuth === 'apple-authenticator' ? : currentAuth === 'facebook-authenticator' ? : null}
) } function AuthInstanceUsage() { return }

一个类应该有一个实例和一个全局入口点. 只有在满足以下三个条件时,才能雇用单身人士:

  • 无法分配单个实例的逻辑所有权.
  • Lazy initialization of an object is considered.
  • Global access to any instance is not needed.

延迟初始化或延迟对象初始化是一种性能改进技术,在这种技术中,我们可以等到实际需要时再创建对象.

Factory Design Pattern

The factory design pattern 是使用当我们有一个超类与多个子类,需要根据输入返回一个子类. 这种模式将类实例化的责任从客户端程序转移到工厂类.

您可以使用工厂模式简化生产对象的过程. 假设我们有一个汽车组件,它可以通过改变组件的行为来进一步定制为任何子汽车组件. 我们在工厂模式中看到了多态性和接口的使用,因为我们必须在运行时创建对象(不同的汽车) .

工厂根据计算出来的类型、品牌、型号和颜色等道具生产XCar或YCar.

In the code sample below, we can see abstract cars with props carModel, brandName, and color. The factory is named CarFactory但它有一些基于品牌命名条件的类别. The XCar (say, Toyota) brand will create its own car with specific features, but it still falls into the CarFactory abstraction. We can even define the color, trim level, 并且发动机排量适用于不同车型和类型内相同的车型 Car factory component.

我们已经将继承实现为所使用的类组件的蓝图. 在本例中,我们通过提供props来创建不同的对象 Car objects. 多态也会发生,因为代码决定了每个产品的品牌和型号 Car 对象,根据不同场景中提供的类型:

const CarFactoryComponent = (carModel, brandName, color) => {
   
} const ToyotaCamry = () => { } const FordFiesta = () => { }

工厂方法通常由体系结构框架指定,然后由框架的用户实现.

Structural Design Patterns in React

结构设计模式可以帮助React开发人员定义各种组件之间的关系, 允许他们对组件进行分组并简化更大的结构.

Facade Design Pattern

The facade design pattern 旨在通过创建单个API简化与多个组件的交互. 隐藏底层交互使代码更具可读性. facade模式还可以帮助将通用功能分组到更具体的上下文中, 对于具有交互模式的复杂系统特别有用.

支持服务的图标分为三个框:账单、票务和订单.

一个例子是具有多重职责的支持部门, such as verifying whether or not an item was billed, a support ticket was received, or an order was placed.

Suppose we have an API that contains get, post, and delete methods:

class FacadeAPI {
   constructor() { ... }
  
   get() { ... }
   post() { ... }
   delete() { ... }
}

Now we’ll finish implementing this facade pattern example:

import { useState, useEffect } from 'react';

const Facade = () => {
   const [data, setData] = useState([]);

   useEffect(()=>{
       // Get data from API.
       const response = axios.get('/getData');
       setData(response.data)
   }, [])

   // Posting data.
   const addData = (newData) => {
       setData([...data, newData]);
   }

   // Using remove/delete API.
   const removeData = (dataId) =>  { 
      // ...logic here...
   }

   return (
       
{data.map(item=>{ <>

{item.id}

})}
); }; export default Facade;

请注意facade模式的一个重要限制:客户群的子集需要一个流线型接口来实现复杂子系统的整体功能.

Decorator Design Pattern

The decorator design pattern uses layered, 包装对象以向现有对象添加行为而不修改其内部工作方式. This way a component can be layered or wrapped by an infinite number of components; all outer components can change their behavior instantly but the base component’s behavior doesn’t change. 基组件是一个纯函数,只返回一个没有副作用的新组件.

A HOC is an example of this pattern. (The best use case for decorator design patterns is memo, but that is not covered here as there are many good examples available online.)

Let’s explore decorator patterns in React:

export function canFly({ targetAnimal }) {
    if (targetAnimal) {
        targetAnimal.fly = true;
    }
}

// Example 1.
@canFly()
//我们可以在这里为任何类或功能组件定义一个装饰器列表.
class Eagle(){
    // ...logic here...
}

// Example 2
const Eagle = () => {
    @canFly()
        function eagleCanFly() {
        // ...logic here...
    }
}

In this example, canFly 是否有一种方法可以在任何地方使用而没有任何副作用. We can define decorators on top of any class component, 或者我们可以在类或功能组件中声明的函数上使用它们.

装饰器是一种强大的代码设计模式,它允许你编写更简洁、更易于维护的React组件, but I still prefer HOCs over class decorators. Because decorators are still an ECMAScript proposal, they may change over time; therefore, use them with caution.

Bridge Design Pattern

The bridge design pattern 在任何前端应用程序中都非常强大,因为它将抽象与实现分离,因此两者可以独立更改.

当需要绑定运行时实现时,我们使用桥接设计模式, 由于耦合接口和众多实现而导致类的激增, want to share an implementation among multiple objects, or when we need to map orthogonal class hierarchies.

让我们来观察桥接模式是如何与这些电视和控制器示例一起工作的:

电视1、电视2和电视3位于顶部(实现),在标记为桥的线上方. 桥接线下的远程1、远程2和远程3被标记为抽象.

Suppose each TV and remote are a different brand. Each remote would be referenced to its proprietary brand. A Samsung TV would have to be referenced to a Samsung remote; a Sony remote would not work with it because even though it contains similar buttons (e.g.(打开、关闭、通道向上和通道向下),其实现是不同的.

// Just a path to remotes.
import { remote1, remote2, remote3 } from "./generic-abstraction";
// Just a path to TVs.
import { TV1, TV2, TV3 } from "./implementation-of-abstraction";

// This function is a bridge of all these remotes and TVs.
const BridgeTV = () => {
  //一些状态计算遥控器的类型,这样我们就可以返回电视类型.
  return (
     : remote2 ?  : remote3 ?  : null
      }
    />
  );
};

In the bridge design pattern, 我们必须记住,参考应该是正确的,并反映正确的变化.

Proxy Design Pattern

The proxy design pattern 使用代理,该代理在访问对象时充当代理或占位符. 代理的一个日常例子是信用卡,它代表银行账户中的现金或钱.

一个标有“支付”的收银机,上面有两个支付选项图标:信用卡(标记为代理)和现金(标记为真实对象),由一个箭头连接,表示信用卡是现金的代理.

让我们看看这个模式的实际情况,并编写一个类似的示例,其中我们转移资金和a payment application checks the available balance in our bank account:

const thirdPartyAPI = (accountId) => { ... }

// The proxy function.
const checkBalance = accountId => {
  return new Promise(resolve => {
      // Some conditions.
      thirdPartyAPI(accountId).then((data) => { ... });
  });
}
// Test run on proxy function.
transferFunds().then(someAccountId => {
  // Using proxy before transferring or money/funds.
  if(checkBalance(someAccountId)) { ... }
}).catch(error=> console.log('Payment failed', error))

在我们的代码中,支付应用程序对账户余额的验证充当代理.

Behavioral Design Patterns in React

行为设计模式关注不同组件之间的通信, 由于React以组件为中心的特性,使得它们非常适合React.

State Design Pattern

The state design pattern 在组件编程中通常用于添加封装(状态)的基本单元. 状态模式的一个例子是通过遥控器改变其行为的电视:

Two TV sets at the top, one is on, one is off (labeled Behavior) are above a line labeled State, and a remote controller labeled Props.

根据遥控器按钮的状态(开或关),电视机的状态也随之改变. 同样,在React中,我们可以根据组件的props或其他条件来改变组件的状态.

When an object’s state changes, its behavior is modified:

// Without state property.


// With state property.

The WithState component acts differently in these code examples, 这取决于我们什么时候提供一个状态道具,什么时候提供 null to it. 所以我们的组件根据我们的输入改变它的状态或行为, 这就是为什么我们称状态设计模式为行为模式.

Command Design Pattern

The command design pattern 是设计干净、解耦系统的优秀模式吗. 此模式允许我们在将来的某个时间点执行业务逻辑. 我特别想关注命令模式,因为我相信它是的根模式 Redux. 让我们看看命令模式如何与Redux reducer一起使用:

const initialState = {
   filter: 'SHOW_ALL',
   arr: []
}
 function commandReducer(state = initialState, action) {
   switch (action.type) {
       case 'SET_FILTER': { ... }
       case 'ADD_TODO': { ... }
       case 'EDIT_TODO': { ... }
       default:
       return state
   }
}

In this example, Redux减速器包括由不同情况触发的多个情况,这些情况返回不同的行为.

Observer Design Pattern

The observer design pattern 允许对象订阅另一个对象的状态更改,并在状态更改时自动接收通知. 这种模式将观察对象与被观察对象解耦,从而促进 modularity and flexibility.

In the Model-View-Controller (MVC) architecture, 观察者模式通常用于将更改从模型传播到视图, 使视图能够观察和显示模型的更新状态,而不需要直接访问模型的内部数据:

const Observer = () => {
    useEffect(() => {
       const someEventFunc = () => { ... }
      
       // Add event listener.
       documentListener('EVENT_TRIGGER_NAME', () => { ... })
  
       return () => {
           // Remove event listener.
           documentListener('EVENT_TRIGGER_NAME', () => { ... })
       }
    }, [])
}

观察者对象通过引入“观察者”和“主体”对象来分配通信, whereas other patterns like the mediator 它的对象封装了其他对象之间的通信. 创建可重用的可观察对象比创建可重用的中介更容易, 但是中介可以使用观察者动态地注册同事并与他们通信.

Strategy Design Pattern

The strategy design pattern 有没有一种方法可以在不改变基本组件的情况下从外部动态地改变某些行为. 它定义了一个算法族,封装了每个算法族,并使它们可以互换. 该策略允许父组件独立于使用它的子组件进行更改. 你可以把抽象放在接口中,把实现细节隐藏在派生类中:

const Strategy = ({ children }) => {
   return 
{children}
; }; const ChildComp = () => { return
ChildComp
; }; } />;

As the open-closed principle is the dominant strategy of object-oriented design, 策略设计模式是一种既符合面向对象原则又能实现运行时灵活性的方法.

Memento Design Pattern

The memento design pattern 捕获和外部化对象的内部状态,以便随后可以在不破坏封装的情况下恢复它. We have the following roles in the memento design pattern:

  • The object that can save itself is the originator.
  • 看守人知道在何种情况下始发人必须拯救和恢复自己.
  • 记忆被保存在一个上锁的盒子里,由看守人照管,由始作俑者书写和阅读.

Let’s learn it by examining a code example. The memento pattern uses the chrome.storage API (我已经删除了它的实现细节)来存储和加载数据. In the following conceptual example, we set data in setState function and load data in getState function:

class Memento {
   // Stores the data.
   setState(){ ... }
   // Loads the data.
   getState() { ... }
}

But the actual use case in React is as follows:

const handler = () => ({
  organizer: () => {
    return getState(); // Organizer.
  },
  careTaker: (circumstance, type) => {
    return type === "B" && circumstance === "CIRCUMSTANCE_A"
      ? {
          condition: "CIRCUMSTANCE_A",
          state: getState().B,
        }
      : {
          condition: "CIRCUMSTANCE_B",
          state: getState().B,
        };
    //
  },
  memory: (param) => {
    const state = {};
    // Logic to update state based on param.
    // Send param as well to memorize the state based on.
    // Circumstances for careTaker function.
    setState({ param, ...state }); // Memories.
  },
});

In this abstract example, we return the getState in the organizer (in handler的返回语句中的两个逻辑分支中的状态子集 careTaker.

Why React Patterns Matter

尽管模式为反复出现的问题提供了久经考验的解决方案, 软件工程师应该在应用任何设计模式之前了解它的优点和缺点.

Engineers routinely use React’s state, hooks, custom hooks, and Context API design patterns, 但是理解和使用我所介绍的React设计模式将加强React开发人员的基本技术技能,并适用于多种语言. Through these general patterns, React工程师被授权描述代码在体系结构上的行为,而不仅仅是使用特定的模式来满足需求或解决单个问题.

Toptal工程博客的编辑团队向 Teimur Gasanov 查看本文中提供的代码示例和其他技术内容.

Understanding the basics

  • What is meant by design patterns?

    在软件开发中,设计模式是对经常出现的问题的可重复的回答.

  • What are software design patterns used for?

    设计模式可以减少开发人员的工作量,并帮助实现代码库中的最佳实践. 它们充当了一个框架,在这个框架上可以构建程序的自定义功能.

  • When should design patterns be used?

    设计模式最适合需要灵活性和快速开发的项目. Every design pattern has its own methodology and use cases. 因此,每个设计模式中使用的策略都是唯一的.

  • What design pattern does React use?

    React relies on different design patterns for different uses. 流行的模式包括高阶组件、提供程序、复合组件和钩子.

Hire a Toptal expert on this topic.
Hire Now
Mudassir Ijaz's profile image
Mudassir Ijaz

Located in Lahore, Punjab, Pakistan

Member since June 23, 2022

About the author

Mudassir是一个全栈软件工程师,专门研究JavaScript框架和库. 作为一名高级工程师,他构建了许多React应用程序,并在SAP等全球公司工作过, Bit, Kwanso, and VoicePing.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previous Role

Senior Software Engineer

Years of Experience

5

Previously At

SAP

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Toptal Developers

Join the Toptal® community.