百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Vue + H5 最佳实践模板(vue-h5-template)

myzbx 2025-03-25 15:39 8 浏览

模板基于 vue-cli4 和 Vant-ui 搭建,进行大型 H5 项目开发最佳实践方案,让我们来一探究竟

项目结构

本项目已经为你生成了一个完整的开发框架,下面是整个项目的目录结构。

├── .github                    # git log
├── plop-templates             # 基本模板
├── public                     # 静态资源
│   │── favicon.ico            # favicon图标
│   └── index.html             # html模板
├── src                        # 源代码
│   ├── assets                 # 静态资源
│   ├── components             # 全局公用组件
│   ├── constants              # 常量
│   ├── core                   # 分层
│   ├── enum                   # 枚举
│   ├── filters                # 全局 filter
│   ├── icons                  # 项目所有 svg icons
│   ├── lang                   # 国际化 language
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store 管理
│   ├── styles                 # 全局样式
│   ├── utils                  # 全局公用方法
│   ├── pages                  # pages 所有页面
│   ├── pwa                    # 渐进式Web应用
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
│   └── permission.js          # 权限管理
├── tests                      # 测试
├── .editorconfig              # 代码风格
├── .env.xxx                   # 环境变量配置
├── .eslintrc.js               # eslint 配置项
├── .sentryclirc.js            # 前端异常日志监控配置
├── .babel.config              # babel 配置
├── plopfile.js                # 基本模板配置
├── vue.config.js              # vue-cli 配置
├── postcss.config.js          # postcss 配置
└── package.json               # package.json
...

安装

# 克隆项目
git clone https://github.com/push-over/vue-h5-template.git

# 进入项目目录
cd vue-h5-template

# 安装依赖
npm install

# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org

# 本地开发 启动项目
npm start

TIP

强烈建议不要用直接使用 cnpm 安装,会有各种诡异的 bug,可以通过重新指定 registry 来解决 npm 安装速度慢的问题。若还是不行,可使用 yarn 替代 npm。

Windows 用户若安装不成功,很大概率是node-sass安装失败,解决方案。

另外因为 node-sass 是依赖 python环境的,如果你之前没有安装和配置过的话,需要自行查看一下相关安装教程。

