如何让前端项目更好维护

  在工作过程中,我们常常会遇到一些项目,随着时间的推移变得越来越难以维护的情况。经过分析,可以总结出以下几点原因:功能的持续迭代导致代码冗余增多、缺乏统一规范或者团队成员未严格执行规范、项目成员的变动等。

  那么,如何避免或减少这些问题的出现呢?我整理了几个常用的优化技巧:

1、项目结构:

  在项目结构方面,我们可以借鉴单一职责的设计思想。在基于umi架构的目录结构基础上,可以增加一些文件夹和文件,例如common、hooks、types等。当然,这并不是一种固定的规范,在开发过程中,可以根据实际项目情况进行增删和调整。为什么要增加这些内容呢?用专业术语来说,是为了对代码进行精细化管理,避免组件代码无限堆叠。根据不同的职责,将之前写在组件内部的代码拆分到独立的模块中,这样有利于代码的阅读和维护。

├── config  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── 配置文件 路由配置
│   └── routes.ts 
├── mock ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── mock数据
│   └── api.ts
├── src
│   ├── assets ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── 静态文件  图标 图片 等
│   ├── components ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ─ 公共组件
│   ├── layouts  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──  layout布局
│   ├── pages  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── 页面集合
│   │   ├── pageA  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──  ─ ─ 页面
│   │   │   ├── common 
│   │   │   │   ├── constant.ts  ── ── ── ── ── ── ── ── ── ── ── ── ── 常量
│   │   │   │   ├── context.ts   ── ── ── ── ── ── ── ── ── ── ── ── ── context
│   │   │   │   ├── enum.ts ── ── ── ── ── ── ── ── ── ── ── ── ── ── ─ 枚举
│   │   │   │   ├── validator.ts ── ── ── ── ── ── ── ── ── ── ── ── ── 正则表达式、表单校验方法
│   │   │   │   └── utils.ts ── ── ── ── ── ── ── ── ── ── ── ── ── ──  util方法
│   │   │   ├── components  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ─ 组件
│   │   │   ├── hooks  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──  自定义hooks
│   │   │   │   ├── useA.ts
│   │   │   │   └── useB.ts
│   │   │   ├── index.less  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ─ 页面样式
│   │   │   ├── index.tsx  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── 页面入口文件
│   │   │   └── types.ts   ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ts声明
│   ├── services ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──── ── ────  接口
│   │   └── index.ts
│   ├── types.ts ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──── ── ────  全局ts声明
│   └── utils  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──── ── ── ──── utils
│   │   ├── validator.ts  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──── 全局表单校验
│   │   ├── request.ts  ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──  请求拦截器
│   │   └── utils.ts   ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──   通用utils方法
│   └── app.ts
├── package.json
└── tsconfig.json

2、全局封装:

  全局封装在前端架构中扮演着重要的角色。它不仅可以帮助我们减少代码冗余,还可以让开发人员更专注于核心业务逻辑的实现,而不必重复编写基础功能的代码。当需要修改或更新功能时,只需在封装的模块中进行修改,而不需要逐个查找和修改所有使用该功能的地方,极大的提高了开发效率。

// 拦截器
import eventEmit from '@/utils/eventEmit';
// 请求拦截器
const requestInterceptors = (url, options) => {
  if (options.loading) {
    eventEmit.emit('triggerLoading', true);
  }
  return {
    url,
    options: options,
  };
};
// 响应拦截器
const responseInterceptors = async (response, options) => {
  if (options.loading) {
    eventEmit.emit('triggerLoading', false);
  }

  const data = await response.clone().json();

  // 未登录 跳转到登录页面
  if (data?.code === 1000) {
    history.push(LOGIN_PATH);
    return;
  }

  // 在拦截器里面统一校验code 
  if (data.code === 0 ) {
    return data;
  }
  message.error(data.message);
  return Promise.reject(data.message);
};

// 触发loading效果:接口请求的时候增加loading参数
 const getDataSource = (params,loading=true) => {
  return request(url ,{ params,loading })
}

