首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >前端项目为什么越做越慢?聊聊代码「可持续发展」的那些事

前端项目为什么越做越慢?聊聊代码「可持续发展」的那些事

作者头像
前端达人
发布2025-11-20 08:36:02
发布2025-11-20 08:36:02
380
举报
文章被收录于专栏:前端达人前端达人

见过太多这样的场景了:

产品经理:"这个需求很简单,改一下就行。" 你打开代码:"卧槽,这是谁写的?" Git Blame 一查:"好像是我自己……半年前写的。"

残酷的真相是:90%的前端项目从第一行代码开始,就埋下了半年后重构的种子。

不是你技术不行,而是大多数人根本不知道什么叫"面向未来编程"。今天我们就来拆解前端代码走向死亡的9大致命陷阱,以及如何用架构思维让代码活得更久。

陷阱一:组件化的谎言 —— 你以为的复用,其实是灾难

🚨 常见的错误认知

很多人以为"组件化 = 可复用",于是疯狂拆组件:

代码语言:javascript
复制
// 看起来很"工程化",实则埋下隐患
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);

  useEffect(() => {
    fetchUser().then(setUser); // API调用
  }, []);

const handleEdit = () => {
    // 业务逻辑
    updateUser(user);
  };

return (
    <div>
      {/* UI逻辑 */}
      <Avatar src={user?.avatar} />
      <Form data={user} onSubmit={handleEdit} />
    </div>
  );
}

问题在哪? 这个组件混合了:

  • UI渲染(Avatar、Form)
  • 状态管理(useState)
  • 副作用(useEffect)
  • 业务逻辑(handleEdit)
  • API调用(fetchUser)

半年后需求变化:

  • PM要求换UI库?改不动,UI和逻辑耦合了
  • 需要在其他页面复用逻辑?抽不出来,全写在一起了
  • 单元测试?没法测,依赖太多外部环境

✅ 正确的分层架构

代码语言:javascript
复制
// 1. 服务层 - 隔离外部依赖
// services/userService.ts
exportconst userService = {
fetchUser: () => apiClient.get('/user'),
updateUser: (data) => apiClient.put('/user', data)
};

// 2. 行为层 - 封装业务逻辑
// hooks/useUserProfile.ts
exportfunction useUserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);

const loadUser = useCallback(async () => {
    setLoading(true);
    try {
      const data = await userService.fetchUser();
      setUser(data);
    } finally {
      setLoading(false);
    }
  }, []);

const updateProfile = useCallback(async (updates) => {
    const updated = await userService.updateUser(updates);
    setUser(updated);
  }, []);

return { user, loading, loadUser, updateProfile };
}

// 3. UI层 - 纯展示组件
function UserProfile() {
const { user, loading, loadUser, updateProfile } = useUserProfile();

return<UserProfileView 
    user={user} 
    loading={loading}
    onRefresh={loadUser}
    onUpdate={updateProfile}
  />;
}

分层的威力:

  • 换UI库?只改最外层组件
  • 逻辑复用?直接用 useUserProfile
  • 单元测试?Mock userService 即可
  • API变更?只改 userService

第二个坑:写死的代码 —— 需求一改就要重写

真实故事

朋友在一家做教育SaaS的公司,他们有个表单组件:

代码语言:javascript
复制
// 最开始:只支持邮箱验证
function EmailInput() {
const [email, setEmail] = useState('');

const validate = (value) => {
    return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
  };

const handleChange = (e) => {
    const value = e.target.value;
    if (validate(value)) {
      setEmail(value);
      props.onChange?.(value);
    }
  };

return<input type="email" value={email} onChange={handleChange} />;
}

一个月后,产品说:"我们要支持手机号登录。" 于是他又写了个 PhoneInput 组件,复制粘贴改正则。

两个月后,产品说:"表单输入要支持实时提示,比如'密码强度太弱'。" 他又改了一遍 EmailInputPhoneInput

三个月后,产品说:"输入框要支持前后缀图标。" 这时候他发现,项目里已经有8个几乎一样的输入框组件了,每次改需求要同时改8个地方。

问题根源: 没有为扩展留余地,所有逻辑都写死了。

✅ 面向扩展设计