启动完成后会自动打开浏览器访问 [http://localhost:9000, 你看到下面的页面就代表操作成功了。

接下来你可以修改代码进行业务开发了,本项目内建了典型业务模板、模拟数据、状态管理、国际化、全局路由等等各种实用的功能来辅助开发

常用命令

# 项目打包
npm run build:xxx

# 自动创建
npm run new

# 规范Git提交
npm run git-cz

# 生成CHANGELOG
npm run genlog

分层架构

目前前端的一个开发趋势是以搭建单页应用 (SPA) 为主的。当应用体系比较大,或者你的应用业务逻辑足够复杂的时候,会遇到各种各样的问题,我们随便提两点:

  • 产品需要多人协作时,每个人的代码风格和对业务的理解不同,导致业务逻辑分布杂乱无章
  • 对产品的理解停留在页面驱动层面,导致实现的技术模型与实际业务模型出入较大,当业务需求变动时,技术模型很容易被摧毁

针对上面所遇到的问题,我们以下面这张架构图做讲解:

其中 视图层/View 是大家接触最多的,想必大家都很了解,就不在这里介绍了,重点介绍其他几个层的含义:

Services 层

Services 层是用来对底层技术进行操作的,例如封装 AJAX 请求,操作浏览器 Cookie、LocaStorage、IndexedDB,操作 Native 提供的能力(如调用摄像头等),以及建立 Websocket 与后端进行交互等。

Axios 封装

.....

export default async function(options) {
  const { url } = options
  const requestOptions = Object.assign({}, options)

  try {
    const { data, data: { errno, errmsg }} = await instance.request(requestOptions)
    if (errno) {
      errorReport(url, errmsg, requestOptions, data)
      throw new Error(errmsg)
    }
    return data
  } catch (err) {
    errorReport(url, err, requestOptions)
    throw err
  }
}

IndexedDB

...

export class DBRequest {
  instance

  static getInstance() {
    if (!this.instance) {
      this.instance = new DBRequest()
    }
    return this.instance
  }
  async create(options = {}) {
    const { name, data } = options
    const db = await indexDB(name)
    return await db.add(name, data)
  }
    ...
}

…….

Entities 层

实体 Entity 是领域驱动设计的核心概念,它是领域服务的载体,它定义了业务中某个个体的属性和方法。区分一个对象是否是实体,主要是看他是否有唯一的标志符(例如 id)

通过上面的代码可以看到,这里主要是以实体本身的属性以及派生属性为主,当然实体本身也可以具有方法,用于实现属于实体自身的业务逻辑。

并不是所有的实体都应该按上面那样封装成一个类,如果某个实体本身业务逻辑很简单,就没有必要进行封装,例如本模板中的 Test 只是做个演示。

Interactors 层

Interactors 层是负责处理业务逻辑的层,主要是由业务用例组成

import { Request } from '@/utils/request'
import { CARDS } from '@/constants/api/test'

class TestHttpInteractor {
  service
  constructor(service) {
    this.service = service
  }
  async getTest() {
    try {
      const options = { url: CARDS }
      return await this.service.get(options)
    } catch (error) {
      throw error
    }
  }
  async createTest(data) {
    try {
      const optons = { url: CARDS, data }
      await this.service.post(optons)
    } catch (error) {
      throw error
    }
  }

    ...
}

const testHttpInteractor = new TestHttpInteractor(Request.getInstance())
export default testHttpInteractor

通过上面的代码可以看到,Sevices 层提供的类的实例主要是通过 Interactors 层的类的构造函数获取到,这样就可以达到两层之间解耦,实现快速切换 service 的目的了,当然这个和依赖注入 DI 还是有些差距的,不过已经满足了我们的需求。

另外 Interactors 层还可以获取 Entities 层提供的实体类,将实体类提供的与实体强相关的业务逻辑和 Interactors 层的业务逻辑融合到一起提供给 View 层,例如:

当然这种分层架构并不是银弹,其主要适用的场景是:实体关系复杂,而交互相对模式化,例如企业软件领域。相反实体关系简单而交互复杂多变就不适合这种分层架构了。

然后需要明确的是,架构和项目文件结构并不是等同的,文件结构是你从视觉上分离应用程序各部分的方式,而架构是从概念上分离应用程序的方式。你可以在很好地保持相同架构的同时,选择不同的文件结构方式。没有完美的文件结构,因此请根据项目的不同选择适合你的文件结构。

布局

页面整体布局是一个产品最外层的框架结构, 这里使用了 vue-router 路由嵌套, 所以一般情况下,你增加或者修改页面只会影响 app-main这个主体区域。其它配置在 layout 中的内容如:底部导航都是不会随着你主体页面变化而变化的。

/foo                                  /bar
+------------------+                  +-----------------+
| layout           |                  | layout          |
| +--------------+ |                  | +-------------+ |
| | foo.vue      | |  +------------>  | | bar.vue     | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

这里在 app-main 外部包了一层 keep-alive 主要是为了缓存 的,如不需要可自行去除。

样式

CSS Modules

在样式开发过程中,有两个问题比较突出:

  • 全局污染 —— CSS 文件中的选择器是全局生效的,不同文件中的同名选择器,根据 build 后生成文件中的先后顺序,后面的样式会将前面的覆盖;
  • 选择器复杂 —— 为了避免上面的问题,我们在编写样式的时候不得不小心翼翼,类名里会带上限制范围的标示,变得越来越长,多人开发时还很容易导致命名风格混乱,一个元素上使用的选择器个数也可能越来越多,最终导致难以维护。

好在 vue 为我们提供了 scoped 可以很方便的解决上述问题。 它顾名思义给 css 加了一个域的概念。

/* 编译前 */
.example {
  color: red;
}

/* 编译后 */
.example[_v-f3f3eg9] {
  color: red;
}

只要加上 style scoped 这样 css 就只会作用在当前组件内了。

TIP

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

目录结构

vue-h5-template 所有全局样式都在 @/src/styles 目录下设置

├── styles
│   ├── _animation               # 按钮样式
│   ├── index.scss               # 全局通用样式
│   ├── _mixin.scss              # 全局mixin
│   ├── _transition.scss         # 过渡效果
│   └── variables.scss           # 全局变量

一次完整的与服务器端交互

在 vue-h5-template 中,一个完整的前端 UI 交互到服务端处理流程是这样的:

  1. UI 组件交互操作
  2. 调用统一管理的 api service 请求函数
  3. 使用封装的 request.js 发送请求
  4. 获取服务端返回
  5. 更新 data

request.js

其中,@/src/utils/request.js 是基于 Server目录的 http 的二次封装,便于统一处理 POST,GET 等请求方式。具体可以参看项目代码。

...
export class Request {
  instance

  static getInstance() {
    if (!this.instance) {
      this.instance = new Request()
    }
    return this.instance
  }

  async post(options = {}) {
    const { data } = await service({
      method: 'post',
      ...options
    })
    return data
  }
    ...
}

例子

定义接口地址统一管理 src/constants/api/test.js

export const CARDS = '/admin/cards'

请求方法
src/core/interactors/test-interactor.js

async getTest() {
  try {
    const options = { url: CARDS }
    return await this.service.get(options)
  } catch (error) {
    throw error
  }
}

请求方式 src/utils/request.js

async get(options = {}) {
   const { data } = await service({
     method: 'get',
     ...options
   })
   return data
}

TIP

目录结构不要纠结,个人习惯而定

页面使用 src/pages/test/index.vue

# 生命周期
async created() {
   if (this.id) {
     await this.handleGetTest()
   }
}

# 请求
async handleGetTest() {
  try {
    const test = await testInteractor.getTest(this.id)
    this.addressInfo = Object.assign({}, test)
  } catch (error) {
    console.log(error)
  }
}

可能大家会觉得很繁琐,这么多文件容易搞混,重复编写代码等等,不要着急,本模板配置了自动生成文件,上述除了视图/View层这块需要你手动去编写代码,其他的我们都会去一键生成,接下来我们就来讲讲使用方法。

生成所需文件

在开发过程中,无论我们添加页面也好还是添加组件等等。都需要不停地新建 .vue文件(或者其他框架或者html/js/css文件)

以Vue项目为例, 我们新建一个component 或 view 的时候,需要新建一个.vue文件,然后写