How to build maintainable React components elegantly

May 8, 2023 1271hotness 0likes 0comments

in the daily development of the team, people write components of different quality and styles, and there are many ways of naming them. For example, some use tags as component names, but I won't say who they are. Components cannot be extended or difficult to maintain because of many requirements. It is quite difficult to reuse the functions of many business components.

so in today's article we'll talk about how to set up an elegant React component in terms of component design patterns.

basic principles of component design

when designing a high-quality component or a set of high-quality components, we should first follow the following principles, including the following aspects.

single responsibility

the principle of single responsibility is to make a module focus on a Phoenix girl, that is, to minimize the responsibility of a module. If a module has too many functions, it should be divided into multiple modules, which is more conducive to the maintenance of the code.

A single component is easier to maintain and test, but don't abuse it, split components if necessary, minimize granularity at one extreme, and may result in a large number of modules, and module discretization can make the project difficult to manage.

demarcate boundaries

how to split components, if the two components are too closely related to logically clearly define their respective responsibilities, then the two components should not be split. Otherwise, if their respective responsibilities are not clear and the boundaries are not distinct, the problem of logical confusion will arise. Then the most important thing to split the components is to determine the boundary and let each component exert its own unique capabilities through abstract parameter communication.

High cohesion / low coupling

High-quality components should meet the principles of high cohesion and low coupling. For example, in the src directory of the project, you need to distinguish the functions done between each file, such as routers , pages , components , low coupling means to reduce the dependency between different components, so that each component should be independent, that is to say, code is usually written for low coupling. Through the separation of responsibilities and the division of boundaries, complex businesses are decoupled.

unidirectional data flow

in the React search, the data flows in only one direction, that is, from the parent component to the child component. If the data is shared between sibling components, then the data should be stored in the parent component and passed to the child component that needs the data.

data flows from the parent component to the child component, the data update is sent to the parent component, the parent component makes the actual change, and after the parent component makes the change, the updated data is passed to the child component.

what are the ways components are defined in React?

in the React official documentation, the React component is currently defined in the following ways:

  • Function component: define a component using the normal JavaScript function;
  • Hooks : use a special function called Hooks to manage state and other React features in the Function component;
  • Class component: define the component using the class keyword and extend React.component ;

based on the above three basic component definition methods, we can extend a variety of component design patterns.

component design pattern

to distinguish the design patterns of components, we need to make clear the design classification based on scenarios. In the React community, components are divided into two categories. One is that components that only perform presentation, run independently and do not add additional functions are called dumb components, stateless components or presentation components, and the other is that components that deal with business logic and data state are called stateful components and smart components. It must contain at least one smart component or presentation component.

display components are more reusable, and smart components are more focused on the business itself.

display components

display component is just like its name, like a decoration, it is completely controlled by external props . It has strong versatility, high reuse rate, and often has a relatively weak relationship with the current project, and can be used across projects. This is our common component library.

Agent components

proxy component is often used to encapsulate common properties and reduce repetitive code.

for example, when we want to add a button element to the project, we add the type= "button" attribute to the button element:

<button type="button">button</button>;

if we want to use more than one of these button components, it will be very troublesome to write them everywhere. The most common solution is to encapsulate:

import React from "react";
import { Button } from "antd";

const Component: React.FC = rap:any => {
  return (
    <Button size="small" type="primary" {...rap}>
Click the button
</Button>
  );
};

export default Component;

this kind of encapsulation feels a bit superfluous, but it cuts off the strong dependency of external components, and whether it can achieve painless switching in the business if the current component library is not available.

the design pattern of the proxy component solves the above problems very well. In the above example, the proxy component is isolated from antd , which is only the interaction of a component props APi layer. When antd cannot be used, we can quickly switch to another component library bntd .

The

style component is also a proxy component, but there is a breakdown of the processing style area to separate the current concerns from the current component, see the following example:

import React from "react";
import classnames from "classnames";
import { Button as KFC } from "antd";

const Component: React.FC = ({ sing, v50, dance, ...rap }) => {
  return (
    <KFC
      size="small"
      type="primary"
      className={classnames(
        "v50",
        {
          "btn- primary": v50,
          "hight-light": dance,
        },
        sing,
      )}
      {...rap}
    >
Click the button
</KFC>
  );
};

export default Component;

