In daily team development, components are written with varying quality and styles. Components can be unscalable or difficult to maintain due to many requirements. This results in repetitive functionality of many business components, which can be uncomfortable to use. Let’s talk about designing a more elegant React component from a code structure perspective.
Component directory structure
Good components have a clear directory structure. The directory structure here is divided into project level structure and single component level structure.
Container components/presentation components
Our directory structure in the project can be divided according to component and business coupling, and the less coupling to business, the more reusable. The presentation component focuses only on the presentation layer, can be reused in multiple places, and does not couple the business. Container components focus on business processing, and container components combine presentation components to build a complete view.
Display components | Container components | |
---|---|---|
concerns | UI | business |
The data source | props | State manager |
Component form | Common components | High order component |
Example:
SRC/components/ (generic components, business independent, can be called by all other components) Button/ index.tsx containers/ (container components, deep coupling with business, TSX World/ components/ index.tsx hooks/ (public hooks) pages/ (public hooks) Specific page, non-reuse) my-app/ store/ (state management) services/ (interface definition) utils/ (utility class)Copy the code
Component directory structure
We can divide different directories according to file type/function/responsibility etc.
- According to the file type
images
Such as catalog - According to the file function can be separated
__tests__
、demo
Such as catalog - They can be classified according to document responsibilities
types
、utils
、hooks
Such as catalog - Components can be grouped into categories based on their characteristics
HelloWorld/ (common business component) __tests__/ (Test case) demo/ (component example) Bar/ (unique component classification) kitty.tsx (unique component) kitty.module. less Foo/ hooks/ (custom Utils/(utility class methods) index.tsx (export file)Copy the code
For example, I recently wrote a table component directory structure:
├─SheetTable │ ├─Cell │ ├─Header │ ├─Layer │ ├─Main │ ├─Store │ ├─types │ ├─ utilsCopy the code
Component internal structure
Good sequential logic needs to be maintained within components to unify team specifications. After conventions are established, such a clear definition can make us Review more clearly.
The import order
The import order is node_modules -> @/ starting file -> relative path file -> current component style file
// Import the node_modules dependency
import React from 'react';
// Import public components
import Button from '@/components/Button';
// Import the relative path component
import Foo from './Foo';
// Import the corresponding.less file named styles
import styles from './Kitty.module.less';
Copy the code
useComponent name + Props
Form namedProps
Type and export.
Types are written in the same order as parameters, usually in [a-z] order. Variable comments are not allowed at the end, because it will cause the editor to misidentify and fail to correctly prompt
/** * Type definition (name: component name + Props) */
export interface KittyProps {
/** * multi-line comments (recommended) */
email: string;
// Single line comment (not recommended)
mobile: string;
username: string; // End comment (disable)
}
Copy the code
useReact.FC
define
const Kitty: React.FC<KittyProps> = ({ email, mobile, usename }) = > {};
Copy the code
Generic, code hints are smarter
In the following example, you can use generics to align the types in the value and onChange callbacks and make the editor type smart.
Note: Generic components cannot use the React.FC type
export interface FooProps<Value> {
value: Value;
onChange: (value: Value) = > void;
}
export function Foo<Value extends React.Key> (props: FooProps<Value>) {}
Copy the code
Do not use directlyany
type
Implicitly or explicitly, the use of the any type is not recommended. An argument that defines any can be extremely confusing for people using the component to know exactly what type is in it. We can declare it by generics.
// Implicit any (disallow)
let foo;
function bar(param) {}
// Explicit any (disallow)
let hello: any;
function world(param: any) {}
// Use generic inheritance to narrow the type range (recommended)
function Tom<P extends Record<string.any> > (param: P) {}
Copy the code
Each component corresponds to a style file
The granularity of the component is the unit of abstraction, and the style file should be consistent with the component itself. The cross-introduction of style files is not recommended, as it can lead to a confusing refactoring of how many components are currently using the style.
- Tom.tsx
- Tom.module.less
- Kitty.tsx
- Kitty.module.less
Copy the code
Inline style
Avoid slacking, always be elegant, a handy style={} is not recommended. Not only is there a re-creation cost to each rendering, but there is also clear noise on JSX that affects reading.
Component line limit
Components need to be unambiguously commented and kept to 300 lines of code or less. The number of lines of code can be limited by configuring ESLint (you can skip comments/blank line statistics) :
'max-lines-per-function': [2, { max: 320, skipComments: true, skipBlankLines: true }],
Copy the code
The order in which code is written within a component
The order inside the components is state -> Custom Hooks -> Effects -> Internal Function -> Other Logic -> JSX
/** * Component comments (brief summary) */
const Kitty: React.FC<KittyProps> = ({ email }) = > {
// 1. state
// 2. custom Hooks
// 3. effects
// 4
// 5. Other logic...
return (
<div className={styles.wrapper}>
{email}
<Child />
</div>
);
};
Copy the code
Event functions are named differently
Internal methods are named after handle{Type}{Event}, such as handleNameChange. Expose external methods as on{Type}{Event}, such as onNameChange. The advantage of this is that the function name can be used to distinguish whether it is an external parameter.
For example, the ANTD /Button component fragment:
const handleClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) = > {
const { onClick, disabled } = props;
if (innerLoading || disabled) {
e.preventDefault();
return;
}
(onClick asReact.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)? .(e); };Copy the code
Inherit native elementsprops
define
The native props all inherit from React.HTMLAttributes. Some special elements also extend their attributes, such as InputHTMLAttributes.
We define a custom components will be inherited to the React. InputHTMLAttributes < HTMLInputElement >, make its type has all the characteristics of the input.
export interface KittyProps extends React.InputHTMLAttributes<HTMLInputElement> {
/** * Added support for the return key event */onPressEnter? : React.KeyboardEventHandler<HTMLInputElement>; }function Kitty({ onPressEnter, onKeyUp, ... restProps }: KittyProps) {
function handleKeyUp(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.code.includes('Enter') && onPressEnter) {
onPressEnter(e);
}
if(onKeyUp) { onKeyUp(e); }}return <input onKeyUp={handleKeyUp} {. restProps} / >;
}
Copy the code
Avoid circular dependencies
If you are writing components that contain loop dependencies, consider splitting and designing module files
// --- Foo.tsx ---
import Bar from './Bar';
export interface FooProps {}
export const Foo: React.FC<FooProps> = () = > {};
Foo.Bar = Bar;
// --- Bar.tsx ----
import { FooProps } from './Foo';
Copy the code
The Foo and Bar components above form a simple loop dependency, although it doesn’t cause any runtime problems. The solution is to extract the FooProps into a separate file:
// --- types.ts ---
export interface FooProps {}
// --- Foo.tsx ---
import Bar from './Bar';
import { FooProps } from './types';
export const Foo: React.FC<FooProps> = () = > {};
Foo.Bar = Bar;
// --- Bar.tsx ----
import { FooProps } from './types';
Copy the code
The relative path should not exceed two levels
When the project is complex, the directory structure gets deeper and deeper, and the files get very long.. / path, this looks very inelegant:
import { ButtonProps } from '.. /.. /.. /components/Button';
Copy the code
This can be done in tsconfig.json
"paths": {
"@/*": ["src/*"]
}
Copy the code
And vite
alias: {
'@/': `${path.resolve(process.cwd(), 'src')}/`,
}
Copy the code
Now we can import the module relative to SRC:
import { ButtonProps } from '@/components/Button';
Copy the code
More thoroughly, of course, you can use Monorepo’s project management style to decouple components. With just one set of scaffolding, you can manage (build, test, publish) multiple packages
Don’t use it directlyexport default
Export an unnamed component
Components exported in this way are displayed as Unknown in the React Inspector
// Error
export default() = > {};// The correct way
export default function Kitty() {}
// The correct approach: speak first and export later
function Kitty() {}
export default Kitty;
Copy the code
conclusion
Here are some tips on how to write the React component directory structure and code rules. We’ll look at how to keep your mind elegant.
Articles exported this year
I just started writing in the last two months. I hope I can help you, or I can join groups to learn and progress together.
- 2021 year-end summary, an 8 – year – old front-end where to go
- […undefined] what is the result of the implementation?
- React Boutique component: Mac-ScrollBar
- We migrated from UmiJS to Vite
- Forget useCallback, we have a better way
- How to write more elegant code – JavaScript text
- React Hooks performance optimized for correct posture