// Loading组件
import React, { useEffect, useRef, useState } from 'react';

import { Spin } from 'antd';

import eventEmit from '@/utils/eventEmit';

import './index.less';

const Loading: React.FC = () => {

  const refCount = useRef(0);
  const [loading, setLoading] = useState<boolean>(false);

  useEffect(() => {
    eventEmit.addListener('triggerLoading', onLoading);
    return () => {
      eventEmit.removeListener('triggerLoading', onLoading);
    };
  }, []);

  const onLoading = (isLoading: boolean) => {
    if (isLoading) {
      refCount.current += 1;
    } else {
      refCount.current -= 1;
    }
    setLoading(!!refCount.current);
  };

  return (
    <Spin className="global-loading"  spinning={loading} />
  );
};
export default Loading;

  上面的代码我们实现了两个功能的封装:全局响应code校验和全局loading。 大部分项目都做了全局响应校验,这块就不展开讲了。唯一要注意的就是不要重复校验(做了全局校验,实际的接口调用时,就不用再做校验)。关于全局loading,实现方式有很多种,以上代码是结合发布订阅模式来实现的。主要有两个优点,一是不用在每个组件内部实现loading逻辑,二是Loading使用了固定定位,所以组件loading状态的改变,不会导致整个页面的重新render和文档流的重绘与重排。使用时只需在全局layout组件导入Loading组件。

3、代码排版:

  良好的代码排版对于代码的可读性和可维护性非常重要。通过对不同职责的代码进行归类和分层,让代码看起来更整洁,视觉体验更好。下面是我个人比较喜欢的一种代码排版方式:

// 1 react
import { FC, useState, useEffect } from 'react';

// 2 第三方npm包 antd umi moment等
import { xxx } from 'antd';
import { history } from 'umi';
import moment from 'moment';

// 3 组件
import { xxx } from '@/components/xxxx'
import { xxx } from '@/components/xxxx'
import { xxxx } from '../../components/xxxxx'
import { abc } from '../../components/abc'

// 4 其他 接口 常量 util方法 等
import { xxx } from '../constant';
import { DetailInfo } from './types';
import { xxx } from '@/utils/utils';

// 5 样式
import styles from './index.less';

// 6 组件ts声明 每个组件只保留一个基础的ts声明 其余的放到types.ts文件下
interface PropsType {
  
}

const OrderDetail:FC<PropsType>=(props)=>{

  // 7 props
  const {abc} = props;

  // 8 状态
  const [detailInfo,setDetailInfo] = useState<DetailInfo>(xxx);
  const [xxx,xxx] = useState(xxx);

  // 9 hooks
  const xxx = useMemo(()=>{return xxxx},[abc]);

  useEffect(()=>{},[a]);

  useEffect(()=>{},[b]);

  //  10 方法
  const onChange=()=>{};

  const onClick=()=>{};

  return(
    <div></div>
  )
}

export default OrderDetail;

4、代码聚合:

  代码聚合指的是实现高内聚低耦合的原则,这也是评判软件设计优劣的一个标准。许多设计模式和框架都基于高内聚低耦合的原则。例如,我们可以拆分原本有1000行代码的组件A,虽然拆分后的代码量可能会增加,但是通过拆分,我们可以更快的定位我们要改动的模块,并且可以更准确的评估我们的改动所影响的范围。与代码的灵活性和可维护性相比,这些额外的代码量可以忽略不计。关于代码聚合的案例,可以参考React性能优化之状态内聚

代码拆分与聚合

总结

  本文主要从代码层面的设计和实现角度,探讨了如何使前端项目更易于维护。然而,要构建一个优秀的前端项目,我们还需从更广泛的角度去思考。本文的目的是希望开发者不要忽略一些细节的问题,把简单的事情做到极致,然后再去思考一些更深层次的东西。只有在不断地优化和改进中,我们才能打造出高质量、易于维护的前端项目。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容