Vue 探索与实践 - 前端加油站 - 科蚁网
请选择 进入手机版 | 继续访问电脑版

Vue 探索与实践

本文主要讲了实际业务在结合 vue 开发的过程中的探索与实践。

业务介绍

基于目标用户的孩子画像,打通、聚合京东现有体系关联资源,建立共生关系的开放式生态平台,涵盖满足家庭陪伴孩子成长过程中的多维度需求。覆盖场景场景导购、精准推荐、专属权益等,为京东有孩家庭购物提供优质优购体验。在项目开发中我们遇到的问题主要有以下三个:

  • 接口众多:近90个数据接口,数据字段不规范、不统一、难理解,接口开发经常延期且频繁变更;
  • 交互复杂:各种交互及状态,且一态多用,给用户展示的是多状态共同作用的结果,用户操作异步更新页面;
  • 快速上线:同时规划多版本,多版本并行开发。

技术选型

技术选型要对症下药,为了统一管理接口和数据,所采用的框架要有统一的数据中心,能做到视图与逻辑的分离,用数据来驱动视图,项目可以工程化来应对快速上线,以及利于后期维护。从学习成本来说,Vue 更容易上手,更轻量,结合 Vuex 管理状态,视图逻辑和数据的耦合度低,项目结构清晰明了,Vue 的可扩展性也非常好。Vue 核心技术主要有以下几点:

  • 声明式渲染:通过简洁的模板语法来声明式地将数据渲染进 DOM,DOM 状态是数据状态的一个映射。
  • 组件系统:跟大多数前端框架一样,都是把 UI 结构拆解成小的、可复用的组件树,然后像零件一样组装它们,Vue 还有比较独特的地方,那就是单文件组件,把归属于同一组件的模板、脚本、样式放在一个文件中,你不必再同时维护一个组件的多个文件,这样是不是很酷。
  • 客户端路由:结合 vue-router,Vue 就可以实现一个 SPA 应用了,主要通过 hash 值来控制路由,路由又可以传递状态参数给组件。
  • 状态管理:Vue 的基本状态触发过程是,用户行为使得 state 发生变化,state 的变化又触发视图的更新。而结合 Vuex 则可以管理全局的数据。

项目详解

项目结构

项目结构

项目开发

下面将分为以下几方面来阐述:开发辅助、路由、组件化、mixins、常量管理、数据中心、环境兼容、滚动行为。

开发依赖

