數(shù)據(jù)動態(tài)過濾技巧在 Vue 項目中的實踐

作者簡介:
錢昱
多年前端工作經(jīng)驗 《JavaScript 設(shè)計模式精講》 作者,主要分享前端方面技術(shù)博客
公眾號:前端下午茶

這個問題是在下在做一個 Vue 項目中遇到的實際場景尺碰,這里記錄一下我遇到問題之后的思考和最后怎么解決的(老年程序員記性不好 -耘戚。-)郊愧,過程中會涉及到一些Vue源碼的概念比如 $mount懦底、 render watcher等迁霎,如果不太了解的話可以瞅瞅 Vue源碼閱讀系列文章 ~

問題是這樣的:頁面從后臺拿到的數(shù)據(jù)是由 0烹看、 1之類的key,而這個key代表的value比如 0-女垮媒、 1-男的對應(yīng)關(guān)系是要從另外一個數(shù)據(jù)字典接口拿到的;類似于這樣的Api:

{

"SEX_TYPE"
:

[

{

"paramValue"
:

0
,

"paramDesc"
:

"女"

},

{

"paramValue"
:

1
,

"paramDesc"
:

"男"

}

]

}

那么如果view拿到的是 0睡雇,就要從字典中找到它的描述 女并且顯示出來;下面故事開始了~

  1. 思考
    有人說它抱,這不是過濾器 filter 要做的事么,直接 Vue.filter 不就行了观蓄,然而問題是這個filter 是要等待異步的數(shù)據(jù)字典接口返回之后才能拿到祠墅,如果在 $mount 的時候這個filter沒有找到,那么就會導(dǎo)致錯誤影響之后的渲染(白屏并報 undefined錯)毁嗦;

我想到的解決方法有兩個:

把接口變?yōu)橥剑?beforeCreate或 created鉤子中同步地獲取數(shù)據(jù)字典接口狗准,保證在 $mount的時候可以拿到注冊好的filter克锣,保證時序,但是這樣會阻塞掛載腔长,延長白屏?xí)r間袭祟,因此不推介;

把filter的注冊變?yōu)楫惒嚼谈剑讷@取filter之后通知 render watcher 更新自己巾乳,這樣可以利用vue自己的響應(yīng)式化更新視圖,不會阻塞渲染鸟召,因此在下初步采用了這個方法想鹰。

  1. 實現(xiàn)
    因為filter屬于 assettypes ,關(guān)于在Vue實例中assettypes的訪問鏈有以下幾個結(jié)論药版;具體代碼實踐可以參考:Codepen - filter test

asset_types包括 filters辑舷、 components、 directives槽片,以下所有的 asset_types都自行替換成前面幾項

子組件中的 asset_types訪問不到父組件中的 asset_types何缓,但是可以訪問到全局注冊的掛載在 root.options.asset_types.proto上的 asset_types肢础,這里對應(yīng)源碼 src/core/util/options.js

全局注冊方法Vue.assettypes,比如Vue.filters注冊的assettypes會掛載到根實例(其他實例的 root)的options.asset_types.proto上碌廓,并被以后所有創(chuàng)建的Vue實例繼承传轰,也就是說,以后所有創(chuàng)建的Vue實例都可以訪問到

組件的slot的作用域僅限于它被定義的地方谷婆,也就是它被定義的組件中慨蛙,訪問不到父組件的 asset_types,但是可以訪問到全局定義的 asset_types

同理纪挎,因為main.js中的 newVue()實例是根實例期贫,它中注冊的 asset_types會被掛載在 root.options.asset_types上而不是 root.options.asset_types.proto

根據(jù)以上幾個結(jié)論,可以著手coding了~

2.1 使用根組件的filters
因此首先我考慮的是把要注冊的filter掛載到根組件上异袄,這樣其他組件通過訪問 $root可以拿到注冊的filter通砍,這里的實現(xiàn):

<template>

<div>

{{ rootFilters( sexVal )}}

</div>

</template>

<script

type

'text/javascript'

import

Vue
from
'vue'

import

{
registerFilters
}
from
'utils/filters'

export

default

