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 normalJavaScript
function; -
Hooks
: use a special function calledHooks
to manage state and otherReact
features in theFunction
component; -
Class
component: define the component using theclass
keyword and extendReact.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.