项目采用 Webpack,并结合了 ESLint 和 Babel 等来进行开发和编译打包,Webpack 的基本配置不详讲,在基本配置的基础上,再分了开发环境的生产环境的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Dev 的配置
module.exports = merge(base, {
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
‘process.env.NODE_ENV’JSON.stringify(process.env.NODE_ENV ||‘development’)
}),
new HtmlWebpackPlugin({
filename: ‘index.html’,
template: ‘../index.html’
})
]
})
// Prod 的配置
module.exports = merge.smart(base, {
module: {
loaders: [
{
test: /\.s[a|c]ss$/,
loader: ExtractTextPlugin.extract({
fallbackLoader: “style-loader”,
loader: ‘css!sass’
})
}
]
},
plugins: [
new ExtractTextPlugin(‘style.css’),
new webpack.DefinePlugin({
‘process.env.NODE_ENV’JSON.stringify(process.env.NODE_ENV || ‘production’)
}),
new webpack.optimize.CommonsChunkPlugin({
name: ‘vendor’,
filename: ‘vendor.js’
}),
new webpack.LoaderOptionsPlugin(loadersConf),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
})

开发环境中,用 express 和 webpack-dev-middleware 来搭建一个 dev server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require(‘express’)
const webpackDevMiddleware = require(‘webpack-dev-middleware’)
const webpack = require(‘webpack’)
const conf = require(‘./webpack.dev.conf’)
const app = express()
const port = process.env.PORT || 8080
conf.entry.app = [‘webpack-hot-middleware/client’, conf.entry.app]
const compiler = webpack(conf)
app.use(webpackDevMiddleware(compiler, {
publicPath: conf.output.publicPath,
stats: {
colors: true,
chunks: false
}
}))
app.use(require(‘webpack-hot-middleware’)(compiler))
app.listen(port, () => {
console.log(`server started at localhost:${port}`)
})

路由

一个路由子项如下:

1
2
3
4
5
{
name: ‘index’path‘/index’,
meta: {title‘陪伴空间’pv50profilestruevisitortrue, verify () { return true }},
components: {default: Index2, navbar: Navbar}
}

其中,配置里的 meta 包含了该页面(视图)的配置信息:

  • title:页面的标题
  • pv:用作记录页面的 PV
  • profiles:用于判断是否需要有孩子才能进入这个页面
  • visitor: 是否支持游客访问
  • verify:如果支持游客访问,可选的额外的放行校验

问题:在 ios 里,单页面应用切换视图时页面标题不能更新
解决:切换路由时用 iframe 加载一个空页面即可触发 title 更新,如下所示

1
2
3
4
5
6
7
8
9
10
11
const iframeLoad = (src) => {
let iframe = document.createElement(‘iframe’)
iframe.style.display = ‘none’
iframe.src = src
document.body.appendChild(iframe)
iframe.addEventListener(‘load’function() {
setTimeout(function() {
iframe.remove()
}, 0)
})
}

路由中还要处理比较多的事情,在 router.beforeEach 中处理传进页面的参数,请求登陆状态和档案数据等基本接口,上报 PV,在 router.afterEach 中处理比较次要的事情。

组件化

接下来讲的是项目中的单文件组件。下面是一段特别编辑过的单文件组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<div v-show=“isShow” class=“test”>
<slot>slot>
<slot name=“slot2”>slot>
<template v-if=“testProp”>template>
<template v-else>template>
<div @click=“changeNickname && changeNickname(‘小镇’)”>div>
<div @click=“close” class=“test_btn”>{{btnText}}div>
div>
template>
<script>
import Utils from ‘@/utils’
export default {
props: {
testProp: {
type: [NumberString],
required: true
},
changeNickname: Function
},
data () {
return {
isShow: false,
btnText: ,
closeFn: null
}
},
methods: {
close () {
this.isShow = false
this.closeFn && this.closeFn()
},
// 除了 poops 传参,函数传参也是一种方式
open (btnText = , closeFn) {
this.isShow = true
this.btnText = btnText
this.closeFn = closeFn
}
}
}
script>
<style lang=“sass”>
@import “common”;
.test {
background-imageurl(~@img/test/bg.png);
}
style>

slot 对于可复用组件来说意义重大,因为我们在实际的应用中,组件往往大同小异,看起来可以做成组件的模块总会或多或少差异的地方,通过参数来控制这些差异也是可行的,但非常不利于组件的扩展,所以这些地方就交给 slot 来应对,slot 的意思是插槽,意指我们能在父组件中需要的时候,给组件填充自定义内容。

父组件通过 props 给子组件传值,或者,父组件还可以通过子组件实例的方法来给子组件传参(如代码中的 open 方法)。

子组件可以通过 emit 触发事件来向上通信,或者,通过直接调用作为 prop 传进来的父组件方法也可以实现向上通信(如代码中的 changeNickname)。

mixins

通常来说,不建议使用全局的 mixin,但总会有特殊需要,比如在本项目中,由于埋点和其他需要,几乎每个组件都要用到几个公用的全局数据,所以放到全局的 mixin 是最好不过的了 Vue.mixin(mixins)。使用全局的 mixin 要注意的是,不要把逻辑放到 mixin 里,因为每个组件都会执行一遍 mixin 的内容,组件一多就非常可怕了。

常量管理

为了以后能更好地维护代码,需要对常量作归集管理,这里的常量主要是链接和数据的字段等。

1
2
3
4
5
// 链接常量的统一管理
export const REBUY_LIST = `${NIGHT}/re_purchase_detail`
export const REBUY_SWITCH = `${NIGHT}/re_purchase_switch_good`
export const REBUY_REMIND = `${NIGHT}/re_purchase_remind`
// …
1
2
3
4
5
6
7
8
9
// 数据字段的统一管理
export const ID = ‘id’
export const SKU = ‘sku’
export const LINK = ‘link’
export const NAME = ‘name’
export const IMAGE = ‘image’
export const JD_PRICE = ‘jdPrice’
export const PRICE = ‘price’
// …

统一的常量管理也有利于规范统一,比如数据字段,接口给到的数据可能有字段不统一,或者不表意,或者脏数据多等问题,这就需要在获取到后端数据后对其进行“修剪”,规范的统一的字段名也有利于组件化。

数据中心

vuex

项目用了 vuex 来统一管理数据,在 view 组件中通过 vuex 提供的 mapActions 和 mapGetters 来求取数据,如下代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
computed: {
…mapGetters({
cate1st: ‘cate1st’,
cate2nd: ‘cate2nd’
})
},
methods: {
…mapActions([
‘getCate1st’,
‘getCate2nd’
])
}

而在“数据中心”中,getters 从 state 中取值,调用 action 请求后端接口,主动触发 mutation,在 mutation 里进行数据的“修剪”,得到我们真正想要的数据。大致过程如下图所示:

数据中心


鲜花
1

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)

该文章已有2人参与评论

请发表评论

全部评论

查看全部评论>>