代码语言:javascript
复制
// 核心思想:通过配置注入行为,而非修改代码
function ValidatedInput({ 
  type = 'text',
  validators = [],
  transformers = [],
  ...props 
}) {
const [value, setValue] = useState('');
const [error, setError] = useState('');

const handleChange = (e) => {
    let newValue = e.target.value;
    
    // 应用转换器(可扩展)
    transformers.forEach(transform => {
      newValue = transform(newValue);
    });
    
    // 应用验证器(可扩展)
    for (const validator of validators) {
      const result = validator(newValue);
      if (!result.valid) {
        setError(result.message);
        return;
      }
    }
    
    setError('');
    setValue(newValue);
  };

return (
    <>
      <input value={value} onChange={handleChange} {...props} />
      {error && <span className="error">{error}</span>}
    </>
  );
}

// 使用示例 - 无需修改组件代码
<ValidatedInput 
  validators={[
    emailValidator,
    maxLengthValidator(50)
  ]}
  transformers={[
    trimWhitespace,
    toLowerCase
  ]}
/>

关键原则:开闭原则(OCP)

对扩展开放,对修改关闭

第三个坑:文件夹像迷宫 —— 找个文件要翻半天

场景重现

新来的实习生问我:"哥,我想改一下订单列表的筛选逻辑,应该改哪个文件?"

我:"emmm,你先去 components 文件夹找 OrderList.tsx……"

他找了半天:"找到了,但是这里面没有筛选逻辑啊?"

我:"哦对,筛选逻辑在 hooks 文件夹的 useOrderFilter.ts 里。"

他:"那接口调用呢?"

我:"在 services 文件夹的 orderService.ts……对了,还有类型定义在 types 文件夹的 order.d.ts。"

他:"……"(已经懵了)

典型的按类型分类:

代码语言:javascript
复制
src/
  components/
    Button.tsx
    Modal.tsx
    UserForm.tsx
    OrderList.tsx
    ProductCard.tsx
    ... (100个组件混在一起)

  hooks/
    useUser.ts
    useOrder.ts
    useProduct.ts
    ... (50个hooks找不到)

  services/
    userService.ts
    orderService.ts
    ...

问题在哪?

  • 开发一个功能,要在3-4个文件夹里跳来跳去
  • 删除功能时,不知道要删哪些文件,容易漏删
  • 新人上手难,不知道功能的代码在哪
  • 代码审查时,相关改动分散在多个地方

✅ 按功能模块组织

代码语言:javascript
复制
src/
  features/
    user/
      components/
        UserForm.tsx
        UserAvatar.tsx
      hooks/
        useUserProfile.ts
        useUserAuth.ts
      services/
        userService.ts
      types/
        user.types.ts
      index.ts  // 统一导出
    
    order/
      components/
      hooks/
      services/
      index.ts

  shared/
    ui/         // 通用UI组件
    hooks/      // 通用Hooks
    utils/      // 工具函数

优势:

  • 功能内聚,相关代码在一起
  • 删除功能 = 删除文件夹
  • 新人上手快,不用全局翻找
  • 支持按需加载(Code Splitting)

第四个坑:和框架"深度绑定" —— 想换都换不了

真实困境

去年有个朋友跳槽到一家做B端SaaS的公司,他们的项目是3年前用React写的。现在公司想:

  1. 接入微前端(qiankun),把部分模块拆出来
  2. 部分页面用Vue重写(因为团队新来的人都是Vue技术栈)
  3. 移动端要做小程序,想复用业务逻辑

结果呢?一行代码都复用不了

为什么?因为业务逻辑和React深度耦合:

代码语言:javascript
复制
// 典型的"React全家桶"式写法
function OrderManager() {
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(false);

// 业务逻辑全写在useEffect里
  useEffect(() => {
    setLoading(true);
    fetch('/api/orders')
      .then(res => res.json())
      .then(data => {
        // 筛选逻辑
        const active = data.filter(o => o.status === 'active');
        // 排序逻辑
        const sorted = active.sort((a, b) => b.createTime - a.createTime);
        setOrders(sorted);
      })
      .finally(() => setLoading(false));
  }, []);

const handleApprove = useCallback((id) => {
    // 审批逻辑
    fetch(`/api/orders/${id}/approve`, { method: 'POST' })
      .then(() => {
        setOrders(prev => prev.map(o =>
          o.id === id ? { ...o, status: 'approved' } : o
        ));
      });
  }, []);

return<OrderList data={orders} onApprove={handleApprove} />;
}

