跳转至

项目优化

1. 开始

介绍下对某项目做的优化,包括代码重构、体验优化等方面。

2. 问题

外行写前端有几个明显特征:

  1. 大段dom全写到页面中,没有组件化、没有模块化,缺点:后期难以维护
  2. 不会一些相对高级、简洁的语法,比如数组的map, reduce, filter, 只会 for 循环 index。缺点:代码又臭又长,难以维护
  3. 代码规范严重缺失,风格极度不统一。缺点:难以维护,后面的开发人员边写边骂街
  4. 没有数据驱动,设计稿怎么写就全部罗列到页面里,典型的是数据页和规则。缺点:难以维护、难以扩展、难以复用。

外行还不是“新手”,新手会成长,外行不会成长

之前的项目就是由“外行”写的,上面的问题都有。

此外还有很重要的一点,MR 形同虚设,“同意”机器人,毫无思想,毫无原则,毫无规范。仿佛在比谁点同意点的快,谁就🐮🍺。

3. 解决

组件方面,解决手段就是抽组件、提高复用率,整理代码、删除重复样式等。

3.1. 原则

  1. 严格遵守代码规范,这是防止项目腐化的第一步。长期来看,一个没有代码规范的项目,一定会变成x
  2. 分层,一个项目能健康跑多远,取决于分层的程度。这个项目而言,核心组件、核心流程(拉起游戏、赛程卡片)一定要物理隔离,其他人想污染也污染不到
  3. 消除重复代码,能复用的一定要复用。一方面可以减少维护成本,只维护一份,另一个方面包体积也比较小,性能较好。

这里要特别提下代码规范,我个人比较看重这个。业务项目如果没有代码规范,可能还会因为业务存在不得不维护,技术项目如果没有代码规范,就是在被废弃的路上了。

组件样式细节:

  • 不同位置的 class 名称不能重复,除非是特定、少数的状态词,表示位置、结构的绝不能重复
  • CSS应该按照他们在DOM中顺序,从上到下开始书写,不能乱序

3.2. 工具

针对大量无用样式的问题,做了一个插件,在小程序下,对组件的模板(wxml/qml等)、样式(wxss/qss等)、脚本文件(.js)大小进行分析。可以快速定位哪些组件问题最大、最亟需优化。

插件会获取每个组件(包括页面)的上述三种产物的大小,以及样式与模板的体积比例、模板与脚本的体积比例,输出为 json 文件和 csv 文件。

开发者可以据此进行分析,一般来说,样式模板体积比(cssHtmlRatio)特别大的组件(基础组件除外),往往存在大量无用样式

可利用 Excel 中的自定义排序,对 cssHtmlRatio 进行降序,进而确定优化目标及优先级。

3.3. 优化效果

以一个组件为例,优化前66KB,优化后5KB,减少了93.8%

4. 体验优化

在代码重构的基础了做了一些体验优化,包括

  1. 支持默认路由(404页面重定向到首页)
  2. 跳转二级页面时,携带已有的信息并展示。举例,从首页到个人页面跳转时,会携带用户名、头像等,然后接口返回后,显示其他信息。不能放到 url 里,否则 url 过长,复制后用户体验差。容量控制,不能过大。
  3. 核心页面增加骨架屏
  4. 弹窗、Popup等都增加渐变动画
  5. 页面切换增加切换效果,后面细说
  6. H5在PC端展示优化,后面细说

5. 性能优化

也做了一些涉及性能方面的优化。

  1. 使用tim的页面都放到了一个分包中,views/battle-room => views/match/battle-room, 原页面进行了重定向,外部无感知。优化效果:主包大小减少,1.88MB => 1.22MB
  2. h5tim 使用异步加载
  3. h5vendor 拆分
  4. console 去除

6. 开发体验优化

  1. press-gp-dialogdom 内嵌在 global-component 中,这样每个页面只需要注入一个全局组件即可,即 global-component。同时,在 main.ts 中增加了 press-gp-dialog 调用时的默认 selector,即从 global-component 内部寻找。press-toast 同理。好处是,降低开发成本,减少犯错。
  2. 每个项目的QQ小程序二维码单独一个文档
  3. 去除循环依赖、子包引用主包等引用错误问题,避免潜在的错误。这里是做了插件去提取引用错误的问题。

7. 页面切换动画

介绍下页面切换动画(Page Animation)的原理、效果。

7.1. 原理

核心原理是借助了 routerbeforeEachafterEach 钩子,在路由跳转前后改变顶层类名,进而增加动画。