{

data

()

{

return

{

    sexVal

:

1

// 性別

}

},

methods

:

{

/* 根組件上的過濾器 */

  rootFilters

(
val
,
id
=

'SEX_TYPE'
)

{

const
mth
=

this
.
root .options
.
filters
[
id
]

return
mth
&&
mth
(
val
)

||
val

}

},

created

()

{

// 把根組件中的filters響應(yīng)式化

Vue
.
util
.
defineReactive
(
this
.
root .options
,

'filters'
,

this
.
root .options
.
filters
)

},

mounted

()

{

  registerFilters

.
call
(
this
)

.
then
(
data
=>

// 這里獲取到數(shù)據(jù)字典的data

)

}

}

</script>

注冊filter的js

// utils/filters

import

as
Api
from
'api'

/**

  • 獲取并注冊過濾器

  • 注冊在root.options.filters上不是root.options.filters.proto

  • 注意這里的this是vue實例,需要用call或apply調(diào)用

  • @returns {Promise}

*/

export

function
registerFilters
()

{

return

Api
.
sysParams
()

// 獲取數(shù)據(jù)字典的Api烤蜕,返回的是promise

.
then
(({
data
})

=>

{

Object
.
keys
(
data
).
forEach
(
T
=>

this
.
set ( this .root
.
$options
.
filters
,
T
,
val
=>

{

const
tar
=
data
[
T
].
find
(
item
=>
item
[
'paramValue'
]

===
val
)

return
tar
[
'paramDesc'
]

||

''

})

)

return
data

})

.
catch
(
err
=>
console
.
error
(
err
,

' in utils/filters.js'
))

}

這樣把根組件上的filters變?yōu)轫憫?yīng)式化的封孙,并且在渲染的時候因為在 rootFilters方法中訪問了已經(jīng)在created中被響應(yīng)式化的 root.options.filters,所以當(dāng)異步獲取的數(shù)據(jù)被賦給 root.options.filters的時候讽营,會觸發(fā)這個組件render watcher的重新渲染,這時候再獲取 rootFilters方法的時候就能取到filter了膜蠢;

那這里為什么不用Vue.filter方法直接注冊呢狡蝶,因為 Object.defineProperty不能監(jiān)聽 proto上數(shù)據(jù)的變動贮勃,而全局Vue.filter是將過濾器注冊在了根組件 root.options.asset_types.proto上寂嘉,因此其變動不能被響應(yīng)泉孩。

這里的代碼可以進一步完善寓搬,但是這個方法存在一定的問題句喷,首先這里使用了 Vue.util上不穩(wěn)定的方法,另外在使用中到處可見 this.root.options這樣訪問vue實例內(nèi)部屬性的情況兄春,不太文明锡溯,讀起來也讓人困惑祭饭。

因此在這個項目做完等待測試的時候我思考了一下甜癞,誰說過濾器就一定放在filters里面 -。-蒸辆,也可以使用mixin來實現(xiàn)嘛

2.2 使用 mixin
使用mixin要注意一點躬贡,因為vue中把data里所有以 _拂玻、 開頭的變量都作為內(nèi)部保留的變量檐蚜,并不代理到當(dāng)前實例上闯第,因此直接 this._xx是無法訪問的咳短,需要通過 this.data._xx來訪問咙好。

// mixins/sysParamsMixin.js

import

as
Api
from
'api'

export

default

{

data
()

{

return

{

  _filterFunc

:

null
,

// 過濾器函數(shù)

  _sysParams

:

null
,

// 獲取數(shù)據(jù)字典

  _sysParamsPromise

:

null

// 獲取sysParams之后返回的Promise

}

},

methods
:

{

/* 注冊過濾器到_filterFunc中 */

_getSysParamsFunc

()

{

const

{
$data
}

=

this

return
$data
.
_sysParamsPromise
||

(
$data
.
_sysParamsPromise
=

Api
.
sysParams
()

.
then
(({
data
})

=>

{

this
.
$data
.
_sysParams
=
data

this
.
$data
.
_filterFunc
=

{}

Object
.
keys
(
data
).
forEach
(
paramKey
=>

this
.
$data
.
_filterFunc
[
paramKey
]

=
val
=>

{

const
tar
=
data
[
paramKey
].
find
(
item
=>
item
[
'paramValue'
]

===
val
)

return
tar
&&
tar
[
'paramDesc'
]

||

''

})

return
data

})

.
catch
(
err
=>
console
.
error
(
err
,

' in src/mixins/sysParamsMixin.js'
)))

},

/* 按照鍵值獲取單個過濾器 */

_rootFilters

(
val
,
id
=

'SEX_TYPE'
)

{

const
func
=

this
.
$data
.
_filterFunc

const
mth
=
func
&&
func
[
id
]

return
mth
&&
mth
(
val
)

||
val

},

/* 獲取數(shù)據(jù)字典 */

_getSysParams

()

{

return

this
.
$data
.
_sysParams

}

}

}