这段代码的问题:

  • 筛选、排序等业务逻辑和 React Hooks 混在一起
  • API调用直接用 fetch,没有抽象
  • 要迁移到Vue?整个重写
  • 要做小程序?再写一遍

🛡️ 解决方案:把业务逻辑和框架分开

代码语言:javascript
复制
// ✅ 第一步:业务逻辑用纯JS/TS写(和框架无关)
// domain/OrderManager.ts
exportclass OrderManager {
constructor(private apiClient) {}

// 纯业务逻辑,不依赖任何框架
async getActiveOrders() {
    const orders = awaitthis.apiClient.get('/orders');
    return orders
      .filter(o => o.status === 'active')
      .sort((a, b) => b.createTime - a.createTime);
  }

async approveOrder(orderId) {
    awaitthis.apiClient.post(`/orders/${orderId}/approve`);
    return { success: true };
  }

  calculateTotal(orders) {
    return orders.reduce((sum, o) => sum + o.amount, 0);
  }
}

// ✅ 第二步:React适配层(只负责连接UI和业务逻辑)
// adapters/react/useOrderManager.ts
exportfunction useOrderManager() {
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(false);

// 创建业务逻辑实例
const manager = useMemo(() =>
    new OrderManager(apiClient), 
    []
  );

const loadOrders = useCallback(async () => {
    setLoading(true);
    try {
      const data = await manager.getActiveOrders();
      setOrders(data);
    } finally {
      setLoading(false);
    }
  }, [manager]);

return { orders, loading, loadOrders, manager };
}

// ✅ 使用
function OrderPage() {
const { orders, loading, loadOrders } = useOrderManager();
return<OrderList data={orders} loading={loading} />;
}

这样做的好处:

  1. 核心业务逻辑(OrderManager)是纯JS,可以:
    • 在Vue项目里用
    • 在小程序里用
    • 在Node.js后台用
    • 单独测试,不依赖React环境
  2. React只是适配器,换框架只需要重写适配层:
代码语言:javascript
复制
// 换成Vue也很简单
// adapters/vue/useOrderManager.js
import { ref, onMounted } from'vue';

exportfunction useOrderManager() {
const orders = ref([]);
const loading = ref(false);
const manager = new OrderManager(apiClient);

const loadOrders = async () => {
    loading.value = true;
    try {
      orders.value = await manager.getActiveOrders();
    } finally {
      loading.value = false;
    }
  };

return { orders, loading, loadOrders };
}

我司的实践: 我们用这个思路改造了核心业务模块,后来做小程序时,业务逻辑直接复用了80%,开发时间省了一个月。

第五个坑:没有测试 —— 改代码全凭运气

恐怖故事

上周我朋友公司出了个事故:

  • 开发小A改了一个用户权限的判断逻辑
  • 测试环境看起来没问题,提交上线
  • 结果导致所有VIP用户都无法登录
  • 周六凌晨3点被叫起来紧急回滚
  • 造成损失约20万

事后复盘:那段代码没有任何测试覆盖,改动影响范围完全靠"猜"。

真相

没有测试的代码 = 不敢改的代码 = 遗留代码

很多团队觉得"前端不用写测试",直到:

  1. 改A功能,B功能莫名其妙炸了(没测试,不知道影响范围)
  2. 不敢重构,因为不确定改了会不会出问题
  3. 新人加入,战战兢兢,怕改错被骂

国内现状

我调研了几个团队:

  • 大厂(阿里、字节等):测试覆盖率要求50%+,核心模块必须80%+
  • 创业公司:几乎没有测试,全靠手工测试和"线上测试"
  • 外包公司:更别提了,能跑就行

✅ 怎么做测试?别慌,从小开始

第一步:给核心业务逻辑加单元测试

代码语言:javascript
复制
// 测试业务逻辑(不依赖UI)
import { OrderManager } from'@/domain/OrderManager';