in the above example, pass in different parameters to control whether the button is on or off, and display different styles.

Smart components

Smart components are business-oriented, with richer features, higher complexity, and lower reusability. The presentation component focuses on the characteristics of the component itself, and the smart component focuses on the composition of components.

Container components

Container components have little reusability and are mainly used in to pull data and to combine components .

Container component is concerned with how things work. It may contain both the presentation component and the container component, but except for some outer packages, such as div or Fragment provided by React , they usually do not have any of their own DOM tags and never have any style to provide data and behavior for its presentation component or other container components.

The advantage of

this approach is to separate concerns, and by writing components in this way, you can better understand your application and your UI . You can use the same expressive component with a completely different state source and convert it into a separate container component that can be further reused.

for details, please see the following Demo:

import React, { Fragment, useState, useEffect } from "react";

interface Props {
  card: number[];
}

const Card: React.FC<Props> = ({ card }) => {
  return (
    <div>
      {card.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
    </div>
  );
};

const Container = () => {
  // simulate network request
  const [state, setState] = useState<number[]>([]);
  useEffect(() => {
    setState([1, 2, 3, 4, 5, 6]);
  }, [state]);

  return (
    <Fragment>
      <Card card={state} />
    </Fragment>
  );
};

export default Container;

High-level components

The advanced technology of reusing component logic in

React is a design pattern based on the combined characteristics of React . A high-level component receives a parameter and its return value is a new component, and such a function is called a high-order component.

examples of high-level components

Let's take an example of switching topics to illustrate how high-level components are used. The specific code is as follows:

import React, { Component, createContext } from "react";

interface Context {
  color: string;
  size: number;
}

const ThemeContext = createContext<Context>({ color: "red", size: 60 });

const Layout = () => {
  return (
    <ThemeContext.Consumer>
      {value => {
        return (
          <h1>
Topic: {value.color} size {value.size}
</h1>
        );
      }}
    </ThemeContext.Consumer>
  );
};

class App extends Component {
  render() {
    return (
      <div>
        <ThemeContext.Provider value={{ color: "pink", size: 16 }}>
          <Layout />
        </ThemeContext.Provider>
      </div>
    );
  }
}

export default App;

such a definition method, not only the code is not beautiful, but also difficult to expand, if other pages are used again, it will be troublesome. At this time, we can use high-level components to solve this problem:

import React, { Component, createContext } from "react";

const ThemeContext = createContext({ color: "red", size: 60 });

function hoc(OriginComponent) {
  return class extends React.Component {
    render() {
      return (
        <ThemeContext.Consumer>
          {(value) => {
            return <OriginComponent {...value} />;
          }}
        </ThemeContext.Consumer>
      );
    }
  };
}

class ThemeComponent extends Component {
  render() {
    const { size, color } = this.props;
    return (
      <ThemeContext.Provider value={{ color: "pink", size: 16 }}>
        <h1>
          {color}-{size}
        </h1>
      </ThemeContext.Provider>
    );
  }
}

const Theme = hoc(ThemeComponent);

class App extends Component {
  render() {
    return (
      <ThemeContext.Provider value={{ color: "red", size: 16 }}>
        <Theme />
      </ThemeContext.Provider>
    );
  }
}

export default App;

careful you may have found that the high-level component is actually the implementation of the decorator pattern in React . It enhances the function inside the function by passing a component to the function, and finally returns the component without modifying the passed parameters. This allows you to add new functionality to an existing component without modifying it.

Custom Hooks

in the class era, this Hoc is indeed a very interesting design idea, but now that we have hooks , can we abandon this fearful component construction pattern?

the specific code is as follows:

import React, { useState } from "react";

function useRequest(options) {
  const { url, ...init } = options;
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  // simulate network request
  function loader() {
    console.log(url, init);
    setLoading(true);
    setData([1, 2, 3, 4, 5]);
  }
  // use loader, data and loading as the return values of custom hook
  return { loader, data, loading };
}

const App = () => {
  const { data, loader } = useRequest({
    url: "user",
    method: "GET",
  });
  console.log(data);
  return <div onClick={() => loader()}>theme</div>;
};

export default App;

Summary

there is no summary, there is no best way to design components according to different scenarios and using different design patterns.

InterServer Web Hosting and VPS

Aaron

Hello, my name is Aaron and I am a freelance front-end developer

Comments