前端项目规范

130 阅读4分钟

本文主要分为两部分,一部分是项目文件结构、一部分是编码规范。

项目文件结构 文件和文件夹的命名规则 字母全部小写,单词间用小短线-连接

auto-complete/ date-picker.tsx React UI 组件文件名后缀为tsx

button.tsx menu.tsx 如果一个文件夹下有多个同类型文件,则文件名应该是复数:

components pages index.ts 只能作为导出文件使用

不然搜索文件时需要关注文件夹名,增加了心智负担。

//index.ts export * from './my-component'; export { default } from './my-component'; 项目结构划分的方式 按功能划分

把一个功能相关联的文件都放在一起:

common/ Avatar.js Avatar.css APIUtils.js APIUtils.test.js feed/ index.js Feed.js Feed.css FeedStory.js FeedStory.test.js FeedAPI.js profile/ index.js Profile.js ProfileHeader.js ProfileHeader.css ProfileAPI.js 按功能划分结构很清晰,一个功能的代码都在一起,要删除一个功能也不用到处去找相关的文件。但是,一个功能的定义因人而异,有时一个功能的定义非常模糊,拆分起来非常困难,比如要实现一个搜索订单的功能,是应该属于搜索还是属于订单?

按文件类型划分

将同一类型的文件放一起,比如所有的 component,所有的 api:

apis/ APIUtils.js ProfileAPI.js UserAPI.js components/ Avatar.js Feed.js FeedStory.js Profile.js ProfileHeader.js 按类型放一起的好处在于,不用再纠结该放哪的问题,组件都放 components 就好了,api 都放 apis 下好了,当然这失去了按功能组织的优点,因为相关文件都散落在各处。

这两种项目结构并没有绝对的好坏之分。通常,随着项目扩大,一般都是混用这两种类型。之所以有结构,更多的是一种约定,开发者共同遵守这一约定即可。

项目结构划分 项目结构划分个人更倾向于以按功能划分为主,类型划分为辅的混合方式。 目录大概分为以下类型:

project/ ├── docs/ ├── scripts/ ├── .prettierrc ├── tsconfig.json └── src/ docs 项目相关的文档文件。

docs/ ├── dev.md └── architecture.md scripts 项目的一些工具脚本文件。

scripts/ ├── compile-message.sh └── generate-pro-env.ts .prettierrc prettier 配置文件,如果不配置,可能导致不同开发人员保存时格式化的结果不一致。

//.prettierrc { "semi": true, "singleQuote": true, "printWidth": 100, "trailingComma": "all" } tsconfig.json TypeScript 配置文件。

//tsconfig.json { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "baseUrl": "." //支持通过绝对路径(如src/)引用组件 }, "include": ["src", "scripts"] //src、scripts两个文件夹都接受ts检查 } src src 文件夹为项目的主要文件夹,主要有以下文件:

src/ ├── pages/ ├── locales/ ├── fetcher/ (可能有) ├── components/ (可能有) ├── hooks/ (可能有) ├── utils/ (可能有) ├── features/ ├── store.ts (可能有) ├── routes.tsx (可能有) ├── app.tsx └── index.tsx pages

页面文件,pages 下为各个页面的文件,每个页面都有一个路由,主页命名为 index.tsx。

pages/ ├── login.tsx ├── about.tsx └── index.tsx 页面应由 feature 组合而成,而不应该包含显示的逻辑(逻辑在子组件中实现),只是一个函数式组件:

//pages/login.tsx export const LoginPage = () => { return ( //LoginForm包含了登录逻辑 ); }; locales

多语言文件,一般有中英文的 JSON 文件。

locales/ ├── en.json │ zh.json └── index.ts //导出多语言文件

/分割线**/ // index.ts import zh from './zh.json'; import en from './en.json';

export const getLocale = (type: string) => ({ zh, en }[type]);

fetcher

处理 api 请求权限,配置等相关的文件,如果数据请求无需统一配置,也没有权限问题,则不创建该文件夹。

fetcher/ ├── fetcher.ts └── index.tsx /分割线**/ // fetcher.ts import A, { AxiosInstance, AxiosError } from 'axios';

export const STRAPI_CMS_HOST = process.env.REACT_APP_STRAPI_CMS_URL || '';

class Fetcher { private _axios?: AxiosInstance;

initAxios = (token: string, onUnauthorizedError: () => void) => { this._axios = A.create({ baseURL: STRAPI_CMS_HOST, headers: { authorization: Bearer ${token}, }, }); this._axios.interceptors.response.use(undefined, (error: AxiosError) => { if (error?.response?.status === 401) { onUnauthorizedError(); } else { return Promise.reject(error); } }); };

get axios(): AxiosInstance { if (!this._axios) { throw new Error('It must be init axios'); } return this._axios!; } }

export const fetcher = new Fetcher();

components

通用组件文件,与业务无关,只有在多个 feature 需要复用组件时才存在,如果没有复用的需求则 components 应该挪到对应的 feature 目录。一个文件夹为一个组件(组件内部可能有多个小组件),如果组件只需要一个文件则添加与组件同名文件(不能只用一个 index.tsx),并用 index.ts 导出。如果组件内部有 css 文件则名字与文件夹同名。

components/ ├── button/ │ ├── button.tsx | └── index.ts ├── icon/ │ ├── icon.tsx │ ├── icon.css │ └── index.ts └── index.ts //导出组件 组件的命名采用基于路径的组件命名方式,即根据相对于 components 文件目录的相对路径来命名,名字为大驼峰式,比如components/order/list.tsx,被命名为 OrderList。如果文件名和文件目录名相同,则不需要重复这个名字。比如 components/order/form/form.tsx 会命名为 OrderForm 而不是 OrderFormForm。

components/ ├── order/ │ └── form │ │ ├── form.tsx │ │ └── form.css │ ├── list.tsx │ └── index.ts hooks

自定义的 React Hook 文件,只有在多个功能需要复用 hook 时才存在。一个文件为一个 hook,如果一个 hook 需要多个文件则拆分成更小的 hook,hook 文件以 use 开头。

hooks/ ├── use-mounted └── use-key └── index.ts //导出hook hook 组件的命名方式为 use 开头的小驼峰式。

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {} utils

一些可以复用的工具函数,和业务无关,可以项目