describe('订单管理', () => {
let manager;
let mockApi;

  beforeEach(() => {
    // Mock API,不用真的调后端
    mockApi = {
      get: jest.fn(),
      post: jest.fn()
    };
    manager = new OrderManager(mockApi);
  });

  test('应该正确筛选和排序活跃订单', async () => {
    // 准备测试数据
    mockApi.get.mockResolvedValue([
      { id: 1, status: 'active', createTime: 100 },
      { id: 2, status: 'closed', createTime: 200 },
      { id: 3, status: 'active', createTime: 300 }
    ]);
    
    const orders = await manager.getActiveOrders();
    
    // 验证结果
    expect(orders).toHaveLength(2);
    expect(orders[0].id).toBe(3); // 应该按时间倒序
    expect(orders[1].id).toBe(1);
  });

  test('计算订单总额应该正确', () => {
    const orders = [
      { amount: 100 },
      { amount: 200 },
      { amount: 50 }
    ];
    
    const total = manager.calculateTotal(orders);
    expect(total).toBe(350);
  });
});

第二步:给用户操作加集成测试

代码语言:javascript
复制
import { render, fireEvent, waitFor, screen } from'@testing-library/react';
import { OrderPage } from'@/features/order/OrderPage';

test('用户应该能够审批订单', async () => {
// 渲染页面
  render(<OrderPage />);

// 等待订单加载
await waitFor(() => {
    expect(screen.getByText('待审批订单')).toBeInTheDocument();
  });

// 点击"审批"按钮
const approveButton = screen.getByRole('button', { name: /审批/ });
  fireEvent.click(approveButton);

// 确认弹窗出现
  expect(screen.getByText('确认审批?')).toBeInTheDocument();

// 点击确认
  fireEvent.click(screen.getByRole('button', { name: /确认/ }));

// 验证成功提示
await waitFor(() => {
    expect(screen.getByText('审批成功')).toBeInTheDocument();
  });
});

第三步:循序渐进,不要一次搞太多

我给团队定的规则:

  1. 新功能必须有测试(否则不允许合并)
  2. 修bug时补测试(避免同样的bug再出现)
  3. 重构前先写测试(保证重构不改变行为)

效果: 三个月后,测试覆盖率从0%到了45%,线上bug率下降了60%。

第六个坑:没有规范 —— 每个人都在写自己的风格

典型症状

接手一个项目,打开代码:

代码语言:javascript
复制
// 文件A:老王写的
class UserList extends React.Component {
  componentDidMount() {
    fetch('/api/users').then(res => {
      this.setState({ users: res.data })
    })
  }
}

// 文件B:小李写的
const OrderList = () => {
const [orders, setOrders] = useState([])

  useEffect(() => {
    axios.get('/api/orders').then(({data}) => setOrders(data));
  }, [])
}

// 文件C:实习生写的
function ProductList() {
const [products, setProducts] = useState([])

  useEffect(() => {
    request({
      url: '/api/products',
      method: 'get'
    }).then(res => {
      setProducts(res.list)
    })
  }, [])
}

同一个项目里:

  • Class组件、函数组件混用
  • fetch、axios、自定义request混用
  • 有人写分号,有人不写
  • 有人用单引号,有人用双引号
  • 变量命名五花八门(data、list、items、records……)

结果: 代码库像"拼接怪",新人看代码都要先猜"这是谁的风格"。

现实场景

我见过最夸张的:一个10人团队的项目,同一个功能的实现方式有5种不同写法

为什么?因为:

  • 老员工各有各的习惯
  • 新人来了照着老代码写,学会了3种风格
  • 没人统一,也没人敢改

✅ 工程化 = 强制统一

第一步:配置ESLint + Prettier

代码语言:javascript
复制
// .eslintrc.js
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended'
  ],
  rules: {
    // 禁止直接使用fetch
    'no-restricted-globals': ['error', {
      name: 'fetch',
      message: '请使用 @/utils/request 代替 fetch'
    }],
    
    // 禁止使用var
    'no-var': 'error',
    
    // 必须使用命名导出(不用export default)
    'import/no-default-export': 'error',
    
    // useState必须有类型注解(如果用TS)
    '@typescript-eslint/explicit-function-return-type': 'warn'
  }
};

// prettier.config.js
module.exports = {
  printWidth: 100,
  semi: true,              // 统一使用分号
  singleQuote: true,       // 统一使用单引号
  trailingComma: 'es5',
  tabWidth: 2
};

第二步:Git提交前自动检查(Husky)

