作者簡介:
錢昱
多年前端工作經(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睡雇,就要從字典中找到它的描述 女并且顯示出來;下面故事開始了~
- 思考
有人說它抱,這不是過濾器 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)式化更新視圖,不會阻塞渲染鸟召,因此在下初步采用了這個方法想鹰。
- 實現(xiàn)
因為filter屬于 assettypes ,關(guān)于在Vue實例中assettypes的訪問鏈有以下幾個結(jié)論药版;具體代碼實踐可以參考:Codepen - filter test
asset_types包括 filters辑舷、 components、 directives槽片,以下所有的 asset_types都自行替換成前面幾項
子組件中的 asset_types訪問不到父組件中的 asset_types何缓,但是可以訪問到全局注冊的掛載在 options.asset_types.proto上的 asset_types肢础,這里對應(yīng)源碼 src/core/util/options.js
全局注冊方法Vue.assettypes,比如Vue.filters注冊的assettypes會掛載到根實例(其他實例的 options.asset_types.proto上碌廓,并被以后所有創(chuàng)建的Vue實例繼承传轰,也就是說,以后所有創(chuàng)建的Vue實例都可以訪問到
組件的slot的作用域僅限于它被定義的地方谷婆,也就是它被定義的組件中慨蛙,訪問不到父組件的 asset_types,但是可以訪問到全局定義的 asset_types
同理纪挎,因為main.js中的 newVue()實例是根實例期贫,它中注冊的 asset_types會被掛載在 options.asset_types上而不是
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
.
options
.
filters
[
id
]
return
mth
&&
mth
(
val
)
||
val
}
},
created
()
{
// 把根組件中的filters響應(yīng)式化
Vue
.
util
.
defineReactive
(
this
.
options
,
'filters'
,
this
.
options
.
filters
)
},
mounted
()
{
registerFilters
.
call
(
this
)
.
then
(
data
=>
// 這里獲取到數(shù)據(jù)字典的data
)
}
}
</script>
注冊filter的js
// utils/filters
import
as
Api
from
'api'
/**
獲取并注冊過濾器
注冊在
options.filters上不是
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
.
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)式化的 options.filters,所以當(dāng)異步獲取的數(shù)據(jù)被賦給
options.filters的時候讽营,會觸發(fā)這個組件render watcher的重新渲染,這時候再獲取 rootFilters方法的時候就能取到filter了膜蠢;
那這里為什么不用Vue.filter方法直接注冊呢狡蝶,因為 Object.defineProperty不能監(jiān)聽 proto上數(shù)據(jù)的變動贮勃,而全局Vue.filter是將過濾器注冊在了根組件 options.asset_types.proto上寂嘉,因此其變動不能被響應(yīng)泉孩。
這里的代碼可以進一步完善寓搬,但是這個方法存在一定的問題句喷,首先這里使用了 Vue.util上不穩(wěn)定的方法,另外在使用中到處可見 this.options這樣訪問vue實例內(nèi)部屬性的情況兄春,不太文明锡溯,讀起來也讓人困惑祭饭。
因此在這個項目做完等待測試的時候我思考了一下甜癞,誰說過濾器就一定放在filters里面 -。-蒸辆,也可以使用mixin來實現(xiàn)嘛
2.2 使用 mixin
使用mixin要注意一點躬贡,因為vue中把data里所有以 _拂玻、 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)裕膀。