beforeEach 负责添加 xxx-out 类名,afterEach 负责添加 xxx-in 类名。这两钩子中是拿不到路由是前进还是后退的,所以需要 hook 路由跳转方法,记录跳转类型。

以最常用的 slide-in-right 举例,最核心的样式是 translateX,其作用的元素是 uni-app,它是 body > #app 的下个元素。

7.1.1. 页面跳转

前进时,动画从右往左。

.page-slide-in-right--out {
  uni-page {
    // 前进,1
    animation: page-left-out var(--redirect-duration, 300ms) ease;
  }
}

.page-slide-in-right--in {
  uni-page {
    // 前进,2
    animation: page-right-in var(--redirect-duration, 300ms) ease;
  }
}

left-out 内容:

@keyframes page-left-out {
  0% {
    opacity: 1;
    transform: translateX(0);
  }

  100% {
    opacity: 0;
    transform: translateX(-90%);
  }
}

right-in 内容:

@keyframes page-right-in {
  0% {
    opacity: 0;
    transform: translateX(90%);
  }

  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

页面表现:

7.1.2. 页面返回

返回时,动画从左往右。

.page-slide-in-right--out {
  &.back uni-page {
    // 返回,1
    animation: page-right-out var(--redirect-duration, 300ms) ease;
  }
}

.page-slide-in-right--in {
  &.back uni-page {
    // 返回,2
    animation: page-left-in var(--redirect-duration, 300ms) ease;
  }
}

right-out 内容:

@keyframes page-right-out {
  0% {
    opacity: 1;
    transform: translateX(0);
  }

  100% {
    opacity: 0;
    transform: translateX(90%);
  }
}

left-in 内容:

@keyframes page-left-in {
  0% {
    opacity: 0;
    transform: translateX(-90%);
  }

  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

页面表现:

7.2. 效果

左边是使用之前,右边是使用之后。使用后有切换动画,流畅感有提升。

7.3. 接入方式

App.vueonLaunch 方法中增加拦截。

// #ifdef H5
import { pageAnimation } from 'press-plus/common/page-animation/index';
import { hookVueRouter } from 'press-plus/common/page-animation/hook-vue-router';
// #endif


onLaunch(() => {
  // #ifdef H5
  pageAnimation({ router: this.$router });
  hookVueRouter(this.$router, {
    log: process.env.NODE_ENV === 'development',
  });
  // #endif
})

8. H5在PC端展示优化

介绍下 H5 项目在 PC 端打开的优化。背景是 H5 用电脑浏览器打开时,会变形,宽高都会被拉长。

8.1. 原理

优化思路是模拟浏览器的手机调试,具体就是在 PC 端打开一个 iframe,其宽高是手机的尺寸,其路径是想要打开的页面路径。

当发现是在PC打开,且不在白名单内时,就跳转到 /web-container?path=xxx 的路由,xxx 就是之前的 window.location.href

web-container 页面内是一个 iframe,会拿到页面的 query.path,将其作为 iframesrc

当子应用页面跳转时,调用 window.parent.history.replaceState,更新 query.path,这样刷新页面,不会跳转到其他地方。

当发现是手机浏览器打开时,且当前是 web-container 模式,就 router.replace 到真正的页面,即去掉子应用。

8.2. 效果

8.3. 接入方式

先根据自身业务,进行二次封装,如:

import { redirectToWebContainerInPC } from 'press-plus/common/web-container/index';


const pathWhiteList = ['web-container', 'third-plat-login', 'ai-room-pc'];
const disableWebContainerList = ['ai-room-pc'];
const corePath = '/pvpesport.next.user';


export function redirectToWebContainer(options) {
  return redirectToWebContainerInPC({
    pathWhiteList,
    disableWebContainerList,
    corePath,

    router: options?.router,
    log: true,
    tag: options?.tag,
  });
}

App.vueonLaunch 方法中增加以下方法。

onLaunch(() => {
  // #ifdef H5
  redirectToWebContainer({ router: this.$router, tag: 'onLaunch' });

  window.addEventListener('resize', () => {
    redirectToWebContainer({ router: this.$router, tag: 'resize' });
  });

  this.$router.afterEach(() => {
    redirectToWebContainer({ router: this.$router, tag: 'afterEach' });
  });
  // #endif
})

9. 感想

事后改代码成本颇多

  1. 不测,或不全量测,存在影响线上的风险,测,则成本过高
  2. 难以模拟所有环境,自测、调试困难
  3. 需了解之前的需求、之前的代码,存在上下文切换成本