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.

  1. According to the file typeimagesSuch as catalog
  2. According to the file function can be separated__tests__demoSuch as catalog
  3. They can be classified according to document responsibilitiestypesutilshooksSuch as catalog
  4. 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 + PropsForm namedPropsType 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.FCdefine

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 directlyanytype

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 elementspropsdefine

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 defaultExport 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