SOLID implements in React Project

247 阅读3分钟

Introduction

SOLID is a popular kind of design principles that are used in object oriented programming. SOILD can not only be used in backend which is just like jave or nodejs, but also be used in frontend. We use those principles to design our component and function, to make our codes and business logic cleaner and better for readability, easier to scaled and maintained.

So what‘s the concrete means of SOLID priciples?

SOILD stands for five key design priciples, we should code with conforming each of those. Though. sometimes some of these principles may look similar but they are not targeting the same objective.

Explain SOLID

S (Single Responsibility Priciple)

A class should have a single responsibility, such as User class, Log class and so on...

If a class has many responsibilties, it increasees the possibility of bugs and the complexity of code maintenance, because changing one of its responsibilities could affect the other ones unexpectedly.

This principle attempt to separate behaviours so that if exceptions arouse by changing, it won't influence other irrelevant behaviours.

O (Open-Closed Principle)

Class should be open for extension, but closed for modification

In jave. we typically create a new class by inheriting existing class to implement other diffrent functionalities instead of modifying directly

Make it simple, if we want a Class to peform more functions, the informed approach is to add the functions that already exist, instead of changing them.

This principle aims to extend a Class's behavior without changing the existing behavior of that Class. This is to avoid bugs wherever the Class is used.

L (Liskov Substitution Principle)

The object of Child Class can replace the object of its base Class.

That can cause bugs When a clild Class can't perform the same actions as it's parent Class. It's a child Class that should be able to do everything which it's parent Class can do.

This principle aims to enforce consistency so that the parent Class or its child Class can be used in the same way without any error.

I (Interface Isolation Priciple)

使用多个专门的接口比使用单一总接口要好 Clients should not depend on methods that they do not use at all.

It's wasteful when a Class is required to perform useless actions. that may cause unexpected bugs.

This principle aims to split a set of actions into some smaller sets so that only executing when it is required

D (Dependency Inversion Priciple)

High level modules should not depend on low level ones. Both should depend on abstraction. Abstractions should not depend on details, details should depend on Abstractions. It's a little difficult to understand it.

In a nutshell,we should programe oriented interface. Abstracting into a interface makes the implementation of each class independed of each other and loose coupling.

Overall, Class should not be fused with the tool it uses to execute an action, instead, it should be fused to the interface that will allow the tool to connect to the Class. Both Class and interface should not know how the tool works. However, the tool needs to meet the spicification of the interface.

This principle aims to reduce the dependency of high level Class on low level Class by introducing interfaces

SOLID implements in React Project

We are supposed to figure SOLID principle out through the above explaination. So then we are gonnar research how use them in React Project one after another.

S

For instance, let's implement a fitter component of products with fetching daata

image.png

import { useState, useMemo, useEffect, ChangeEvent } from 'react';
import data from '../mock';

type Products = typeof data;

export default function Bad() {
    const [products, setProducts] = useState<Products>([]);
    const [filterFeatured, setFilterFeatured] = useState(true);

    const fetchProducts = async () => {
        const getMockData = (): Promise<Products> => new Promise(resolve => setTimeout(() => resolve(data), 0));
        const response = await getMockData();
        setProducts(response);
    };

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

    const toggleFeatured = (e: ChangeEvent<HTMLSelectElement>) => {
        e.preventDefault();
        setFilterFeatured(e.target.value === '1');
    };

    const filteredProducts = useMemo(
        () => products.filter((product: Products[0]) => product.isFeatured === filterFeatured),
        [products, filterFeatured]
    );

    return (
        <>
            <div>
                <select
                    value={filterFeatured ? '1' : '0'}
                    onChange={toggleFeatured}
                >
                    <option value="0">unfeatured</option>
                    <option value="1">featured</option>
                </select>
            </div>
            <ul>
                {filteredProducts.map(({ id, title, description, date, isFeatured }) => (
                    <li key={id}>
                        <h3>{title}</h3>
                        <p>{description}</p>
                        <p>{date} - {isFeatured ? 'featured' : 'unfeatured'}</p>
                    </li>
                ))}
            </ul>
        </>
    );
}

Abouve bad codes, all funcitons written in a component that does not confirm the single responsibility priciple. Let's split the functions into single. codes like this:

// entrance
import Product from './product';
import Filter, { filterProducts } from './filter';
import { useProducts } from './hooks/useProducts';
import { useFeaturedFitler } from './hooks/useFeaturedFitler';

export default function Good() {
    const { products } = useProducts();
    const { filterFeatured, toggleFeatured } = useFeaturedFitler();

    return (
        <>
            <Filter
                filterFeatured={filterFeatured}
                toggleFeatured={toggleFeatured}
            />
            <ul>
                {filterProducts(products, filterFeatured).map(product => (
                    <Product product={product} />
                ))}
            </ul>
       </>
    );
}
  • hooks

image.pngimage.png

  • product

image.png

  • Fitter

image.png

O

For instance, we are gonnar build a Button component.

image.pngimage.png

Abouve bad codes, we have to modify the codes of the Button component that it comes with new requirements, that's we are not expecting. Therefore, the component should be scale instead of modification. Let's code like this:

image.png image.png

L

For instance, Let's we build a SearchInput component.

image.pngimage.png

I

image.png image.png

That's so bad. The Image component only needs imageUrl from props, but we transport the whole product data. It's not necessary. So we can do it like this:

image.png image.png

D

For instance. Let's build a sign-in Form component.

image.png

It's not appropriate to change the form's codes once we have to change the logic of submit function, that's a problem. What the submit do should be abstract devised and picked out from here, by props to execute from external implementation from out of the component. So we shoud code like this:

image.pngimage.png

Summary

So far, we have knew these five priciples and underline their details. They are to help us make code easy to adjust, extend and test without unexpected problems.

If you want to get codes of this articles. you can visit my github.