?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([email protected]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人參與評論

請發表評論

全部評論

查看全部評論>>

老子是皇帝彩金