代码语言:javascript
复制
// package.json
{
"husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
"lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}

效果:

  • 代码提交前自动格式化,不符合规范的代码提交不了
  • 团队成员被"强制"写出统一风格的代码
  • Code Review时不用再讨论"分号加不加"这种问题

第三步:写一份团队规范文档

代码语言:javascript
复制
# 开发规范

## API调用
✅ 使用统一的 request 工具
❌ 不要直接用 fetch 或 axios

## 组件编写
✅ 优先使用函数组件 + Hooks
❌ 不要新写 Class 组件

## 状态管理
- 本地状态:useState
- 跨组件状态:Zustand
- 服务端数据:React Query

## 命名规范
- 组件文件:PascalCase(UserProfile.tsx)
- 工具函数:camelCase(formatDate.ts)
- 常量:UPPER_SNAKE_CASE(API_BASE_URL)

我们团队的实践: 规范落地后,Code Review时间从平均1小时降到了20分钟,因为大家不用再讨论风格问题,可以专注于逻辑本身。

第七个坑:没有文档 —— 知识全在老员工脑子里

真实困境

新人小张入职第一天:

小张:"这个项目的状态管理是怎么设计的?" 老李:"emmm,你看代码就知道了。"

小张(看了一天代码):"为什么有的地方用Redux,有的地方用Context,有的又用Zustand?" 老李:"哦,Redux是历史遗留的,Context是临时方案,Zustand是我们现在推荐的。" 小张:"那我新功能用哪个?" 老李:"看情况吧……"

小张内心:"我看个锤子啊……"

国内现状

我见过很多团队:

  • 大公司:文档倒是有,但都过时了,没人维护
  • 创业公司:"没时间写文档,先把功能做出来再说"
  • 外包团队:项目交付就完事,文档?不存在的

结果:

  • 新人上手全靠"口口相传"
  • 老员工离职,知识就断层了
  • 改代码全凭猜,不知道为什么要这样写

✅ 写最少但最有用的文档

很多人一提文档就头疼:"要写多少文档啊?我代码都写不完……"

我的建议:不要写厚厚的文档,只写3个关键的。

1. 架构决策文档(ADR - Architecture Decision Record)

代码语言:javascript
复制
# docs/architecture/001-状态管理方案.md

## 背景
项目初期用了Redux,但团队反馈太繁琐。

## 决策
- 本地状态:useState / useReducer
- 全局状态:Zustand(轻量、简单)
- 服务端数据:React Query(自动缓存、重试)

## 理由
1. Zustand比Redux简单,学习成本低
2. React Query专门处理异步数据,不用手写loading状态
3. 本地状态够用就不要全局

## 示例
\`\`\`typescript
// ✅ 本地状态
const [count, setCount] = useState(0);

// ✅ 全局状态(跨页面共享)
import { useAuthStore } from '@/stores/auth';
const { user, login, logout } = useAuthStore();

// ✅ 服务端数据
import { useQuery } from '@tanstack/react-query';
const { data: orders } = useQuery(['orders'], fetchOrders);
\`\`\`

## 更新日期
2024-03-15

2. 快速上手指南

代码语言:javascript
复制
# docs/快速开始.md

## 开发一个新功能的标准流程

### 1. 确定功能位置
新功能放在 `src/features/` 下:
\`\`\`
src/features/新功能名/
  components/     # UI组件
  hooks/          # 业务逻辑
  services/       # API调用
  index.ts        # 统一导出
\`\`\`

### 2. 创建组件
\`\`\`typescript
// components/FeatureName.tsx
export function FeatureName() {
  const { data, loading } = useFeatureData();
  return <FeatureView data={data} loading={loading} />;
}
\`\`\`

### 3. 添加路由
在 `src/routes/index.ts` 添加路由配置

### 4. 提交代码
\`\`\`bash
git add .
git commit -m "feat: 添加XX功能"  # 会自动检查代码规范
git push
\`\`\`

3. 常见问题FAQ

代码语言:javascript
复制
# docs/FAQ.md

## Q: 为什么不能直接用 fetch?
A: 我们封装了统一的 request 工具(`@/utils/request`),它自动处理:
- Token添加
- 错误拦截
- 请求日志
- 超时控制

## Q: 新增接口怎么做类型定义?
A: 在对应功能的 `types/` 目录下添加,参考 `features/user/types/user.types.ts`

## Q: 本地开发如何调试接口?
A: `.env.development` 中配置了代理,会自动转发到测试环境

## Q: 遇到奇怪的bug怎么办?
A: 先看浏览器控制台,再看 Sentry 错误日志

效果: 我们团队只维护这3个文档,新人上手时间从2周缩短到3天。关键是文档短小精悍,不会因为太长而没人看。

第八个坑:线上出问题不知道 —— 用户炸了你还蒙在鼓里

恐怖故事

真事:我一个朋友公司的C端产品,某个核心功能崩溃了整整3天,直到用户在微博上吐槽才发现。

为什么?

  1. 没有错误监控,页面白屏了都不知道
  2. 没有性能监控,页面加载慢用户直接流失
  3. 没有用户行为追踪,不知道用户在哪个环节卡住了

后果:

  • 3天流失了2000+用户
  • App Store评分从4.5掉到3.2
  • 老板震怒,前端负责人被约谈

国内常见问题

我问过很多前端团队:"你们有监控吗?"

回答五花八门:

  • "我们有啊,偶尔会看看控制台……"(这不叫监控)
  • "测试会帮我们测的。"(测试覆盖不了所有场景)
  • "线上没出过大问题啊。"(那是你不知道而已)
  • "监控太贵了,老板不批预算。"(出事故更贵)

✅ 最小化监控方案(适合国内团队)

第一步:接入错误监控(推荐用国内服务)

国内可用的监控平台:

  • 阿里云ARMS(前端监控):性价比高,中文文档
  • 腾讯云前端性能监控:和微信生态结合好
  • Sentry(自建或云):功能最全,有中文版
代码语言:javascript
复制
// 接入阿里云ARMS(示例)
import arms from'arms-front';

// 初始化
arms.init({
pid: 'your-project-id',
region: 'cn-hangzhou'
});

// 在React里加错误边界
class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    // 自动上报到ARMS
    arms.error(error, {
      component: errorInfo.componentStack,
      userId: getCurrentUser()?.id,
      page: window.location.href
    });
  }

  render() {
    if (this.state.hasError) {
      return<div>页面出错了,请刷新重试</div>;
    }
    returnthis.props.children;
  }
}

第二步:监控核心性能指标

代码语言:javascript
复制
// 监控页面加载性能
import { reportWebVitals } from'web-vitals';

reportWebVitals((metric) => {
// 上报到监控平台
  arms.send({
    type: 'performance',
    name: metric.name,  // LCP、FID、CLS等
    value: metric.value,
    page: window.location.pathname
  });

// 如果性能太差,打个警告
if (metric.name === 'LCP' && metric.value > 2500) {
    console.warn('⚠️ 页面加载太慢了!', metric);
  }
});

第三步:关键操作埋点

代码语言:javascript
复制
// 工具函数:统一埋点
exportfunction trackEvent(eventName, properties = {}) {
// 上报到监控平台
  arms.track(eventName, {
    ...properties,
    timestamp: Date.now(),
    userId: getCurrentUser()?.id,
    page: window.location.pathname
  });
}

// 使用示例
function OrderButton() {
const handleSubmit = () => {
    // 关键操作要埋点
    trackEvent('订单提交', {
      orderId: order.id,
      amount: order.amount
    });
    
    submitOrder();
  };

return<button onClick={handleSubmit}>提交订单</button>;
}

第四步:设置告警规则

在监控平台配置:

  • 错误率 > 1%:立即告警(钉钉/企业微信)
  • 接口响应时间 > 3秒:告警
  • 页面加载时间 > 5秒:告警

成本:

  • 小公司(日活<1万):每月100-200元
  • 中型公司(日活1-10万):每月500-2000元

收益:

  • 故障平均发现时间:从3天 → 5分钟
  • 线上bug数量:下降70%
  • 用户投诉率:下降50%

建议: 如果预算有限,至少要有错误监控,其他可以慢慢加。

第九个坑:组件乱象 —— 一个项目37个Button

真实案例

去年接手一个项目,产品说:"把所有按钮改成圆角的。"

我:"好的……等等,我们有多少个Button组件?"

Git搜索结果:

  • Button.tsx(最早的)
  • CustomButton.tsx(不知道谁写的)
  • PrimaryButton.tsxSecondaryButton.tsx(按样式分的)
  • UserButton.tsxOrderButton.tsx(按功能分的)
  • ButtonV2.tsx(不知道V1在哪)
  • NewButton.tsxTempButton.tsx(临时方案)
  • ……

总共37个"按钮"组件!

为什么会这样?

  • 每个人都觉得"现有的Button不够用"
  • 直接复制粘贴改一改
  • 没人统一管理
  • 时间久了,谁也不敢删老组件

国内现状

很多团队:

  • 初创期:"先把功能做出来,组件库以后再说"
  • 成长期:"现在没时间统一,等忙完这一版……"
  • 维护期:"组件太乱了,不敢改,怕影响老功能"

结果: 永远在"计划重构",永远没时间做。

✅ 设计系统,从小做起

很多人的误区: "设计系统=要做一套完整的组件库,太复杂了!"

实际上: 从3个基础组件开始就够了。

第一步:定义设计Token

代码语言:javascript
复制
// design-tokens/colors.ts
exportconst colors = {
// 主色
  primary: '#1890ff',
  primaryHover: '#40a9ff',
  primaryActive: '#096dd9',

// 功能色
  success: '#52c41a',
  warning: '#faad14',
  error: '#ff4d4f',

// 中性色
  text: {
    primary: '#000000d9',
    secondary: '#00000073',
    disabled: '#00000040'
  },

// 背景色
  background: {
    default: '#ffffff',
    gray: '#fafafa',
    hover: '#f5f5f5'
  }
};

// design-tokens/spacing.ts
exportconst spacing = {
  xs: '4px',
  sm: '8px',
  md: '16px',
  lg: '24px',
  xl: '32px',
  xxl: '48px'
};

// design-tokens/radius.ts
exportconst radius = {
  small: '2px',
  medium: '4px',
  large: '8px',
  round: '50%'
};

第二步:做3个基础组件

代码语言:javascript
复制
// components/Button/Button.tsx
import { colors, spacing, radius } from '@/design-tokens';

type ButtonVariant = 'primary' | 'secondary' | 'danger';
type ButtonSize = 'small' | 'medium' | 'large';

interface ButtonProps {
  variant?: ButtonVariant;
  size?: ButtonSize;
  children: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
}

export function Button({ 
  variant = 'primary', 
  size = 'medium',
  children,
  ...props 
}: ButtonProps) {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      {...props}
    >
      {children}
    </button>
  );
}

// 样式统一写在一起
const styles = {
  '.btn': {
    borderRadius: radius.medium,
    border: 'none',
    cursor: 'pointer',
    fontWeight: 500,
    transition: 'all 0.2s'
  },

  // 变体样式
  '.btn--primary': {
    background: colors.primary,
    color: '#fff',
    '&:hover': {
      background: colors.primaryHover
    }
  },

  // 尺寸样式
  '.btn--small': {
    padding: `${spacing.xs} ${spacing.sm}`,
    fontSize: '12px'
  },
  '.btn--medium': {
    padding: `${spacing.sm} ${spacing.md}`,
    fontSize: '14px'
  }
};

第三步:强制使用

代码语言:javascript
复制
// .eslintrc.js(加个规则)
rules: {
  'no-restricted-imports': ['error', {
    patterns: [
      {
        group: ['antd/es/button', 'antd/lib/button'],
        message: '请使用 @/components/Button'
      }
    ]
  }]
}

第四步:在Figma/蓝湖同步设计规范

和设计师约定:

  1. 所有按钮统一用这3种样式
  2. 特殊需求先讨论,不行再扩展
  3. 设计稿里标注清楚是哪个variant

我们团队的实践:

  • 第一周:统一Button、Input、Modal 3个组件
  • 第二周:替换项目中所有旧组件(用工具批量替换)
  • 一个月后:组件从37个 → 8个
  • 维护成本:下降80%

关键: 不要追求完美,先把最常用的3-5个组件统一就有巨大收益。

最后聊聊:别被"完美主义"困住

看到这里,可能有人会说:"这也太复杂了吧?我们小团队哪有精力搞这些?"

我想说:面向未来 ≠ 过度设计

判断标准

需要分层吗? 看复杂度:

  • 简单页面(展示型):不用,直接写
  • 中等复杂(有交互):UI和逻辑分开就行
  • 高度复杂(多状态、多交互):完整分层

需要抽象吗? 看复用:

  • 用1次:别抽象,直接写
  • 用2次:复制粘贴,观察一下
  • 用3次及以上:认真考虑抽象

需要测试吗? 看风险:

  • 核心业务逻辑:必须测
  • 工具函数:建议测
  • 简单UI组件:可选

需要文档吗? 看团队:

  • 1-3人:口头沟通够了
  • 4-10人:写个简单的快速开始
  • 10人以上:需要完整文档

循序渐进,一步步来

我的建议:

第一个月: 把基础打好

  • ✅ 配置ESLint + Prettier(工程化)
  • ✅ 统一API调用方式(封装request)
  • ✅ 按功能组织文件夹

第二个月: 加点规范

  • ✅ 写个简单的开发规范文档
  • ✅ 统一2-3个最常用的组件(Button、Input)
  • ✅ 加个错误监控(ARMS或Sentry)

第三个月: 提升质量

  • ✅ 给核心逻辑加测试
  • ✅ 完善设计系统
  • ✅ 开始做性能优化

不要一开始就想着"重构整个项目",那样会被淹没。

国内团队的真实建议

根据我和50+团队交流的经验:

早期创业公司(<10人):

  • 重点:代码规范 + 错误监控
  • 不要:过度设计、复杂架构

成长期公司(10-50人):

  • 重点:模块化 + 设计系统 + 文档
  • 不要:架构调整太频繁

成熟公司(50人+):

  • 重点:完整的工程化体系
  • 可以:考虑微前端、自建组件库

写在最后

一个不那么励志的真相: 大多数前端代码的寿命不超过2年。

不是因为技术过时,而是因为:

  • 业务变了,要重新做
  • 维护成本太高,不如重写
  • 技术栈换了,得迁移

但是! 那些做得好的项目,可以用5年、10年,甚至更久。

它们的共同特点:

  • ✅ 欢迎变化:需求改了,不是灾难
  • ✅ 容易理解:新人1周能上手
  • ✅ 持续优化:不断改进,但不推倒重来

这才是"可持续发展"的代码。

一句话总结

写代码不只是给机器看的,更是给6个月后的自己看的。 如果6个月后的你看到今天的代码会说"牛逼",那就对了。 如果看到会说"这TM是谁写的",那就该改改思路了。

互动时间

你的项目有遇到过这些坑吗? 评论区聊聊:

  1. 你最头疼的技术债是什么?
  2. 你们团队是怎么做代码规范的?
  3. 有没有什么"血泪教训"可以分享?

觉得有帮助的话,点个赞+收藏!

关注我,一起进步! 🚀

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 陷阱一:组件化的谎言 —— 你以为的复用,其实是灾难
    • 🚨 常见的错误认知
    • ✅ 正确的分层架构
  • 第二个坑:写死的代码 —— 需求一改就要重写
    • 真实故事
    • ✅ 面向扩展设计
  • 第三个坑:文件夹像迷宫 —— 找个文件要翻半天
    • 场景重现
    • ✅ 按功能模块组织
  • 第四个坑:和框架"深度绑定" —— 想换都换不了
    • 真实困境
    • 🛡️ 解决方案:把业务逻辑和框架分开
  • 第五个坑:没有测试 —— 改代码全凭运气
    • 恐怖故事
    • 真相
    • 国内现状
    • ✅ 怎么做测试?别慌,从小开始
  • 第六个坑:没有规范 —— 每个人都在写自己的风格
    • 典型症状
    • 现实场景
    • ✅ 工程化 = 强制统一
  • 第七个坑:没有文档 —— 知识全在老员工脑子里
    • 真实困境
    • 国内现状
    • ✅ 写最少但最有用的文档
  • 第八个坑:线上出问题不知道 —— 用户炸了你还蒙在鼓里
    • 恐怖故事
    • 国内常见问题
    • ✅ 最小化监控方案(适合国内团队)
  • 第九个坑:组件乱象 —— 一个项目37个Button
    • 真实案例
    • 国内现状
    • ✅ 设计系统,从小做起
  • 最后聊聊:别被"完美主义"困住
    • 判断标准
    • 循序渐进,一步步来
    • 国内团队的真实建议
  • 写在最后
    • 一句话总结
  • 互动时间
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档