Design Patterns & React

Design patterns become increasingly important in long-term development processes. Mastering these design patterns will be very helpful for future project work.

Design patterns are a fundamental part of software development, as they provide typical solutions to commonly recurring problems in software design. Rather than providing specific pieces of software, design patterns are merely concepts that can be used to handle recurring themes in an optimized way.

1. Singleton Pattern

Share a single global instance throughout our application.

Typical steps:

  1. Create a new instance.
  2. Use Object.freeze to prevent re-initialization.
  3. Export as default.

2. Proxy Pattern

Intercept and control interactions to target objects.

The proxy pattern is useful for state management. I frequently utilize a proxy store object that assumes all data is stored, and if it is not accessible, an HTTP request is made. When combined with Zustand updates, it works well.

For example, Vue's data management pattern has evolved from using Object.defineProperty to using Proxy/Reflect.

3. Provider Pattern

Make data available to multiple child components.

Compared to passing states through props, this is a better way of managing local (or global) functional states. Compared to Redux, Vuex, Zustand and similar libraries, React/Vue's native state support has better usability. This is worth considering when thinking about the maintainability of a project. Of course, in the implementation of R/V Library, the passing of Providers is also being handled layer by layer, haha.

4. Prototype Pattern

Share properties among many objects of the same type.

Prototype Chain implements Inheritance in JS.

There are multiple ways to implement inheritance in JS, including Parasitic, Theft, Prototype, and Combination. With the publication of ES6's Class, the prototype pattern has become mainstream. Due to JS's dynamic typing, we can directly set the prototype, but it is usually only done during initialization.

5. Container/Presentational Pattern

Enforce separation of concerns by separating the view from the application logic.

Within a single module, separate the view presentation logic and detach it from the component logic.View separation is a great way for refactoring.

6. Observer Pattern

Use observables to notify subscribers when an event occurs.

Such as events in Node.js and listeners in the DOM. The former is more convenient for passing information, while the latter reacts to interactions. Both are widely used

7. Module Pattern

Split up your code into smaller, reusable pieces

Along with AMD and CMD, traditional synchronous methods still have an absolute advantage.With thousands of module splits, asynchronous modules mean millions of requests? After all, "asynchronous" is only used for lazy loading in a large application.

JS Module also represents a module with its own exclusive space, selectively exposed properties, just like a class or an object.

8. Mixin Pattern

Add functionality to objects or classes without inheritance.

In the past, it was popular in both React and Vue, but it was later abandoned because it was indeed a bit crude and difficult to manage.

9. Mediator/Middleware Pattern

Use a central mediator object to handle communication between components

Usually, classic implementation is a docking mode between the backend service and multiple clients. The interceptors of axios.js also serve a similar function. In Chrome extensions, the background service takes on such a role.

10. HOC Pattern

Pass reusable logic down as props to components throughout your application.

It's like the "styled" behavior in Material UI and similar to the Mixin pattern, but in the form of a component.

I don't really like this pattern. It will be not convenient to maintain when used frequently, and it gives me a nested feeling. Code should not be too complex. Simple and independent code will be easier to maintain.

11. Render Props Pattern

Pass JSX elements to components through props

It's even better to pass it as children. In Headless UI, when you pass your functional component into Listbox.Option, it will automatically pass the Listbox's state into your business component, which is optional. This fits well with JSX's writing style, making it easy to use and dismantle.

12. Hooks Pattern

Use functions to reuse stateful logic among multiple components throughout the app.

React Hooks retain state without relying on instances, and provide a separate space for describing logic, making them the current mainstream choice.

Custom Hooks are completely self-sufficient and only expose the state, making them incredibly useful for modularization.

13. Flyweight Pattern

Reuse existing instances when working with identical objects.

Currently, there is often enough capacity available and there is little need to worry about saving a particular instance. Instead, the focus is on arranging packages to achieve a lightweight and optimal state.

14. Factory Pattern

Use a factory function in order to create objects.

In this era of rich React/Vue/Svelte ecosystems, front-end developers rarely need to create batches of instances or objects by themselves.

However, in the library itself, the factory pattern remains particularly important.

15. Compound Pattern

Create multiple components that work together to perform a single task.

Most of the components of Headless, such as Listbox.Button, Listbox.Options, and Listbox.Option under the Listbox context, can work well together.

Of course, this is more suitable for the library itself, usually in combination with a Provider application.

16. Command Pattern

Decouple methods that execute tasks by sending commands to a commander

It may be more in line with human language, but few frontend projects do it this way.This will result in more costs and output instructions that are not useful for the project.