17
头图

Hello everyone, I'm Casson.

React One of the great advantages of the technology stack is that the community is prosperous, and you can basically find the corresponding open source library for the functions you need to implement in your business.

But prosperity also has a downside - there are so many options to achieve the same function, which one to choose?

This article will introduce a 12.7k open source project - Bulletproof React

This project provides advice on all aspects of building a clean, powerful, and extensible front-end project architecture .

Welcome to join the human high-quality front-end framework group , with flying

What is Bulletproof React

Bulletproof React is different from our common scaffolding (such as CRA ), which is used to create a new project based on the template .

The former contains a complete React full-stack forum project:

用户登录页面

The author uses this project as an example to show 13 aspects related to the project architecture , such as:

  • How to organize file directories
  • What is the recommendation for engineering configuration?
  • How to standardize when writing business components
  • How to do state management
  • API How layers are designed
  • and many more......

Due to limited space, this article introduces some of them.

Do you agree with these views?

How file directories are organized

The project recommends the following directory format:

 src
|
+-- assets            # 静态资源
|
+-- components        # 公共组件
|
+-- config            # 全局配置
|
+-- features          # 特性
|
+-- hooks             # 公用hooks
|
+-- lib               # 二次导出的第三方库
|
+-- providers         # 应用中所有providers
|
+-- routes            # 路由配置
|
+-- stores            # 全局状态stores
|
+-- test              # 测试工具、mock服务器
|
+-- types             # 全局类型文件
|
+-- utils             # 通用工具函数

Among them, the difference between the features directory and the components directory is:

components stores global public components, and features stores business-related features .

For example, if I want to develop a comment module, comment is a feature, and all content related to it exists in the features/comments directory.

An input box is required in the comment module. The general component of the input box comes from the components directory.

All feature-related content will converge to the features directory, including:

 src/features/xxx-feature
|
+-- api         # 与特性相关的请求
|
+-- assets      # 与特性相关的静态资源
|
+-- components  # 与特性相关的组件
|
+-- hooks       # 与特性相关的hooks
|
+-- routes      # 与特性相关的路由
|
+-- stores      # 与特性相关的状态stores
|
+-- types       # 与特性相关的类型申明
|
+-- utils       # 与特性相关的工具函数
|
+-- index.ts    # 入口

All content exported by the feature can only be called through a unified entry, such as:

 import { CommentBar } from "@/features/comments"

instead of:

 import { CommentBar } from "@/features/comments/components/CommentBar

This can be achieved by configuring ESLint :

 {
  rules: {
    'no-restricted-imports': [
      'error',
      {
        patterns: ['@/features/*/*'],
      },
    ],
    // ...其他配置
  }
}

Compared with storing feature-related content in the global directory in a flat form (for example, storing feature hooks in the global hooks directory), using the features directory as a collection of related codes can effectively prevent project volume The situation where the code organization is chaotic after the increase.

How to do state management

Not all states in the project need to be stored in a centralized store , and need to be treated differently according to the state type.

component status

For the local state of a component, if only the component itself and its descendant components need this part of the state, you can save them with useState or useReducer .

application status

States related to application interaction, such as opening pop-up windows , notifications , changing night mode , etc., should follow the principle of keeping the state as close as possible to the component that uses it , and not define any state as a global state .

Taking the example project in Bulletproof React as an example, first define the notification-related status :

 // bulletproof-react/src/stores/notifications.ts
export const useNotificationStore = create<NotificationsStore>((set) => ({
  notifications: [],
  addNotification: (notification) =>
    set((state) => ({
      notifications: [...state.notifications, { id: nanoid(), ...notification }],
    })),
  dismissNotification: (id) =>
    set((state) => ({
      notifications: state.notifications.filter((notification) => notification.id !== id),
    })),
}));

Then reference useNotificationStore anywhere notification-related state is used, for example:

 // bulletproof-react/src/components/Notifications/Notifications.tsx
import { useNotificationStore } from '@/stores/notifications';

import { Notification } from './Notification';

export const Notifications = () => {
  const { notifications, dismissNotification } = useNotificationStore();

  return (
    <div
    >
      {notifications.map((notification) => (
        <Notification
          key={notification.id}
          notification={notification}
          onDismiss={dismissNotification}
        />
      ))}
    </div>
  );
};

The state management tool used here is zustand , and there are many other options:

  • context + hooks
  • redux + redux toolkit
  • mobx
  • constate
  • jotai
  • recoil
  • xstate

These schemes have their own characteristics, but they are all designed to handle application state .

server cache status

For the data requested from the server and cached in the front-end, although the above tools for processing application status can be used, the server-side cache status , compared with the application status , also involves issues such as cache invalidation and serialized data .

So it is best to use special tools to deal with, such as:

  • react-query - REST + GraphQL
  • swr - REST + GraphQL
  • apollo client - GraphQL
  • urql - GraphQl

form state

Form data needs to be distinguished from controlled and uncontrolled , and the form itself still has a lot of logic to process (such as form validation ), so it is also recommended to use a special library to handle this part of the state, such as:

  • React Hook Form
  • Formik
  • React Final Form

URL status

URL Status includes:

  • url params (/app/${dynamicParam})
  • query params (/app?dynamicParam=1)

This part of the state is usually handled by the routing library, such as react-router-dom .

Summarize

This article has selected some of the recommended solutions in Bulletproof React . Do you have any opinions that you agree with?

Welcome to exchange best practices in project architecture in the comments section.


卡颂
3.1k 声望16.7k 粉丝