這里把 Api的 promise 保存下來勾效,如果其他地方還用到的話直接返回已經(jīng)是 resolved狀態(tài)的 promise绘迁,就不用再次去請求數(shù)據(jù)了卒密。另外為了在其他實例中也可以方便的訪問,這里掛載在根組件上膛腐。

那在我們的根組件中怎么使用呢:

// src/main.js

import
sysParamsMixin from
'mixins/sysParamsMixin'

new

Vue
({

el
:

'#app'
,

mixins
:

[
sysParamsMixin
],

render
:
h
=>
h
(
App
),

})

在需要用過濾器的組件中:

<template>

<div>

{{ $root._rootFilters( sexVal )}}

</div>

</template>

<script

type

'text/javascript'

export

default

{

data

()

{

return

{
sexVal
:

1

}

},

mounted

()

{

this
.
$root
.
_getSysParamsFunc
()

.
then
(
data
=>

// 這里獲取到數(shù)據(jù)字典的data

)

}

}

</script>

這里不僅注冊了過濾器哲身,而且也暴露了數(shù)據(jù)字典勘天,以方便某些地方的列表顯示脯丝,畢竟這是實際項目中常見的場景伏伐。

當(dāng)然如果使用 vuex 更好藐翎,不過這里的場景個人覺得沒必要用 vuex,如果還有更好的方法歡迎討論一下下啊~

作者簡介:
錢昱
多年前端工作經(jīng)驗 《JavaScript 設(shè)計模式精講》 作者堤器,主要分享前端方面技術(shù)博客
公眾號:前端下午茶

本文已經(jīng)獲得錢昱老師授權(quán)轉(zhuǎn)發(fā)闸溃,其他人若有興趣轉(zhuǎn)載圈暗,請直接聯(lián)系作者授權(quán)裕膀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昼扛,一起剝皮案震驚了整個濱河市抄谐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毅厚,老刑警劉巖吸耿,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咽安,死亡現(xiàn)場離奇詭異妆棒,居然都是意外死亡糕珊,警方通過查閱死者的電腦和手機毅糟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門纠脾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苟蹈,你說我怎么就攤上這事慧脱『睾龋” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵氮采,是天一觀的道長鹊漠。 經(jīng)常有香客問我躯概,道長娶靡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任结执,我火速辦了婚禮献幔,結(jié)果婚禮上蜡感,老公的妹妹穿的比我還像新娘郑兴。我一直安慰自己情连,他們只是感情好览效,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布挽拔。 她就那樣靜靜地躺著螃诅,像睡著了一般术裸。 火紅的嫁衣襯著肌膚如雪亭枷。 梳的紋絲不亂的頭發(fā)上奶栖,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天宣鄙,我揣著相機與錄音冻晤,去河邊找鬼苇羡。 笑死设江,一個胖子當(dāng)著我的面吹牛叉存,可吹牛的內(nèi)容都是我干的歼捏。 我是一名探鬼主播笨篷,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼练俐,長吁一口氣:“原來是場噩夢啊……” “哼腺晾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起念颈,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤嗡靡,失蹤者是張志新(化名)和其女友劉穎讨彼,沒想到半個月后哈误,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哩至,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年蜜自,在試婚紗的時候發(fā)現(xiàn)自己被綠了菩貌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡重荠,死狀恐怖箭阶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戈鲁,我是刑警寧澤仇参,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站婆殿,受9級特大地震影響诈乒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜癌压,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一帜消、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦媳溺、人聲如沸捉兴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拙已。三九已至索昂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間领斥,已是汗流浹背孽锥。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工韵丑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钓株,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓纲堵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親督弓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容